|
| 1 | +from subprocess import run as subprocess_run |
| 2 | +import pyjs_code_runner |
| 3 | +import socket |
| 4 | +from contextlib import closing, contextmanager, redirect_stdout |
| 5 | +import os |
| 6 | +from pyjs_code_runner.run import run |
| 7 | +from pyjs_code_runner.backend.backend import BackendType |
| 8 | +from pyjs_code_runner.get_file_filter import get_file_filter |
| 9 | +from pathlib import Path |
| 10 | +import shutil |
| 11 | +import sys |
| 12 | +import json |
| 13 | +import yaml |
| 14 | +import pprint |
| 15 | +import io |
| 16 | +from ..git_utils import git_branch_ctx, set_bot_user, make_pr_for_recipe, get_current_branch_name |
| 17 | + |
| 18 | + |
| 19 | +THIS_DIR = Path(os.getcwd()) |
| 20 | +MAIN_MOUNT_DIR = Path(THIS_DIR) / "main_mount" |
| 21 | +PREFIX_PATH = THIS_DIR / "prefix" |
| 22 | + |
| 23 | +START_MARKER = "/// BEGIN FONTCACHE" |
| 24 | +END_MARKER = "/// END FONTCACHE" |
| 25 | + |
| 26 | +PYTHON_FILENAME = "generate_fontcache.py" |
| 27 | +PYTHON_CODE = f""" |
| 28 | +import matplotlib |
| 29 | +from pathlib import Path |
| 30 | +
|
| 31 | +# This import is the one triggering the font cache building |
| 32 | +import matplotlib.pyplot |
| 33 | +
|
| 34 | +print('{START_MARKER}') |
| 35 | +with open(Path(matplotlib.__file__).parent / "fontlist.json") as fd: |
| 36 | + print(fd.read()) |
| 37 | +print('{END_MARKER}') |
| 38 | +""" |
| 39 | + |
| 40 | +ON_GITHUB_ACTIONS = os.environ.get('GITHUB_ACTIONS') == 'true' |
| 41 | + |
| 42 | + |
| 43 | +def find_free_port(): |
| 44 | + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: |
| 45 | + s.bind(("", 0)) |
| 46 | + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
| 47 | + return s.getsockname()[1] |
| 48 | + |
| 49 | + |
| 50 | +def update_matplotlib_fontcache(recipe_dir, target_branch_name): |
| 51 | + if not ON_GITHUB_ACTIONS: |
| 52 | + raise RuntimeError("Cannot update fontcache outside of github actions") |
| 53 | + |
| 54 | + matplotlib_folder = Path(recipe_dir) / "matplotlib" |
| 55 | + recipe_file = matplotlib_folder / "recipe.yaml" |
| 56 | + fontlist_file = matplotlib_folder / "src" / "fontlist.json" |
| 57 | + |
| 58 | + # Get current font list version |
| 59 | + with open(fontlist_file, "r") as fobj: |
| 60 | + fontlist = json.load(fobj) |
| 61 | + current_fontlist_version = fontlist["_version"] |
| 62 | + |
| 63 | + # Read current matplotlib version |
| 64 | + with open(recipe_file) as stream: |
| 65 | + recipe = yaml.safe_load(stream) |
| 66 | + matplotlib_version = recipe["context"]["version"] |
| 67 | + |
| 68 | + # Create prefix with installing pyjs and matplotlib = _x |
| 69 | + subprocess_run( |
| 70 | + [ |
| 71 | + "micromamba", |
| 72 | + "create", |
| 73 | + "--yes", |
| 74 | + "--prefix", |
| 75 | + PREFIX_PATH, |
| 76 | + "--platform=emscripten-wasm32", |
| 77 | + "-c", |
| 78 | + "https://prefix.dev/emscripten-forge-4x" if target_branch_name == "emscripten-4x" else "https://prefix.dev/emscripten-forge-dev", |
| 79 | + "-c", |
| 80 | + "https://prefix.dev/conda-forge", |
| 81 | + f"matplotlib={matplotlib_version}", |
| 82 | + "pyjs" |
| 83 | + ], |
| 84 | + check=True |
| 85 | + ) |
| 86 | + |
| 87 | + virtual_work_dir = Path("/") |
| 88 | + |
| 89 | + # Create runnable script in to mount in the emscripten fs |
| 90 | + MAIN_MOUNT_DIR.mkdir(exist_ok=True) |
| 91 | + with open(MAIN_MOUNT_DIR / PYTHON_FILENAME, "w") as fobj: |
| 92 | + fobj.write(PYTHON_CODE) |
| 93 | + |
| 94 | + buffer = io.StringIO() |
| 95 | + with redirect_stdout(buffer): |
| 96 | + run( |
| 97 | + conda_env=PREFIX_PATH, |
| 98 | + relocate_prefix="/", |
| 99 | + backend_type=BackendType.browser_main, |
| 100 | + script=PYTHON_FILENAME, |
| 101 | + async_main=False, |
| 102 | + mounts=[ |
| 103 | + (MAIN_MOUNT_DIR, virtual_work_dir) |
| 104 | + ], |
| 105 | + work_dir=virtual_work_dir, |
| 106 | + pyjs_dir=None, |
| 107 | + cache_dir=None, |
| 108 | + use_cache=False, |
| 109 | + host_work_dir=None, |
| 110 | + backend_kwargs=(lambda: dict(port=find_free_port(), slow_mo=1, headless=True))(), |
| 111 | + ) |
| 112 | + output = buffer.getvalue() |
| 113 | + |
| 114 | + # Cleanup |
| 115 | + shutil.rmtree(MAIN_MOUNT_DIR) |
| 116 | + shutil.rmtree(PREFIX_PATH) |
| 117 | + |
| 118 | + start = output.find(START_MARKER) |
| 119 | + end = output.find(END_MARKER) |
| 120 | + |
| 121 | + if start == -1 or end == -1: |
| 122 | + raise ValueError("Fontcache markers not found in the output") |
| 123 | + |
| 124 | + start += len(START_MARKER) |
| 125 | + |
| 126 | + new_fontlist = json.loads(output[start:end].strip()) |
| 127 | + |
| 128 | + if new_fontlist["_version"] == current_fontlist_version: |
| 129 | + print('Matplotlib fontlist is already up-to-date, nothing to do') |
| 130 | + return |
| 131 | + |
| 132 | + print('Matplotlib fontlist has changed! Updating it') |
| 133 | + |
| 134 | + # We are on GitHub Actions, we **cannot** **restore** the user account |
| 135 | + # therefore we just set the bot user and use an empty context manager |
| 136 | + set_bot_user() |
| 137 | + |
| 138 | + # Write new file |
| 139 | + with open(fontlist_file, "w") as fobj: |
| 140 | + fobj.write(json.dumps(new_fontlist, indent=2)) |
| 141 | + |
| 142 | + # Bump build number |
| 143 | + recipe["build"]["number"] = recipe["build"]["number"] + 1 |
| 144 | + |
| 145 | + with open(recipe_file, "w") as stream: |
| 146 | + yaml.safe_dump( |
| 147 | + recipe, |
| 148 | + stream, |
| 149 | + default_flow_style=False, |
| 150 | + sort_keys=False, |
| 151 | + ) |
| 152 | + |
| 153 | + # Commit and push |
| 154 | + branch_name = f"update-matplotlib-fontcache_for_{target_branch_name}" |
| 155 | + |
| 156 | + with git_branch_ctx(branch_name, stash_current=False): |
| 157 | + # commit the changes and make a PR |
| 158 | + pr_title = f"Update matplotlib fontcache for matplotlib {matplotlib_version}" |
| 159 | + make_pr_for_recipe(recipe_dir=recipe_dir, pr_title=pr_title, branch_name=branch_name, |
| 160 | + target_branch_name=target_branch_name, |
| 161 | + automerge=True) |
0 commit comments