Skip to content

Commit dfb121f

Browse files
[ci] build only required modules (keiyoushi#6900)
* workflow * configs * workflow * workflow * workflow * workflow * workflow * workflow * workflow * workflow * workflow * workflow * rewrite python scripts * small tweak * Update .github/scripts/modules-to-build.py Co-authored-by: AwkwardPeak7 <[email protected]> * set full path of git * now? * i hate my life * and should work now * And some more changes * And some more changes 2 * Oops * support for always building some modules * string replace fix * don't run matrix chunk if empty * bruh moment * revert * test pr workflow - remove extension - modify extension - modify multisrc * fix gradlew command * Revert "test pr workflow" This reverts commit 8c3aa34. * skip step * skip step x2 * perhaps * make if work? * Reapply "test pr workflow" This reverts commit 9c7f2a7. * make if work! * Revert "Reapply "test pr workflow"" This reverts commit 43238b0. * update job name * Revert "update job name" This reverts commit 361b6e7. * final changes * Cleanup some common code * Cleanup settings.gradle.kts * Reapply "Reapply "test pr workflow"" This reverts commit 68524b6. * Revert "Reapply "Reapply "test pr workflow""" This reverts commit bafab8b. * correct delete output name * pr test: multiple matrices * Revert "pr test: multiple matrices" This reverts commit 87fd5fc. --------- Co-authored-by: FourTOne5 <[email protected]>
1 parent 4e7aef3 commit dfb121f

14 files changed

+283
-158
lines changed

.github/always_build.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[
2+
"ko.newtoki",
3+
"ko.wolfdotcom"
4+
]

.github/scripts/commit-repo.sh

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#!/bin/bash
22
set -e
33

4-
rsync -a --delete --exclude .git --exclude .gitignore --exclude README.md --exclude repo.json ../main/repo/ .
54
git config --global user.email "[email protected]"
65
git config --global user.name "keiyoushi-bot"
76
git status

.github/scripts/create-repo.py

+5-34
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import html
21
import json
32
import os
43
import re
@@ -11,10 +10,8 @@
1110
VERSION_NAME_REGEX = re.compile(r"versionName='([^']+)'")
1211
IS_NSFW_REGEX = re.compile(r"'tachiyomi.extension.nsfw' value='([^']+)'")
1312
APPLICATION_LABEL_REGEX = re.compile(r"^application-label:'([^']+)'", re.MULTILINE)
14-
APPLICATION_ICON_320_REGEX = re.compile(
15-
r"^application-icon-320:'([^']+)'", re.MULTILINE
16-
)
17-
LANGUAGE_REGEX = re.compile(r"tachiyomi-([^\.]+)")
13+
APPLICATION_ICON_320_REGEX = re.compile(r"^application-icon-320:'([^']+)'", re.MULTILINE)
14+
LANGUAGE_REGEX = re.compile(r"tachiyomi-([^.]+)")
1815

1916
*_, ANDROID_BUILD_TOOLS = (Path(os.environ["ANDROID_HOME"]) / "build-tools").iterdir()
2017
REPO_DIR = Path("repo")
@@ -26,7 +23,6 @@
2623
with open("output.json", encoding="utf-8") as f:
2724
inspector_data = json.load(f)
2825

29-
index_data = []
3026
index_min_data = []
3127

3228
for apk in REPO_APK_DIR.iterdir():
@@ -41,7 +37,7 @@
4137
).decode()
4238

4339
package_info = next(x for x in badging.splitlines() if x.startswith("package: "))
44-
package_name = PACKAGE_NAME_REGEX.search(package_info).group(1)
40+
package_name = PACKAGE_NAME_REGEX.search(package_info).group(1)
4541
application_icon = APPLICATION_ICON_320_REGEX.search(badging).group(1)
4642

4743
with ZipFile(apk) as z, z.open(application_icon) as i, (
@@ -87,31 +83,6 @@
8783
)
8884

