Make release CI works (#291)

* fix ci

* commit updated woff2 var

* fix `--normal --no-liga`

* reduce logs

* split custom and release
This commit is contained in:
subframe7536 2024-12-07 23:32:53 +08:00 committed by GitHub
parent 84f52b0ca2
commit 0986e4aa50
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 228 additions and 192 deletions

9
.github/release_template.md vendored Normal file
View file

@ -0,0 +1,9 @@
| Format | Ligature | No-Ligature | Normal-Ligature | Normal-No-Ligature |
| -------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Variable | [unhinted](https://<url>/MapleMono-Variable.zip) | [unhinted](https://<url>/MapleMonoNL-Variable.zip) | [unhinted](https://<url>/MapleMonoNormal-Variable.zip) | [unhinted](https://<url>/MapleMonoNormalNL-Variable.zip) |
| TTF | [hinted](https://<url>/MapleMono-TTF-AutoHint.zip) / [unhinted](https://<url>/MapleMono-TTF.zip) | [hinted](https://<url>/MapleMonoNL-TTF-AutoHint.zip) / [unhinted](https://<url>/MapleMonoNL-TTF.zip) | [hinted](https://<url>/MapleMonoNormal-TTF-AutoHint.zip) / [unhinted](https://<url>/MapleMonoNormal-TTF.zip) | [hinted](https://<url>/MapleMonoNormalNL-TTF-AutoHint.zip) / [unhinted](https://<url>/MapleMonoNormalNL-TTF.zip) |
| OTF | [unhinted](https://<url>/MapleMono-OTF.zip) | [unhinted](https://<url>/MapleMonoNL-OTF.zip) | [unhinted](https://<url>/MapleMonoNormal-OTF.zip) | [unhinted](https://<url>/MapleMonoNormalNL-OTF.zip) |
| WOFF2 | [unhinted](https://<url>/MapleMono-Woff2.zip) | [unhinted](https://<url>/MapleMonoNL-Woff2.zip) | [unhinted](https://<url>/MapleMonoNormal-Woff2.zip) | [unhinted](https://<url>/MapleMonoNormalNL-Woff2.zip) |
| NF | [hinted](https://<url>/MapleMono-NF.zip) / [unhinted](https://<url>/MapleMono-NF-unhinted.zip) | [hinted](https://<url>/MapleMonoNL-NF.zip) / [unhinted](https://<url>/MapleMonoNL-NF-unhinted.zip) | [hinted](https://<url>/MapleMonoNormal-NF.zip) / [unhinted](https://<url>/MapleMonoNormal-NF-unhinted.zip) | [hinted](https://<url>/MapleMonoNormalNL-NF.zip) / [unhinted](https://<url>/MapleMonoNormalNL-NF-unhinted.zip) |
| CN | [hinted](https://<url>/MapleMono-CN.zip) / [unhinted](https://<url>/MapleMono-CN-unhinted.zip) | [hinted](https://<url>/MapleMonoNL-CN.zip) / [unhinted](https://<url>/MapleMonoNL-CN-unhinted.zip) | [hinted](https://<url>/MapleMonoNormal-CN.zip) / [unhinted](https://<url>/MapleMonoNormal-CN-unhinted.zip) | [hinted](https://<url>/MapleMonoNormalNL-CN.zip) / [unhinted](https://<url>/MapleMonoNormalNL-CN-unhinted.zip) |
| NF-CN | [hinted](https://<url>/MapleMono-NF-CN.zip) / [unhinted](https://<url>/MapleMono-NF-CN-unhinted.zip) | [hinted](https://<url>/MapleMonoNL-NF-CN.zip) / [unhinted](https://<url>/MapleMonoNL-NF-CN-unhinted.zip) | [hinted](https://<url>/MapleMonoNormal-NF-CN.zip) / [unhinted](https://<url>/MapleMonoNormal-NF-CN-unhinted.zip) | [hinted](https://<url>/MapleMonoNormalNL-NF-CN.zip) / [unhinted](https://<url>/MapleMonoNormalNL-NF-CN-unhinted.zip) |

94
.github/workflows/custom.yml vendored Executable file
View file

@ -0,0 +1,94 @@
name: Custom Build Font
on:
workflow_dispatch:
inputs:
cn:
description: 'Include Chinese version (add "--cn" to build args)'
required: false
default: false
type: boolean
normal:
description: 'Remove opinionated features (add "--normal" to build args)'
required: false
default: false
type: boolean
no_liga:
description: 'Remove ligatures (add "--no-liga" to build args)'
required: false
default: false
type: boolean
no_hinted:
description: 'Build unhinted font (add "--no-hinted" to build args)'
required: false
default: false
type: boolean
feat:
description: 'Enable features, split by "," (add "--feat feat1,feat2" to build args)'
required: false
default: ''
type: string
build_args:
description: 'Other args for build.py'
required: false
default: ''
type: string
permissions:
contents: write
jobs:
custom-build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
cache-dependency-path: './requirements.txt'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run custom script
run: |
build_args="--archive"
if [ "${{ github.event.inputs.cn }}" == "true" ]; then
build_args="$build_args --cn"
fi
if [ "${{ github.event.inputs.normal }}" == "true" ]; then
build_args="$build_args --normal"
fi
if [ "${{ github.event.inputs.no_liga }}" == "true" ]; then
build_args="$build_args --no-liga"
fi
if [ "${{ github.event.inputs.no_hinted }}" == "true" ]; then
build_args="$build_args --no-hinted"
fi
if [ -n "${{ github.event.inputs.feat }}" ]; then
build_args="$build_args --feat ${{ github.event.inputs.feat }}"
fi
if [ -n "${{ github.event.inputs.build_args }}" ]; then
build_args="$build_args ${{ github.event.inputs.build_args }}"
fi
echo "BUILD_ARGS=$build_args" >> $GITHUB_ENV
python build.py $build_args
- name: Create release
run: |
echo "### Build arguments" >> NOTES
echo "\`\`\`" >> NOTES
echo "python build.py $BUILD_ARGS" >> NOTES
echo "\`\`\`" >> NOTES
echo "### Final Configuration" >> NOTES
echo "\`\`\`" >> NOTES
python build.py $BUILD_ARGS --dry >> NOTES
echo "\`\`\`" >> NOTES
gh release create "v$(date +%s)" fonts/archive/*.* --notes-file NOTES
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -1,72 +1,64 @@
name: Release Font
name: Build and Release Fonts in All Formats
on:
push:
tags:
- 'v*'
- v*
workflow_dispatch:
inputs:
options:
description: 'Options for build.py'
required: false
default: ''
type: string
cn:
description: 'Include Chinese version (add `--cn` to options)'
required: false
default: false
type: boolean
permissions:
contents: write
jobs:
release:
build:
strategy:
matrix:
config: [
{name: normal-liga, args: "--normal --liga"},
{name: normal-no-liga, args: "--normal --no-liga"},
{name: liga, args: "--liga"},
{name: no-liga, args: "--no-liga"}
]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
- uses: actions/checkout@v4
- 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
cache: 'pip'
cache-dependency-path: './requirements.txt'
- run: pip install -r requirements.txt
- name: Build fonts
run: |
if [ "${{ github.event_name }}" == "push" ]; then
python release.py
else
options="${{ github.event.inputs.options }}"
if [ ${{ github.event.inputs.cn }} ]; 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
python build.py --archive --cn-both --hinted ${{ matrix.config.args }}
python build.py --archive --cn-both --no-hinted --cache ${{ matrix.config.args }}
- uses: actions/upload-artifact@v4
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/*
name: release-artifacts-${{ matrix.config.name }}
path: fonts/archive/*
create-release:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: release-artifacts*
path: release
- name: Log artifacts tree
run: tree release
- name: Release artifacts
run: |
TAG="${{ github.ref_name }}"
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
TAG="v$(date +%s)"
fi
sed -i "s|https://<url>|https://github.com/subframe7536/maple-font/releases/download/${TAG//\//\\/}|g" release_template.md
gh release create "$TAG" release/**/*.* --notes-file .github/release_template.md --draft
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -67,10 +67,9 @@ The build script will auto download neccessory assets from GitHub. If you have t
### Build Script Options
```
usage: build.py [-h] [-v] [-d] [--debug] [-n] [--feat FEAT] [--hinted]
[--no-hinted] [--liga] [--no-liga] [--cn-narrow]
[--nerd-font | --no-nerd-font] [--cn | --no-cn] [--cn-both]
[--cache] [--archive]
usage: build.py [-h] [-v] [-d] [--debug] [-n] [--feat FEAT] [--hinted] [--no-hinted]
[--liga] [--no-liga] [--cn-narrow] [--nerd-font | --no-nerd-font]
[--cn | --no-cn] [--cn-both] [--ttf-only] [--cache] [--archive]
✨ Builder and optimizer for Maple Mono
@ -95,11 +94,12 @@ Build Options:
--no-nerd-font Do not build Nerd-Font version
--cn Build Chinese version
--no-cn Do not build Chinese version
--cn-both Build both `Maple Mono CN` and `Maple Mono NF CN`. Nerd-Font
version must be enabled
--cn-both Build both `Maple Mono CN` and `Maple Mono NF CN`. Nerd-Font version
must be enabled
--ttf-only Only build unhinted TTF format
--cache Reuse font cache of TTF, OTF and Woff2 formats
--archive Build font archives with config and license. If has `--cache`
flag, only archive Nerd-Font and CN formats
--archive Build font archives with config and license. If has `--cache` flag,
only archive Nerd-Font and CN formats
```
## Credit

42
build.py Normal file → Executable file
View file

@ -405,7 +405,7 @@ class BuildOption:
def should_build_cn(self, config: FontConfig) -> bool:
if not config.cn["enable"] and not config.use_cn_both:
print(
'No `"cn.enable": true` in config.json or no `--cn` / `--cn-both` in argv. Skip CN build.'
'\nNo `"cn.enable": true` in config.json or no `--cn` / `--cn-both` in argv. Skip CN build.'
)
return False
if (
@ -427,7 +427,7 @@ class BuildOption:
target_dir=self.cn_variable_dir,
github_mirror=config.github_mirror,
)
print("CN base fonts don't exist. Skip CN build.")
print("\nCN base fonts don't exist. Skip CN build.")
return False
return True
@ -606,15 +606,19 @@ def build_mono(f: str, font_config: FontConfig, build_option: BuildOption):
if font_config.ttf_only:
return
print(f"Auto hint {postscript_name}.ttf")
run(f"ftcli ttf autohint {_path} -out {build_option.output_ttf_hinted}")
print(f"Convert {postscript_name}.ttf to WOFF2")
run(f"ftcli converter ft2wf {_path} -out {build_option.output_woff2} -f woff2")
_otf_path = joinPaths(
build_option.output_otf, path.basename(_path).replace(".ttf", ".otf")
)
print(f"Convert {postscript_name}.ttf to OTF")
run(f"ftcli converter ttf2otf --silent {_path} -out {build_option.output_otf}")
if not font_config.debug:
run(f"ftcli otf check-outlines --quiet-mode {_otf_path}")
print(f"Optimize {postscript_name}.otf")
run(f"ftcli otf fix-contours --silent {_otf_path}")
run(f"ftcli otf fix-version {_otf_path}")
@ -650,7 +654,7 @@ def build_nf_by_font_patcher(
_nf_args += font_config.nerd_font["extra_args"]
run(_nf_args + [joinPaths(build_option.ttf_base_dir, font_basename)])
run(_nf_args + [joinPaths(build_option.ttf_base_dir, font_basename)], log=True)
nf_file_name = "NerdFont"
if font_config.nerd_font["mono"]:
nf_file_name += "Mono"
@ -717,7 +721,7 @@ def build_nf(
def build_cn(f: str, font_config: FontConfig, build_option: BuildOption):
style_compact_cn = f.split("-")[-1].split(".")[0]
print(f"👉 CN version for {f}")
print(f"👉 {build_option.cn_suffix_compact} version for {f}")
merger = Merger()
font = merger.merge(
@ -817,9 +821,10 @@ def main():
build_option.load_cn_dir_and_suffix(font_config.should_cn_use_nerd_font())
if parsed_args.dry:
print("parsed_args:", json.dumps(parsed_args.__dict__, indent=4))
print("font_config:", json.dumps(font_config.__dict__, indent=4))
if not is_ci():
print("build_option:", json.dumps(build_option.__dict__, indent=4))
print("parsed_args:", json.dumps(parsed_args.__dict__, indent=4))
return
should_use_cache = parsed_args.cache
@ -852,14 +857,17 @@ def main():
print("\n✨ Instatiate and optimize fonts...\n")
print("Check and optimize variable fonts")
if not font_config.debug:
run(f"ftcli fix decompose-transformed {build_option.output_variable}")
run(f"ftcli fix italic-angle {build_option.output_variable}")
run(f"ftcli fix monospace {build_option.output_variable}")
print("Instantiate TTF")
run(
f"ftcli converter vf2i {build_option.output_variable} -out {build_option.output_ttf}"
)
print("Fix static TTF")
run(f"ftcli fix italic-angle {build_option.output_ttf}")
run(f"ftcli fix monospace {build_option.output_ttf}")
run(f"ftcli fix strip-names {build_option.output_ttf}")
@ -921,11 +929,12 @@ def main():
print("Instantiating CN Base font, be patient...")
print("=========================================")
run(
f"ftcli converter vf2i {build_option.cn_variable_dir} -out {build_option.cn_static_dir}"
f"ftcli converter vf2i {build_option.cn_variable_dir} -out {build_option.cn_static_dir}",
log=True
)
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}")
run(f"ftcli ttf fix-contours {build_option.cn_static_dir}", log=True)
run(f"ftcli ttf remove-overlaps {build_option.cn_static_dir}", log=True)
run(f"ftcli utils del-table -t kern -t GPOS {build_option.cn_static_dir}", log=True)
def _build_cn():
print(
@ -937,6 +946,7 @@ def main():
run_build(font_config.pool_size, fn, build_option.cn_base_font_dir)
if font_config.cn["use_hinted"]:
print("Auto hint all glyphs")
run(f"ftcli ttf autohint {build_option.output_cn}")
drop_mac_names(build_option.cn_base_font_dir)
@ -977,15 +987,17 @@ def main():
def compress_folder(
source_file_or_dir_path: str, target_parent_dir_path: str
) -> str:
) -> tuple[str, str]:
"""
compress folder and return sha1
"""
source_folder_name = path.basename(source_file_or_dir_path)
zip_file_name_without_ext = f"{font_config.family_name_compact}-{source_folder_name}{'-unhinted' if not font_config.use_hinted else ''}"
zip_path = joinPaths(
target_parent_dir_path,
f"{font_config.family_name_compact}-{source_folder_name}.zip",
f"{zip_file_name_without_ext}.zip",
)
with ZipFile(
@ -1013,7 +1025,7 @@ def main():
break
sha256.update(data)
return sha256.hexdigest()
return sha256.hexdigest(), zip_file_name_without_ext
if font_config.archive:
print("\n🚀 archive files...\n")
@ -1031,12 +1043,12 @@ def main():
if should_use_cache and f not in ["CN", "NF", "NF-CN"]:
continue
sha256 = compress_folder(
sha256, zip_file_name_without_ext = compress_folder(
source_file_or_dir_path=joinPaths(build_option.output_dir, f),
target_parent_dir_path=archive_dir,
)
with open(
joinPaths(archive_dir, f"{font_config.family_name_compact}-{f}.sha256"),
joinPaths(archive_dir, f"{zip_file_name_without_ext}.sha256"),
"w",
encoding="utf-8",
) as hash_file:

View file

@ -1,79 +0,0 @@
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, liga: bool, cache: bool = False):
args = [
"python",
"build.py",
"--archive",
"--cn-both",
]
if cache:
args.append("--cache")
if normal:
args.append("--normal")
if hinted:
args.append("--hinted")
else:
args.append("--no-hinted")
if liga:
args.append("--liga")
else:
args.append("--no-liga")
print(" ".join(args))
subprocess.run(args)
build_archive_dir = f"{output_base}/archive"
for file_name in listdir(build_archive_dir):
file_path = joinPaths(build_archive_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, liga=True, hinted=True)
build(normal=True, liga=True, hinted=False, cache=True)
build(normal=True, liga=False, hinted=True)
build(normal=True, liga=False, hinted=False, cache=True)
build(normal=False, liga=True, hinted=True)
build(normal=False, liga=True, hinted=False, cache=True)
build(normal=False, liga=False, hinted=True)
build(normal=False, liga=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)

View file

@ -48,10 +48,10 @@ def freeze_feature(font, calt, moving_rules=[], config={}):
glyph_dict = font["glyf"].glyphs
hmtx_dict = font["hmtx"].metrics
for index in target_feature.LookupListIndex:
lookup = font["GSUB"].table.LookupList.Lookup[index].SubTable[0].mapping
if not lookup:
lookup_subtable = font["GSUB"].table.LookupList.Lookup[index].SubTable[0]
if not lookup_subtable or "mapping" not in lookup_subtable.__dict__:
continue
for old_key, new_key in lookup.items():
for old_key, new_key in lookup_subtable.mapping.items():
if (
old_key in glyph_dict
and old_key in hmtx_dict

View file

@ -7,9 +7,32 @@ from zipfile import ZipFile
from fontTools.ttLib import TTFont
# run command
def run(cli: str | list[str], extra_args: list[str] = []):
subprocess.run((cli.split(" ") if isinstance(cli, str) else cli) + extra_args)
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 run(command, extra_args=None, log=not is_ci()):
"""
Run a command line interface (CLI) command.
"""
if extra_args is None:
extra_args = []
if isinstance(command, str):
command = command.split()
subprocess.run(command + extra_args, stdout=subprocess.DEVNULL if not log else None)
def set_font_name(font: TTFont, name: str, id: int):
@ -42,7 +65,7 @@ def get_font_forge_bin():
system_name = platform.uname()[0]
result = ''
result = ""
if "Darwin" in system_name:
result = MAC_FONTFORGE_PATH
elif "Windows" in system_name:
@ -56,23 +79,6 @@ def get_font_forge_bin():
return result
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:
@ -100,6 +106,8 @@ def download_zip_and_extract(
break
out_file.write(buffer)
if not is_ci():
downloaded_size += len(buffer)
percent_downloaded = (downloaded_size / total_size) * 100