.gitlab-ci.yml | 4 .gitlab-ci/ci.template | 3 .gitlab-ci/fedora-install.sh | 2 .gitlab-ci/freebsd-install.sh | 1 build-aux/fetch-testfonts.py | 193 ++++++++++++++++++++++++++++++++++++++++++ meson.build | 1 test/meson.build | 21 +++- test/test_issue431.py | 37 +++++--- 8 files changed, 244 insertions(+), 18 deletions(-) New commits: commit a352486ad464084ac765b8c954a6d3fc6dc3b0e6 Merge: 88ee3ad 1911d1c Author: Akira TAGOH <akira@xxxxxxxxx> Date: Wed Apr 23 15:15:48 2025 +0000 Merge branch 'crosTestFonts' into 'main' [Fontations] Local and container download of test font files See merge request fontconfig/fontconfig!386 commit 1911d1ce00ae3b088aeba5326e5205e64b2238f7 Author: Dominik Röttsches <drott@xxxxxxxxxxxx> Date: Wed Apr 23 15:27:21 2025 +0300 Migrate pytest testcase 431 to pre-downloaded fonts diff --git a/test/meson.build b/test/meson.build index da5ac1f..895dbab 100644 --- a/test/meson.build +++ b/test/meson.build @@ -92,9 +92,10 @@ if host_machine.system() != 'windows' if pytest.found() test('pytest', pytest, args: ['--tap'], workdir: meson.current_source_dir(), - env: ['builddir=@0@'.format(meson.current_build_dir())], + env: ['builddir=@0@'.format(meson.project_build_root())], protocol: 'tap', - timeout: 600) + timeout: 600, + depends: fetch_test_fonts) endif endif diff --git a/test/test_issue431.py b/test/test_issue431.py index 55ca2b4..ba0b336 100644 --- a/test/test_issue431.py +++ b/test/test_issue431.py @@ -12,16 +12,29 @@ from pathlib import Path def test_issue431(tmp_path): - req = requests.get('https://github.com/googlefonts/roboto-flex/releases/download/3.100/roboto-flex-fonts.zip', - allow_redirects=True) - with open(tmp_path / 'roboto-flex-fonts.zip', 'wb') as f: - f.write(req.content) - shutil.unpack_archive(tmp_path / 'roboto-flex-fonts.zip', tmp_path) - builddir = Path(os.environ.get('builddir', Path(__file__).parent)).parent - result = subprocess.run([builddir / 'fc-query' / 'fc-query', '-f', '%{family[0]}:%{index}:%{style[0]}:%{postscriptname}\n', tmp_path / 'roboto-flex-fonts/fonts/variable/RobotoFlex[GRAD,XOPQ,XTRA,YOPQ,YTAS,YTDE,YTFI,YTLC,YTUC,opsz,slnt,wdth,wght].ttf'], stdout=subprocess.PIPE) + builddir = Path(os.environ.get("builddir", Path(__file__).parent.parent)) + roboto_flex_font = ( + builddir + / "testfonts" + / "roboto-flex-fonts/fonts/variable/RobotoFlex[GRAD,XOPQ,XTRA,YOPQ,YTAS,YTDE,YTFI,YTLC,YTUC,opsz,slnt,wdth,wght].ttf" + ) - for line in result.stdout.decode('utf-8').splitlines(): - family, index, style, psname = line.split(':') - normstyle = re.sub('[\x04\\(\\)/<>\\[\\]{}\t\f\r\n ]', '', style) - assert psname.split('-')[-1] == normstyle, \ - f'postscriptname `{psname}\' does not contain style name `{normstyle}\': index {index}' + if not roboto_flex_font.exists(): + pytest.skip(f"Font file not found: {roboto_flex_font}") + + result = subprocess.run( + [ + builddir / "fc-query" / "fc-query", + "-f", + "%{family[0]}:%{index}:%{style[0]}:%{postscriptname}\n", + roboto_flex_font, + ], + stdout=subprocess.PIPE, + ) + + for line in result.stdout.decode("utf-8").splitlines(): + family, index, style, psname = line.split(":") + normstyle = re.sub("[\x04\\(\\)/<>\\[\\]{}\t\f\r\n ]", "", style) + assert ( + psname.split("-")[-1] == normstyle + ), f"postscriptname `{psname}' does not contain style name `{normstyle}': index {index}" commit 00c57ca34ac1c532a0568c7584558185ddeb866e Author: Dominik Röttsches <drott@xxxxxxxxxxxx> Date: Wed Apr 23 15:27:52 2025 +0300 Add Roboto Flex to font downloading script Preparation for removing custom download from issue 431 testcase. diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8920035..a783d87 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,8 +43,8 @@ variables: # changing these will force rebuilding the associated image # Note: these tags have no meaning and are not tied to a particular # fontconfig version - FEDORA_TAG: '2025-04-23.1-21487182ca34' - FREEBSD_TAG: '2025-04-23.1-1c3182609f3d' + FEDORA_TAG: '2025-04-23.1-36c045e261d1' + FREEBSD_TAG: '2025-04-23.1-61a4c35ef3b2' FEDORA_EXEC: 'bash .gitlab-ci/fedora-install.sh' FREEBSD_EXEC: 'bash .gitlab-ci/freebsd-install.sh' diff --git a/build-aux/fetch-testfonts.py b/build-aux/fetch-testfonts.py index 0bec884..85f11a1 100755 --- a/build-aux/fetch-testfonts.py +++ b/build-aux/fetch-testfonts.py @@ -30,6 +30,7 @@ SOURCE_FILES = ( "ko-nanumfonts-3.20.tar.bz2", ) SOURCES = [URL_TEMPLATE % source for source in SOURCE_FILES] +SOURCES += ["https://github.com/googlefonts/roboto-flex/releases/download/3.100/roboto-flex-fonts.zip"] CONTAINER_DOWNLOAD_DIR = "/testfonts" @@ -41,6 +42,7 @@ notofonts-20250214.tar.xz cb0f9edc030e07eca58b799c607d000dfde588911d8e228fc robotofonts-2.132.tar.bz2 087c6f0708fe71f71a056f70fdbd5a85f4d2ce916670a98bd4be10b168abe16a lohitfonts-cros-2.5.5.tar.bz2 ce0ce2a5098c8ffc52327cc030576df7f5328ad9fd8a3289e2476990ad133ff1 ko-nanumfonts-3.20.tar.bz2 59f9b6d7fcf63ca2bea7156ad66c784a1f0601d47be1b11237e17733d7112832 +roboto-flex-fonts.zip 02e0f5db84e69f254958434269d83aa6057b06f3c2a21042bb81b1afe1a0c8c6 """ STAMP_FILE = ".stamp" @@ -74,6 +76,8 @@ def extract_archive(filepath, target_dir): logger.info(f"Extracting {filepath} to {target_dir}") if filename.endswith((".tar.bz2", ".tar.gz", ".tar.xz")): subprocess.run(["tar", "xf", filepath, "-C", target_dir], check=True) + elif filename.endswith(".zip"): + shutil.unpack_archive(filepath, target_dir) else: raise ValueError(f"Unsupported archive type: {filename}") commit 5f00c2e653690489428ff1b26d43c09551f7adb2 Author: Dominik Röttsches <drott@xxxxxxxxxxxx> Date: Wed Apr 23 15:26:55 2025 +0300 Add Pytest status to Meson Summary diff --git a/meson.build b/meson.build index cce3585..344682e 100644 --- a/meson.build +++ b/meson.build @@ -604,6 +604,7 @@ summary({ 'Documentation': (doc_targets.length() > 0 ? doc_targets : false), 'NLS': not opt_nls.disabled(), 'Tests': not get_option('tests').disabled(), + 'Pytest': pytest.found(), 'Tools': not get_option('tools').disabled(), 'iconv': found_iconv == 1, 'XML backend': xmltype, commit 1597b9bafd732f4f7ed8ca6d3eb0b2b57c9c94f7 Author: Dominik Röttsches <drott@xxxxxxxxxxxx> Date: Wed Apr 16 13:23:28 2025 +0300 [Fontations] Container and local download of testfiles * Add a python script that downloads a set of Chrome OS test files. Intended as preparation for testing Fontations indexing against FreeType indexing based on the set of fonts in ChromeOS. * Define a custom target in Meson that ensure the test files are available in the build directory. In containers: symlink them from the directory in the container. Locally: Download them into the build/testfonts. Skip the test if a stamp file with matching hashes is present. * Depend on the presence of the test fonts for the testing target executables. diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4fa6385..8920035 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,8 +43,8 @@ variables: # changing these will force rebuilding the associated image # Note: these tags have no meaning and are not tied to a particular # fontconfig version - FEDORA_TAG: '2025-04-23.1-dd4daa0bc582' - FREEBSD_TAG: '2025-04-23.1-e06fb6df1f3e' + FEDORA_TAG: '2025-04-23.1-21487182ca34' + FREEBSD_TAG: '2025-04-23.1-1c3182609f3d' FEDORA_EXEC: 'bash .gitlab-ci/fedora-install.sh' FREEBSD_EXEC: 'bash .gitlab-ci/freebsd-install.sh' diff --git a/.gitlab-ci/ci.template b/.gitlab-ci/ci.template index 0a6397d..6f97c72 100644 --- a/.gitlab-ci/ci.template +++ b/.gitlab-ci/ci.template @@ -46,7 +46,8 @@ variables: {% for distro in distributions|unique(attribute="name") %} {{"%-15s"| format(distro.name.upper() + '_TAG:')}}'{{distro.tag}}-{{ (ci_fairy.hashfiles('.gitlab-ci/config.yml', - '.gitlab-ci/' + distro.name + '-install.sh'))[0:12] + '.gitlab-ci/' + distro.name + '-install.sh', + 'build-aux/fetch-testfonts.py'))[0:12] }}' {% endfor %} diff --git a/.gitlab-ci/fedora-install.sh b/.gitlab-ci/fedora-install.sh index 710b02e..d8ecddd 100644 --- a/.gitlab-ci/fedora-install.sh +++ b/.gitlab-ci/fedora-install.sh @@ -27,3 +27,5 @@ unzip android-ndk-r28-linux.zip ln -sf android-ndk-r28 ndk rm android-ndk-r28-linux.zip popd + +python3 build-aux/fetch-testfonts.py --target-dir /testfonts \ No newline at end of file diff --git a/.gitlab-ci/freebsd-install.sh b/.gitlab-ci/freebsd-install.sh index 4a21923..5ae6e23 100644 --- a/.gitlab-ci/freebsd-install.sh +++ b/.gitlab-ci/freebsd-install.sh @@ -1,3 +1,4 @@ #! /bin/bash set -ex + diff --git a/build-aux/fetch-testfonts.py b/build-aux/fetch-testfonts.py new file mode 100755 index 0000000..0bec884 --- /dev/null +++ b/build-aux/fetch-testfonts.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 + +import argparse +import os +import shutil +import subprocess +import sys +import tempfile +from urllib.request import urlretrieve +import multiprocessing + +import logging +import sys + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger() + +if not sys.stdout.isatty(): + logger.handlers = [] + +URL_TEMPLATE = ( + "https://commondatastorage.googleapis.com/chromeos-localmirror/distfiles/%s" +) +SOURCE_FILES = ( + "noto-cjk-20210501.tar.bz2", + "crosextrafonts-20130214.tar.gz", + "notofonts-20250214.tar.xz", + "robotofonts-2.132.tar.bz2", + "lohitfonts-cros-2.5.5.tar.bz2", + "ko-nanumfonts-3.20.tar.bz2", +) +SOURCES = [URL_TEMPLATE % source for source in SOURCE_FILES] + +CONTAINER_DOWNLOAD_DIR = "/testfonts" + +# If you want to update these, copy the contents of the .stamp file from the target directory here. +EXPECTED_HASHES = """ +noto-cjk-20210501.tar.bz2 5c70f246c392bf15aeaee22b2fba26bb0ac54e47c00a4b9c3b556bd06fe89281 +crosextrafonts-20130214.tar.gz c48d1c2fd613c9c06c959c34da7b8388059e2408d2bb19845dc3ed35f76e4d09 +notofonts-20250214.tar.xz cb0f9edc030e07eca58b799c607d000dfde588911d8e228fc82d5f61b9e5b6b3 +robotofonts-2.132.tar.bz2 087c6f0708fe71f71a056f70fdbd5a85f4d2ce916670a98bd4be10b168abe16a +lohitfonts-cros-2.5.5.tar.bz2 ce0ce2a5098c8ffc52327cc030576df7f5328ad9fd8a3289e2476990ad133ff1 +ko-nanumfonts-3.20.tar.bz2 59f9b6d7fcf63ca2bea7156ad66c784a1f0601d47be1b11237e17733d7112832 +""" + +STAMP_FILE = ".stamp" + + +def download_file(url, tmp_dir, target_dir): + filename = os.path.basename(url) + filepath = os.path.join(tmp_dir, filename) + logger.info(f"Downloading {url} to {filepath}") + urlretrieve(url, filepath) + return filepath + + +def compute_sha256(filepath): + """Computes the SHA-256 hash of a file.""" + import hashlib + + hasher = hashlib.sha256() + with open(filepath, "rb") as file: + while True: + chunk = file.read(4096) + if not chunk: + break + hasher.update(chunk) + return hasher.hexdigest() + + +def extract_archive(filepath, target_dir): + """Extracts an archive to the target directory.""" + filename = os.path.basename(filepath) + logger.info(f"Extracting {filepath} to {target_dir}") + if filename.endswith((".tar.bz2", ".tar.gz", ".tar.xz")): + subprocess.run(["tar", "xf", filepath, "-C", target_dir], check=True) + else: + raise ValueError(f"Unsupported archive type: {filename}") + + +def download_and_extract(url, tmp_dir, target_dir): + archivepath = download_file(url, tmp_dir, target_dir) + archivehash = compute_sha256(archivepath) + extract_archive(archivepath, target_dir) + return os.path.basename(archivepath), archivehash + + +def stamp_target_dir(target_dir, downloaded_files_hashes): + if not downloaded_files_hashes or len(downloaded_files_hashes) == 0: + logger.error("No files to stamp") + return + + stamp_path = os.path.join(target_dir, STAMP_FILE) + if os.path.exists(stamp_path): + os.remove(stamp_path) + + max_filename_length = ( + max(len(filename) for filename, _ in downloaded_files_hashes) + if downloaded_files_hashes + else 0 + ) + + hash_results = "\n".join( + f"{filename:<{max_filename_length}} {hash}" + for filename, hash in downloaded_files_hashes + ) + with open(stamp_path, "w") as f: + f.write(hash_results) + + if not stamp_hashes_match(stamp_path): + raise ValueError("Downloaded files do not match recorded hashes!") + + +def stamp_hashes_match(stamp_path): + if not os.path.exists(stamp_path): + return False + + lines = [] + with open(stamp_path, "r") as f: + lines = f.readlines() + + expected_hashes = {} + for line in EXPECTED_HASHES.strip().split("\n"): + filename, hash = line.split() + expected_hashes[filename] = hash + + for line in lines: + filename, hash = line.split() + if filename not in expected_hashes: + logger.error(f"Unexpected file in stamp: {filename}") + return False + if expected_hashes[filename] != hash: + logger.error( + f"Hash mismatch for {filename}: expected {expected_hashes[filename]}, got {hash}" + ) + return False + return True + + +def main(): + """Main function to parse arguments and download/extract fonts.""" + + parser = argparse.ArgumentParser(description="Download and extract ChromeOS fonts.") + + parser.add_argument("--target-dir", help="Target directory for extracted fonts.") + parser.add_argument( + "--try-symlink", + action="store_true", + help="Try to symlink test files previously downloaded to the container.", + ) + args = parser.parse_args() + + target_dir = args.target_dir + + if args.try_symlink: + if os.path.exists(CONTAINER_DOWNLOAD_DIR) and stamp_hashes_match( + os.path.join(CONTAINER_DOWNLOAD_DIR, STAMP_FILE) + ): + try: + os.symlink(CONTAINER_DOWNLOAD_DIR, target_dir, target_is_directory=True) + except FileExistsError: + logger.debug( + f"Target directory {target_dir} already exists. Skipping symlink." + ) + except OSError as e: + logger.warning(f"Failed to create symlink: {e}") + logger.info( + f"Symlinked test fonts directory from {CONTAINER_DOWNLOAD_DIR} to {target_dir}." + ) + else: + logger.debug("Pre-downloaded test fonts not found.") + + if stamp_hashes_match(os.path.join(target_dir, STAMP_FILE)): + logger.info("Fonts already downloaded and extracted.") + return 0 + + os.makedirs(target_dir, exist_ok=True) + + with tempfile.TemporaryDirectory() as tmp_dir: + with multiprocessing.Pool(processes=multiprocessing.cpu_count()) as pool: + hashes = pool.starmap( + download_and_extract, + [(source, tmp_dir, target_dir) for source in SOURCES], + ) + stamp_target_dir(target_dir, hashes) + + +if __name__ == "__main__": + main() diff --git a/test/meson.build b/test/meson.build index a7b2637..da5ac1f 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,3 +1,17 @@ +fetch_test_fonts = custom_target( + 'fetch_test_fonts', + output: 'testfonts', + command: [ + python3, + join_paths(meson.project_source_root(), 'build-aux', 'fetch-testfonts.py'), + '--target-dir', + '@BUILD_ROOT@/testfonts', + '--try-symlink', + ], + build_by_default: false, + build_always_stale: true, +) + tests = [ ['test-bz89617.c', {'c_args': ['-DSRCDIR="@0@"'.format(meson.current_source_dir())]}], ['test-bz131804.c'], @@ -59,7 +73,7 @@ endforeach if get_option('fontations').enabled() rust = import('rust') - rust.test('fc_fontations_rust_tests', fc_fontations, link_with: [fc_fontations, libfontconfig_internal]) + rust.test('fc_fontations_rust_tests', fc_fontations, link_with: [fc_fontations, libfontconfig_internal], depends: fetch_test_fonts) endif fs = import('fs')