8985
index_min_data.append(min_data)
90-
index_data.append(
91-
{
92-
**common_data,
93-
"hasReadme": 0,
94-
"hasChangelog": 0,
95-
"sources": sources,
96-
}
97-
)
9886

99-
index_data.sort(key=lambda x: x["pkg"])
100-
index_min_data.sort(key=lambda x: x["pkg"])
101-
102-
with (REPO_DIR / "index.json").open("w", encoding="utf-8") as f:
103-
index_data_str = json.dumps(index_data, ensure_ascii=False, indent=2)
104-
105-
print(index_data_str)
106-
f.write(index_data_str)
107-
108-
with (REPO_DIR / "index.min.json").open("w", encoding="utf-8") as f:
109-
json.dump(index_min_data, f, ensure_ascii=False, separators=(",", ":"))
110-
111-
with (REPO_DIR / "index.html").open("w", encoding="utf-8") as f:
112-
f.write('<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n<title>apks</title>\n</head>\n<body>\n<pre>\n')
113-
for entry in index_data:
114-
apk_escaped = 'apk/' + html.escape(entry["apk"])
115-
name_escaped = html.escape(entry["name"])
116-
f.write(f'<a href="{apk_escaped}">{name_escaped}</a>\n')
117-
f.write('</pre>\n</body>\n</html>\n')
87+
with REPO_DIR.joinpath("index.min.json").open("w", encoding="utf-8") as index_file:
88+
json.dump(index_min_data, index_file, ensure_ascii=False, separators=(",", ":"))
+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import itertools
2+
import json
3+
import os
4+
import re
5+
import subprocess
6+
import sys
7+
from pathlib import Path
8+
from typing import NoReturn
9+
10+
EXTENSION_REGEX = re.compile(r"^src/(?P<lang>\w+)/(?P<extension>\w+)")
11+
MULTISRC_LIB_REGEX = re.compile(r"^lib-multisrc/(?P<multisrc>\w+)")
12+
LIB_REGEX = re.compile(r"^lib/(?P<lib>\w+)")
13+
MODULE_REGEX = re.compile(r"^:src:(?P<lang>\w+):(?P<extension>\w+)$")
14+
15+
def run_command(command: str) -> str:
16+
result = subprocess.run(command, capture_output=True, text=True, shell=True)
17+
if result.returncode != 0:
18+
print(result.stderr.strip())
19+
sys.exit(result.returncode)
20+
return result.stdout.strip()
21+
22+
def get_module_list(ref: str) -> tuple[list[str], list[str]]:
23+
changed_files = run_command(f"git diff --name-only {ref}").splitlines()
24+
25+
modules = set()
26+
libs = set()
27+
deleted = set()
28+
29+
for file in map(lambda x: Path(x).as_posix(), changed_files):
30+
if match := EXTENSION_REGEX.search(file):
31+
lang = match.group("lang")
32+
extension = match.group("extension")
33+
if Path("src", lang, extension).is_dir():
34+
modules.add(f':src:{lang}:{extension}')
35+
deleted.add(f"{lang}.{extension}")
36+
elif match := MULTISRC_LIB_REGEX.search(file):
37+
multisrc = match.group("multisrc")
38+
if Path("lib-multisrc", multisrc).is_dir():
39+
libs.add(f":lib-multisrc:{multisrc}:printDependentExtensions")
40+
elif match := LIB_REGEX.search(file):
41+
lib = match.group("lib")
42+
if Path("lib", lib).is_dir():
43+
libs.add(f":lib:{lib}:printDependentExtensions")
44+
45+
def is_extension_module(module: str) -> bool:
46+
if not (match := MODULE_REGEX.search(module)):
47+
return False
48+
lang = match.group("lang")
49+
extension = match.group("extension")
50+
deleted.add(f"{lang}.{extension}")
51+
return True
52+
53+
modules.update([
54+
module for module in
55+
run_command("./gradlew -q " + " ".join(libs)).splitlines()
56+
if is_extension_module(module)
57+
])
58+
59+
if os.getenv("IS_PR_CHECK") != "true":
60+
with Path.cwd().joinpath(".github/always_build.json").open() as always_build_file:
61+
always_build = json.load(always_build_file)
62+
for extension in always_build:
63+
modules.add(":src:" + extension.replace(".", ":"))
64+
deleted.add(extension)
65+
66+
return list(modules), list(deleted)
67+
68+
def main() -> NoReturn:
69+
_, ref, build_type = sys.argv
70+
modules, deleted = get_module_list(ref)
71+
72+
chunked = {
73+
"chunk": [
74+
{"number": i + 1, "modules": modules}
75+
for i, modules in
76+
enumerate(itertools.batched(
77+
map(lambda x: f"{x}:assemble{build_type}", modules),
78+
int(os.getenv("CI_CHUNK_SIZE", 65))
79+
))
80+
]
81+
}
82+
83+
print(f"Module chunks to build:\n{json.dumps(chunked, indent=2)}\n\nModule to delete:\n{json.dumps(deleted, indent=2)}")
84+
85+
if os.getenv("CI") == "true":
86+
with open(os.getenv("GITHUB_OUTPUT"), 'a') as out_file:
87+
out_file.write(f"matrix={json.dumps(chunked)}\n")
88+
out_file.write(f"delete={json.dumps(deleted)}\n")
89+
90+
if __name__ == '__main__':
91+
main()

