From dc3fcdae9e2f181de7a06daf2c52907f0026e64c Mon Sep 17 00:00:00 2001 From: subframe7536 <1667077010@qq.com> Date: Tue, 3 Dec 2024 18:02:41 +0800 Subject: [PATCH] release and custom build in CI --- .github/workflows/release.yml | 70 +++++++++++++++++++++++++++++ .gitignore | 5 ++- build.py | 43 +++++++++++++----- config.json | 2 +- release.py | 70 +++++++++++++++++++++++++++++ source/preset-normal.json | 2 +- source/py/utils.py | 84 ++++++++++++++++++++++++++--------- 7 files changed, 241 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 release.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..27a0f90 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,70 @@ +name: Release Font + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + options: + description: 'Options for build.py' + required: false + default: '' + cn: + description: 'Include Chinese version' + required: false + default: 'false' +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Checkout branch + run: git checkout variable + + - name: Install ftcli + run: python -m pip install foundrytools-cli + + - name: Run release script + run: | + if [ "${{ github..event_name }}" == "push" ]; then + python release.py + else + options="${{ github.event.inputs.options }}" + if [ "${{ github.event.inputs.cn }}" == "true" ]; then + options="$options --cn" + fi + python build.py $options + fi + + - name: Generate release notes + id: release_notes + run: | + if [ "${{ github.event_name }}" == "push" ]; then + echo "notes=$(git log --pretty=%B $(git describe --tags --abbrev=0)..HEAD)" >> $GITHUB_ENV + else + notes=$(python release.py ${{ github.event.inputs.options }} --dry) + echo "notes=### Configuration:\n\n```\n$notes\n```" >> $GITHUB_ENV + + - name: Generate tag name + id: generate_timestamp + run: echo "::set-output name=tag_name::v$(date +%s)" + + - name: Create release + id: create_release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name || steps.generate_timestamp.outputs.tag_name }} + draft: ${{ github.event_name == 'push' }} + body: ${{ env.notes }} + token: ${{ secrets.GITHUB_TOKEN }} + file: | + release/* diff --git a/.gitignore b/.gitignore index c0affdc..f336819 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ dist /FontPatcher /fonts /source/cn/static -__pycache__ \ No newline at end of file +__pycache__ +.DS_store +release +source/cn \ No newline at end of file diff --git a/build.py b/build.py index c3dfeec..a782045 100644 --- a/build.py +++ b/build.py @@ -13,7 +13,9 @@ from fontTools.ttLib import TTFont, newTable from fontTools.merge import Merger from source.py.utils import ( check_font_patcher, + download_cn_static_fonts, get_font_forge_bin, + is_ci, run, set_font_name, joinPaths, @@ -341,7 +343,8 @@ class BuildOption: self.output_dir, "TTF-AutoHint" if config.use_hinted else "TTF" ) - self.cn_static_path = f"{self.src_dir}/cn/static" + self.cn_variable_dir = f"{self.src_dir}/cn" + self.cn_static_dir = f"{self.cn_variable_dir}/static" self.cn_base_font_dir = None self.cn_suffix = None @@ -373,6 +376,22 @@ class BuildOption: self.cn_suffix = self.cn_suffix_compact = "CN" self.output_cn = joinPaths(self.output_dir, self.cn_suffix_compact) + def should_build_cn(self, config: FontConfig) -> bool: + if not config.cn["enable"] and not config.use_cn_both: + return False + if path.exists(self.cn_static_dir) and listdir(self.cn_static_dir).__len__() == 16: + return True + if not path.exists(self.cn_variable_dir) and listdir(self.cn_variable_dir).__len__() < 1: + if is_ci(): + return download_cn_static_fonts( + tag="cn_static", + target_dir=self.cn_static_dir, + github_mirror=self.nerd_font["github_mirror"], + ) + print("CN varaible fonts does not exist. Skip CN build.") + return False + return True + def has_cache(self) -> bool: return ( self.__check_cache_dir(self.output_variable, count=2) @@ -660,7 +679,7 @@ def build_cn(f: str, font_config: FontConfig, build_option: BuildOption): [ joinPaths(build_option.cn_base_font_dir, f), joinPaths( - build_option.cn_static_path, f"MapleMonoCN-{style_compact_cn}.ttf" + build_option.cn_static_dir, f"MapleMonoCN-{style_compact_cn}.ttf" ), ] ) @@ -743,6 +762,7 @@ def run_build(pool_size: int, fn: Callable, dir: str): for f in listdir(dir): fn(f) + def main(): check_ftcli() parsed_args = parse_args() @@ -847,20 +867,17 @@ def main(): # ==================================== build CN ======================================= # ========================================================================================= - if font_config.cn["enable"] and path.exists(f"{build_option.src_dir}/cn"): - if ( - not path.exists(build_option.cn_static_path) - or font_config.cn["clean_cache"] - ): + if build_option.should_build_cn(font_config): + if not path.exists(build_option.cn_static_dir) or font_config.cn["clean_cache"]: print("=========================================") print("Instantiating CN Base font, be patient...") print("=========================================") run( - f"ftcli converter vf2i {build_option.src_dir}/cn -out {build_option.cn_static_path}" + f"ftcli converter vf2i {build_option.cn_variable_dir} -out {build_option.cn_static_dir}" ) - run(f"ftcli ttf fix-contours {build_option.cn_static_path}") - run(f"ftcli ttf remove-overlaps {build_option.cn_static_path}") - run(f"ftcli utils del-table -t kern -t GPOS {build_option.cn_static_path}") + run(f"ftcli ttf fix-contours {build_option.cn_static_dir}") + run(f"ftcli ttf remove-overlaps {build_option.cn_static_dir}") + run(f"ftcli utils del-table -t kern -t GPOS {build_option.cn_static_dir}") def _build_cn(): print( @@ -969,7 +986,9 @@ def main(): target_parent_dir_path=archieve_dir, ) with open( - joinPaths(archieve_dir, f"{font_config.family_name_compact}-{f}.sha256"), + joinPaths( + archieve_dir, f"{font_config.family_name_compact}-{f}.sha256" + ), "w", encoding="utf-8", ) as hash_file: diff --git a/config.json b/config.json index 3e7fc65..acae05f 100644 --- a/config.json +++ b/config.json @@ -38,7 +38,7 @@ "extra_args": [] }, "cn": { - "enable": true, + "enable": false, "with_nerd_font": true, "fix_meta_table": true, "clean_cache": false, diff --git a/release.py b/release.py new file mode 100644 index 0000000..26479c0 --- /dev/null +++ b/release.py @@ -0,0 +1,70 @@ +from os import listdir, mkdir, path +from shutil import copytree, move, rmtree +import subprocess + +from source.py.utils import joinPaths + +output_base = "fonts" +output_release = "release" + + +def move_and_log(file_path: str, target_path: str): + print(f"Move {file_path} -> {target_path}") + move(file_path, target_path) + +def build(normal: bool, hinted: bool, cache: bool = False): + args = [ + "python", + "build.py", + "--archieve", + "--cn-both", + ] + + if cache: + args.append("--cache") + + if normal: + args.append("--normal") + + if hinted: + args.append("--hinted") + else: + args.append("--no-hinted") + + print(" ".join(args)) + subprocess.run(args) + + build_archieve_dir = f"{output_base}/archieve" + + for file_name in listdir(build_archieve_dir): + file_path = joinPaths(build_archieve_dir, file_name) + if path.isfile(file_path): + if not hinted: + name, ext = path.splitext(file_name) + file_name = f"{name}-unhinted{ext}" + + move_and_log(file_path, joinPaths(output_release, file_name)) + + +# clear old releases +rmtree(output_base, ignore_errors=True) +mkdir(output_base) +rmtree(output_release, ignore_errors=True) +mkdir(output_release) + +# build all formats +build(normal=True, hinted=True) +build(normal=True, hinted=False, cache=True) +build(normal=False, hinted=True) +build(normal=False, hinted=False, cache=True) + +# copy woff2 to root +rmtree("woff2", ignore_errors=True) +copytree(f"{output_base}/woff2", "woff2") +print("Copy woff2 to root") + +subprocess.run(f"ftcli converter ft2wf -out woff2/var -f woff2 {output_base}/variable") + +target_dir = "website/public-dev/fonts" +rmtree(target_dir, ignore_errors=True) +copytree("woff2/var", target_dir) diff --git a/source/preset-normal.json b/source/preset-normal.json index ccacd84..390344b 100644 --- a/source/preset-normal.json +++ b/source/preset-normal.json @@ -38,7 +38,7 @@ "extra_args": [] }, "cn": { - "enable": true, + "enable": false, "with_nerd_font": true, "fix_meta_table": true, "clean_cache": false, diff --git a/source/py/utils.py b/source/py/utils.py index 4f16fb1..3f727c8 100644 --- a/source/py/utils.py +++ b/source/py/utils.py @@ -50,9 +50,58 @@ def get_font_forge_bin(): return LINUX_FONTFORGE_PATH +def is_ci(): + ci_envs = [ + "JENKINS_HOME", + "TRAVIS", + "CIRCLECI", + "GITHUB_ACTIONS", + "GITLAB_CI", + "TF_BUILD", + ] + + for env in ci_envs: + if environ.get(env): + return True + + return False + + +def parse_github_mirror(github_mirror: str) -> str: + github = environ.get("GITHUB") # custom github mirror, for CN users + if not github: + github = github_mirror + return f"https://{github}" + + +def download_zip_and_extract( + name: str, url: str, zip_path: str, output_dir: str, remove_zip: bool = True +) -> bool: + try: + if not path.exists(zip_path): + try: + print(f"NerdFont Patcher does not exist, download from {url}") + with urlopen(url) as response, open(zip_path, "wb") as out_file: + shutil.copyfileobj(response, out_file) + except Exception as e: + print( + f"\nFail to download {name}. Please download it manually from {url}, then put downloaded file into project's root and run this script again. \n Error: {e}" + ) + return False + with ZipFile(zip_path, "r") as zip_ref: + zip_ref.extractall(output_dir) + if remove_zip: + remove(zip_path) + return True + except Exception as e: + print(f"Download zip and extract failed, error: {e}") + return False + + def check_font_patcher(version: str, github_mirror: str = "github.com") -> bool: - if path.exists("FontPatcher"): - with open("FontPatcher/font-patcher", "r", encoding="utf-8") as f: + target_dir = "FontPatcher" + if path.exists(target_dir): + with open(f"{target_dir}/font-patcher", "r", encoding="utf-8") as f: if f"# Nerd Fonts Version: {version}" in f.read(): return True else: @@ -60,22 +109,17 @@ def check_font_patcher(version: str, github_mirror: str = "github.com") -> bool: shutil.rmtree("FontPatcher", ignore_errors=True) zip_path = "FontPatcher.zip" - if not path.exists(zip_path): - github = environ.get("GITHUB") # custom github mirror, for CN users - if not github: - github = github_mirror - url = f"https://{github}/ryanoasis/nerd-fonts/releases/download/v{version}/FontPatcher.zip" - try: - print(f"NerdFont Patcher does not exist, download from {url}") - with urlopen(url) as response, open(zip_path, "wb") as out_file: - shutil.copyfileobj(response, out_file) - except Exception as e: - print( - f"\nFail to download NerdFont Patcher. Please download it manually from {url}, then put downloaded 'FontPatcher.zip' into project's root and run this script again. \n Error: {e}" - ) - exit(1) + url = f"{parse_github_mirror(github_mirror)}/ryanoasis/nerd-fonts/releases/download/v{version}/{zip_path}" + return download_zip_and_extract( + name="Nerd Font Patcher", url=url, zip_path=zip_path, output_dir=target_dir + ) - with ZipFile(zip_path, "r") as zip_ref: - zip_ref.extractall("FontPatcher") - remove(zip_path) - return True \ No newline at end of file + +def download_cn_static_fonts( + tag: str, target_dir: str, github_mirror: str = "github.com" +) -> bool: + url = f"{parse_github_mirror(github_mirror)}/subframe7536/maple-font/releases/download/{tag}/cn-base.zip" + zip_path = "cn-base-static.zip" + return download_zip_and_extract( + name="Nerd Font Patcher", url=url, zip_path=zip_path, output_dir=target_dir + )