|
| 1 | +# Logic is as follows: |
| 2 | +# 1. Download cairo source code: https://cairographics.org/releases/cairo-<version>.tar.xz |
| 3 | +# 2. Verify the downloaded file using the sha256sums file: https://cairographics.org/releases/cairo-<version>.tar.xz.sha256sum |
| 4 | +# 3. Extract the downloaded file. |
| 5 | +# 4. Create a virtual environment and install meson and ninja. |
| 6 | +# 5. Run meson build in the extracted directory. Also, set required prefix. |
| 7 | +# 6. Run meson compile -C build. |
| 8 | +# 7. Run meson install -C build. |
| 9 | + |
| 10 | +import hashlib |
| 11 | +import logging |
| 12 | +import os |
| 13 | +import subprocess |
| 14 | +import sys |
| 15 | +import tarfile |
| 16 | +import tempfile |
| 17 | +import typing |
| 18 | +import urllib.request |
| 19 | +from contextlib import contextmanager |
| 20 | +from pathlib import Path |
| 21 | +from sys import stdout |
| 22 | + |
| 23 | +CAIRO_VERSION = "1.18.0" |
| 24 | +CAIRO_URL = f"https://cairographics.org/releases/cairo-{CAIRO_VERSION}.tar.xz" |
| 25 | +CAIRO_SHA256_URL = f"{CAIRO_URL}.sha256sum" |
| 26 | + |
| 27 | +VENV_NAME = "meson-venv" |
| 28 | +BUILD_DIR = "build" |
| 29 | +INSTALL_PREFIX = Path(__file__).parent.parent / "third_party" / "cairo" |
| 30 | + |
| 31 | +logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") |
| 32 | +logger = logging.getLogger(__name__) |
| 33 | + |
| 34 | + |
| 35 | +def is_ci(): |
| 36 | + return os.getenv("CI", None) is not None |
| 37 | + |
| 38 | + |
| 39 | +def download_file(url, path): |
| 40 | + logger.info(f"Downloading {url} to {path}") |
| 41 | + block_size = 1024 * 1024 |
| 42 | + with urllib.request.urlopen(url) as response, open(path, "wb") as file: |
| 43 | + while True: |
| 44 | + data = response.read(block_size) |
| 45 | + if not data: |
| 46 | + break |
| 47 | + file.write(data) |
| 48 | + |
| 49 | + |
| 50 | +def verify_sha256sum(path, sha256sum): |
| 51 | + with open(path, "rb") as file: |
| 52 | + file_hash = hashlib.sha256(file.read()).hexdigest() |
| 53 | + if file_hash != sha256sum: |
| 54 | + raise Exception("SHA256SUM does not match") |
| 55 | + |
| 56 | + |
| 57 | +def extract_tar_xz(path, directory): |
| 58 | + with tarfile.open(path) as file: |
| 59 | + file.extractall(directory) |
| 60 | + |
| 61 | + |
| 62 | +def run_command(command, cwd=None, env=None): |
| 63 | + process = subprocess.Popen(command, cwd=cwd, env=env) |
| 64 | + process.communicate() |
| 65 | + if process.returncode != 0: |
| 66 | + raise Exception("Command failed") |
| 67 | + |
| 68 | + |
| 69 | +@contextmanager |
| 70 | +def gha_group(title: str) -> typing.Generator: |
| 71 | + if not is_ci(): |
| 72 | + yield |
| 73 | + return |
| 74 | + print(f"\n::group::{title}") |
| 75 | + stdout.flush() |
| 76 | + try: |
| 77 | + yield |
| 78 | + finally: |
| 79 | + print("::endgroup::") |
| 80 | + stdout.flush() |
| 81 | + |
| 82 | + |
| 83 | +def set_env_var_gha(name: str, value: str) -> None: |
| 84 | + if not is_ci(): |
| 85 | + return |
| 86 | + env_file = os.getenv("GITHUB_ENV", None) |
| 87 | + if env_file is None: |
| 88 | + return |
| 89 | + with open(env_file, "a") as file: |
| 90 | + file.write(f"{name}={value}\n") |
| 91 | + stdout.flush() |
| 92 | + |
| 93 | + |
| 94 | +def get_ld_library_path(prefix: Path) -> str: |
| 95 | + # given a prefix, the ld library path can be found at |
| 96 | + # <prefix>/lib/* or sometimes just <prefix>/lib |
| 97 | + # this function returns the path to the ld library path |
| 98 | + |
| 99 | + # first, check if the ld library path exists at <prefix>/lib/* |
| 100 | + ld_library_paths = list(prefix.glob("lib/*")) |
| 101 | + if len(ld_library_paths) == 1: |
| 102 | + return ld_library_paths[0].absolute().as_posix() |
| 103 | + |
| 104 | + # if the ld library path does not exist at <prefix>/lib/*, |
| 105 | + # return <prefix>/lib |
| 106 | + ld_library_path = prefix / "lib" |
| 107 | + if ld_library_path.exists(): |
| 108 | + return ld_library_path.absolute().as_posix() |
| 109 | + return "" |
| 110 | + |
| 111 | + |
| 112 | +def main(): |
| 113 | + if sys.platform == "win32": |
| 114 | + logger.info("Skipping build on windows") |
| 115 | + return |
| 116 | + |
| 117 | + with tempfile.TemporaryDirectory() as tmpdir: |
| 118 | + with gha_group("Downloading and Extracting Cairo"): |
| 119 | + logger.info(f"Downloading cairo version {CAIRO_VERSION}") |
| 120 | + download_file(CAIRO_URL, os.path.join(tmpdir, "cairo.tar.xz")) |
| 121 | + |
| 122 | + logger.info("Downloading cairo sha256sum") |
| 123 | + download_file(CAIRO_SHA256_URL, os.path.join(tmpdir, "cairo.sha256sum")) |
| 124 | + |
| 125 | + logger.info("Verifying cairo sha256sum") |
| 126 | + with open(os.path.join(tmpdir, "cairo.sha256sum")) as file: |
| 127 | + sha256sum = file.read().split()[0] |
| 128 | + verify_sha256sum(os.path.join(tmpdir, "cairo.tar.xz"), sha256sum) |
| 129 | + |
| 130 | + logger.info("Extracting cairo") |
| 131 | + extract_tar_xz(os.path.join(tmpdir, "cairo.tar.xz"), tmpdir) |
| 132 | + |
| 133 | + with gha_group("Installing meson and ninja"): |
| 134 | + logger.info("Creating virtual environment") |
| 135 | + run_command([sys.executable, "-m", "venv", os.path.join(tmpdir, VENV_NAME)]) |
| 136 | + |
| 137 | + logger.info("Installing meson and ninja") |
| 138 | + run_command( |
| 139 | + [ |
| 140 | + os.path.join(tmpdir, VENV_NAME, "bin", "pip"), |
| 141 | + "install", |
| 142 | + "meson", |
| 143 | + "ninja", |
| 144 | + ] |
| 145 | + ) |
| 146 | + |
| 147 | + env_vars = { |
| 148 | + # add the venv bin directory to PATH so that meson can find ninja |
| 149 | + "PATH": f"{os.path.join(tmpdir, VENV_NAME, 'bin')}{os.pathsep}{os.environ['PATH']}", |
| 150 | + } |
| 151 | + |
| 152 | + with gha_group("Building and Installing Cairo"): |
| 153 | + logger.info("Running meson setup") |
| 154 | + run_command( |
| 155 | + [ |
| 156 | + os.path.join(tmpdir, VENV_NAME, "bin", "meson"), |
| 157 | + "setup", |
| 158 | + BUILD_DIR, |
| 159 | + f"--prefix={INSTALL_PREFIX.absolute().as_posix()}", |
| 160 | + "--buildtype=release", |
| 161 | + "-Dtests=disabled", |
| 162 | + ], |
| 163 | + cwd=os.path.join(tmpdir, f"cairo-{CAIRO_VERSION}"), |
| 164 | + env=env_vars, |
| 165 | + ) |
| 166 | + |
| 167 | + logger.info("Running meson compile") |
| 168 | + run_command( |
| 169 | + [ |
| 170 | + os.path.join(tmpdir, VENV_NAME, "bin", "meson"), |
| 171 | + "compile", |
| 172 | + "-C", |
| 173 | + BUILD_DIR, |
| 174 | + ], |
| 175 | + cwd=os.path.join(tmpdir, f"cairo-{CAIRO_VERSION}"), |
| 176 | + env=env_vars, |
| 177 | + ) |
| 178 | + |
| 179 | + logger.info("Running meson install") |
| 180 | + run_command( |
| 181 | + [ |
| 182 | + os.path.join(tmpdir, VENV_NAME, "bin", "meson"), |
| 183 | + "install", |
| 184 | + "-C", |
| 185 | + BUILD_DIR, |
| 186 | + ], |
| 187 | + cwd=os.path.join(tmpdir, f"cairo-{CAIRO_VERSION}"), |
| 188 | + env=env_vars, |
| 189 | + ) |
| 190 | + |
| 191 | + logger.info(f"Successfully built cairo and installed it to {INSTALL_PREFIX}") |
| 192 | + |
| 193 | + |
| 194 | +if __name__ == "__main__": |
| 195 | + if "--set-env-vars" in sys.argv: |
| 196 | + with gha_group("Setting environment variables"): |
| 197 | + # append the pkgconfig directory to PKG_CONFIG_PATH |
| 198 | + set_env_var_gha( |
| 199 | + "PKG_CONFIG_PATH", |
| 200 | + f"{Path(get_ld_library_path(INSTALL_PREFIX), 'pkgconfig').as_posix()}{os.pathsep}" |
| 201 | + f'{os.getenv("PKG_CONFIG_PATH", "")}', |
| 202 | + ) |
| 203 | + set_env_var_gha( |
| 204 | + "LD_LIBRARY_PATH", |
| 205 | + f"{get_ld_library_path(INSTALL_PREFIX)}{os.pathsep}" |
| 206 | + f'{os.getenv("LD_LIBRARY_PATH", "")}', |
| 207 | + ) |
| 208 | + sys.exit(0) |
| 209 | + main() |
0 commit comments