.github/scripts/merge-repo.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import html
2+
import sys
3+
import json
4+
from pathlib import Path
5+
import shutil
6+
7+
REMOTE_REPO: Path = Path.cwd()
8+
LOCAL_REPO: Path = REMOTE_REPO.parent.joinpath("main/repo")
9+
10+
to_delete: list[str] = json.loads(sys.argv[1])
11+
12+
for module in to_delete:
13+
apk_name = f"tachiyomi-{module}-v*.*.*.apk"
14+
icon_name = f"eu.kanade.tachiyomi.extension.{module}.png"
15+
for file in REMOTE_REPO.joinpath("apk").glob(apk_name):
16+
file.unlink()
17+
for file in REMOTE_REPO.joinpath("icon").glob(icon_name):
18+
file.unlink()
19+
20+
shutil.copytree(src=LOCAL_REPO.joinpath("apk"), dst=REMOTE_REPO.joinpath("apk"))
21+
shutil.copytree(src=LOCAL_REPO.joinpath("icon"), dst=REMOTE_REPO.joinpath("icon"))
22+
23+
with REMOTE_REPO.joinpath("index.min.json").open() as remote_index_file:
24+
remote_index = json.load(remote_index_file)
25+
26+
with LOCAL_REPO.joinpath("index.min.json").open() as local_index_file:
27+
local_index = json.load(local_index_file)
28+
29+
index = [
30+
item for item in remote_index
31+
if not any([item["pkg"].endswith(f".{module}") for module in to_delete])
32+
]
33+
index.extend(local_index)
34+
index.sort(key=lambda x: x["pkg"])
35+
36+
with REMOTE_REPO.joinpath("index.json").open("w", encoding="utf-8") as index_file:
37+
json.dump(index, index_file, ensure_ascii=False, indent=2)
38+
39+
with REMOTE_REPO.joinpath("index.min.json").open("w", encoding="utf-8") as index_min_file:
40+
json.dump(index, index_min_file, ensure_ascii=False, separators=(",", ":"))
41+
42+
with REMOTE_REPO.joinpath("index.html").open("w", encoding="utf-8") as index_html_file:
43+
index_html_file.write('<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n<title>apks</title>\n</head>\n<body>\n<pre>\n')
44+
for entry in index:
45+
apk_escaped = 'apk/' + html.escape(entry["apk"])
46+
name_escaped = html.escape(entry["name"])
47+
index_html_file.write(f'<a href="{apk_escaped}">{name_escaped}</a>\n')
48+
index_html_file.write('</pre>\n</body>\n</html>\n')

.github/scripts/move-apks.py

-16
This file was deleted.

.github/scripts/move-built-apks.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from pathlib import Path
2+
import shutil
3+
4+
REPO_APK_DIR = Path("repo/apk")
5+
6+
shutil.rmtree(REPO_APK_DIR, ignore_errors=True)
7+
REPO_APK_DIR.mkdir(parents=True, exist_ok=True)
8+
9+
for apk in Path.home().joinpath("apk-artifacts").glob("**/*.apk"):
10+
apk_name = apk.name.replace("-release.apk", ".apk")
11+
12+
shutil.move(apk, REPO_APK_DIR.joinpath(apk_name))
+28-30
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: PR build check
1+
name: PR check
22

33
on:
44
pull_request:
@@ -14,49 +14,48 @@ concurrency:
1414

1515
env:
1616
CI_CHUNK_SIZE: 65
17+
IS_PR_CHECK: true
1718

1819
jobs:
1920
prepare:
2021
name: Prepare job
21-
runs-on: ubuntu-latest
22+
runs-on: 'ubuntu-24.04'
2223
outputs:
23-
individualMatrix: ${{ steps.generate-matrices.outputs.individualMatrix }}
24+
matrix: ${{ steps.generate-matrices.outputs.matrix }}
25+
delete: ${{ steps.generate-matrices.outputs.delete }}
2426
steps:
25-
- name: Clone repo
27+
- name: Checkout PR
2628
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
2729

28-
- name: Get number of modules
29-
run: |
30-
set -x
31-
projects=(src/*/*)
32-
33-
echo "NUM_INDIVIDUAL_MODULES=${#projects[@]}" >> $GITHUB_ENV
34-
35-
- id: generate-matrices
36-
name: Create output matrices
37-
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
30+
- name: Set up Java
31+
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
3832
with:
39-
script: |
40-
const numIndividualModules = process.env.NUM_INDIVIDUAL_MODULES;
41-
const chunkSize = process.env.CI_CHUNK_SIZE;
42-
43-
const numIndividualChunks = Math.ceil(numIndividualModules / chunkSize);
33+
java-version: 17
34+
distribution: temurin
4435

45-
console.log(`Individual modules: ${numIndividualModules} (${numIndividualChunks} chunks of ${chunkSize})`);
36+
- name: Set up Gradle
37+
uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
38+
with:
39+
cache-read-only: true
4640

47-
core.setOutput('individualMatrix', { 'chunk': [...Array(numIndividualChunks).keys()] });
41+
- id: generate-matrices
42+
name: Generate build matrices
43+
run: |
44+
git fetch origin main
45+
python ./.github/scripts/generate-build-matrices.py origin/main Debug
4846
49-
build_individual:
50-
name: Build individual modules
47+
build:
48+
name: Build extensions (${{ matrix.chunk.number }})
5149
needs: prepare
52-
runs-on: ubuntu-latest
50+
runs-on: 'ubuntu-24.04'
51+
if: ${{ toJson(fromJson(needs.prepare.outputs.matrix).chunk) != '[]' }}
5352
strategy:
54-
matrix: ${{ fromJSON(needs.prepare.outputs.individualMatrix) }}
53+
matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }}
5554
steps:
5655
- name: Checkout PR
5756
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
5857

59-
- name: Set up JDK
58+
- name: Set up Java
6059
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
6160
with:
6261
java-version: 17
@@ -67,7 +66,6 @@ jobs:
6766
with:
6867
cache-read-only: true
6968

70-
- name: Build extensions (chunk ${{ matrix.chunk }})
71-
env:
72-
CI_CHUNK_NUM: ${{ matrix.chunk }}
73-
run: ./gradlew -p src assembleDebug
69+
- name: Build extensions (${{ matrix.chunk.number }})
70+
run: |
71+
./gradlew $(echo '${{ toJson(matrix.chunk.modules) }}' | jq -r 'join(" ")')

0 commit comments

Comments
 (0)