GitHub Actions CI 自动打包

This commit is contained in:
RememBerBer
2026-06-07 00:29:54 +08:00
parent 6a97032c83
commit 8fea6286ff
8 changed files with 293 additions and 16 deletions
+63
View File
@@ -298,4 +298,67 @@ jobs:
release-assets/**/*.exe
release-assets/**/*.jar
publish-download-links:
name: Update download_links.json on master
if: >-
${{
always() && (
(startsWith(github.ref, 'refs/tags/v') && needs.release.result == 'success') ||
(github.event_name == 'workflow_dispatch' && inputs.release_tag != '' && needs.release-manual-assets.result == 'success')
)
}}
needs:
- package-mac-apple-silicon
- package-windows-x64
- package-linux-x64
- package-mac-intel-self-hosted
- release
- release-manual-assets
runs-on: ubuntu-latest
steps:
- name: Resolve release tag
id: release
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "tag=${{ inputs.release_tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
fi
echo "merge=true" >> "$GITHUB_OUTPUT"
- name: Checkout master
uses: actions/checkout@v4
with:
ref: master
fetch-depth: 1
- name: Download packaged artifacts
uses: actions/download-artifact@v4
with:
path: release-assets
merge-multiple: true
- name: Generate download_links.json
run: |
ARGS=(
--assets-dir release-assets
--tag "${{ steps.release.outputs.tag }}"
--output download_links.json
)
ARGS+=(--merge-existing)
python -u scripts/generate_download_links.py "${ARGS[@]}"
- name: Commit and push download_links.json
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add download_links.json
if git diff --staged --quiet; then
echo "download_links.json is already up to date"
exit 0
fi
git commit -m "chore: update download_links.json for ${{ steps.release.outputs.tag }}"
git push
+5 -5
View File
@@ -1,6 +1,6 @@
{
"windows": "https://mootool-1258719368.cos.ap-beijing.myqcloud.com/MooTool-1.7.0-windows.exe",
"mac": "https://mootool-1258719368.cos.ap-beijing.myqcloud.com/MooTool_1.7.0.dmg",
"macSilicon": "https://mootool-1258719368.cos.ap-beijing.myqcloud.com/MooTool_1.7.0-AppleSilicon.dmg",
"linux": "https://mootool-1258719368.cos.ap-beijing.myqcloud.com/MooTool_1.7.0.deb"
}
"windows": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-windows.exe",
"mac": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool_1.7.0.dmg",
"macSilicon": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool_1.7.0-AppleSilicon.dmg",
"linux": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool_1.7.0.deb"
}
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.luoboduner.moo.tool</groupId>
<artifactId>MooTool</artifactId>
<version>1.7.0</version>
<version>1.7.1</version>
<packaging>jar</packaging>
<name>MooTool</name>
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env python3
"""Generate download_links.json pointing at GitHub Release assets."""
from __future__ import annotations
import argparse
import json
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable
DEFAULT_REPO = "rememberber/MooTool"
PLATFORM_RULES: dict[str, tuple[str, ...]] = {
"windows": ("windows-x64",),
"macSilicon": ("mac-apple-silicon",),
"mac": ("mac-intel",),
"linux": ("linux-x64",),
}
INSTALLER_SUFFIXES: dict[str, tuple[str, ...]] = {
"windows": (".exe", ".msi", ".zip"),
"macSilicon": (".dmg", ".pkg", ".zip"),
"mac": (".dmg", ".pkg", ".zip"),
"linux": (".deb", ".rpm", ".tar.gz"),
}
@dataclass(frozen=True)
class AssetMatch:
platform_key: str
file_name: str
def normalize_tag(tag: str) -> str:
tag = tag.strip()
if not tag:
raise ValueError("Release tag must not be empty")
return tag if tag.startswith("v") else f"v{tag}"
def version_from_tag(tag: str) -> str:
normalized = normalize_tag(tag)
return normalized[1:] if normalized.startswith("v") else normalized
def release_asset_url(repo: str, tag: str, file_name: str) -> str:
return f"https://github.com/{repo}/releases/download/{normalize_tag(tag)}/{file_name}"
def list_assets(assets_dir: Path) -> list[Path]:
if not assets_dir.exists():
return []
return sorted(path for path in assets_dir.rglob("*") if path.is_file())
def find_installer(assets: Iterable[Path], version: str, target_label: str, suffixes: tuple[str, ...]) -> Path | None:
prefix = f"MooTool-{version}-{target_label}"
for suffix in suffixes:
exact = [asset for asset in assets if asset.name == f"{prefix}{suffix}"]
if exact:
return exact[0]
prefixed = [asset for asset in assets if asset.name.startswith(f"{prefix}-") and asset.name.endswith(suffix)]
if prefixed:
return prefixed[0]
return None
def match_assets(assets_dir: Path, version: str) -> list[AssetMatch]:
assets = list_assets(assets_dir)
matches: list[AssetMatch] = []
for platform_key, target_labels in PLATFORM_RULES.items():
suffixes = INSTALLER_SUFFIXES[platform_key]
for target_label in target_labels:
installer = find_installer(assets, version, target_label, suffixes)
if installer is not None:
matches.append(AssetMatch(platform_key=platform_key, file_name=installer.name))
break
return matches
def build_links(
assets_dir: Path,
tag: str,
repo: str = DEFAULT_REPO,
existing_links: dict[str, str] | None = None,
) -> dict[str, str]:
version = version_from_tag(tag)
links = dict(existing_links or {})
for match in match_assets(assets_dir, version):
links[match.platform_key] = release_asset_url(repo, tag, match.file_name)
return links
def write_links(output_path: Path, links: dict[str, str]) -> None:
ordered_keys = [key for key in PLATFORM_RULES if key in links]
extra_keys = sorted(key for key in links if key not in PLATFORM_RULES)
ordered_links = {key: links[key] for key in ordered_keys + extra_keys}
output_path.write_text(json.dumps(ordered_links, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
def load_existing_links(path: Path | None) -> dict[str, str]:
if path is None or not path.exists():
return {}
data = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(data, dict):
raise ValueError(f"Expected object in {path}")
return {str(key): str(value) for key, value in data.items()}
def main(argv: Iterable[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--assets-dir", required=True, type=Path)
parser.add_argument("--tag", required=True)
parser.add_argument("--repo", default=DEFAULT_REPO)
parser.add_argument("--output", required=True, type=Path)
parser.add_argument("--merge-existing", action="store_true")
args = parser.parse_args(list(argv) if argv is not None else None)
existing_links = load_existing_links(args.output) if args.merge_existing else {}
links = build_links(args.assets_dir.resolve(), args.tag, args.repo, existing_links)
if not links:
raise SystemExit("No release assets matched the expected CI naming convention")
write_links(args.output.resolve(), links)
for key, url in links.items():
print(f"{key}: {url}")
return 0
if __name__ == "__main__":
sys.exit(main())
+67
View File
@@ -0,0 +1,67 @@
from __future__ import annotations
import json
import tempfile
import unittest
from pathlib import Path
from scripts.generate_download_links import build_links, find_installer, release_asset_url, version_from_tag
class GenerateDownloadLinksTests(unittest.TestCase):
def test_version_from_tag(self) -> None:
self.assertEqual(version_from_tag("v1.7.0"), "1.7.0")
self.assertEqual(version_from_tag("1.7.0"), "1.7.0")
def test_release_asset_url(self) -> None:
actual = release_asset_url("rememberber/MooTool", "v1.7.0", "MooTool-1.7.0-windows-x64.exe")
self.assertEqual(
actual,
"https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-windows-x64.exe",
)
def test_find_installer_prefers_exact_name(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir)
(root / "MooTool-1.7.0-windows-x64.exe").write_text("exe", encoding="utf-8")
(root / "MooTool-1.7.0-windows-x64.zip").write_text("zip", encoding="utf-8")
actual = find_installer([root / "MooTool-1.7.0-windows-x64.exe", root / "MooTool-1.7.0-windows-x64.zip"], "1.7.0", "windows-x64", (".exe", ".zip"))
self.assertEqual(actual.name, "MooTool-1.7.0-windows-x64.exe")
def test_build_links_for_tag_release_assets(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir)
(root / "MooTool-1.7.0-mac-apple-silicon.dmg").write_text("dmg", encoding="utf-8")
(root / "MooTool-1.7.0-windows-x64.exe").write_text("exe", encoding="utf-8")
(root / "MooTool-1.7.0-linux-x64.deb").write_text("deb", encoding="utf-8")
links = build_links(root, "v1.7.0")
self.assertEqual(
links,
{
"windows": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-windows-x64.exe",
"macSilicon": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-mac-apple-silicon.dmg",
"linux": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-linux-x64.deb",
},
)
def test_build_links_merge_keeps_existing_mac_when_only_intel_rebuilt(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir)
(root / "MooTool-1.7.0-mac-intel.dmg").write_text("dmg", encoding="utf-8")
existing = {
"windows": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-windows-x64.exe",
"macSilicon": "https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-mac-apple-silicon.dmg",
}
links = build_links(root, "v1.7.0", existing_links=existing)
self.assertEqual(
links["mac"],
"https://github.com/rememberber/MooTool/releases/download/v1.7.0/MooTool-1.7.0-mac-intel.dmg",
)
self.assertEqual(links["windows"], existing["windows"])
self.assertEqual(links["macSilicon"], existing["macSilicon"])
if __name__ == "__main__":
unittest.main()
@@ -19,7 +19,7 @@ public class UiConsts {
* 软件名称,版本
*/
public static final String APP_NAME = "MooTool";
public static final String APP_VERSION = "v1.7.0";
public static final String APP_VERSION = "v1.7.1";
public static final int TABLE_ROW_HEIGHT = 30;
@@ -87,28 +87,33 @@ public class UiConsts {
*/
public final static Icon HELP_FOCUSED_ICON = new FlatSVGIcon("icon/help-filling.svg");
/**
* GitHub master 分支 raw 内容根路径
*/
public static final String GITHUB_RAW_MASTER_URL = "https://raw.githubusercontent.com/rememberber/MooTool/master/";
/**
* 软件版本检查url
*/
public static final String CHECK_VERSION_URL = "https://gitee.com/zhoubochina/MooTool/raw/master/src/main/resources/version_summary.json";
public static final String CHECK_VERSION_URL = GITHUB_RAW_MASTER_URL + "src/main/resources/version_summary.json";
/**
* 软件下载链接信息url
*/
public static final String DOWNLOAD_LINK_INFO_URL = "https://gitee.com/zhoubochina/MooTool/raw/develop/download_links.json";
public static final String DOWNLOAD_LINK_INFO_URL = GITHUB_RAW_MASTER_URL + "download_links.json";
/**
* Grace信息url
*/
public static final String GRACE_INFO_URL = "https://gitee.com/zhoubochina/MooTool/raw/develop/grace.json";
public static final String GRACE_INFO_URL = GITHUB_RAW_MASTER_URL + "grace.json";
/**
* 贡献者信息url
*/
public static final String CONTRIBUTOR_URL = "https://gitee.com/zhoubochina/MooTool/raw/develop/contributor.json";
public static final String CONTRIBUTOR_URL = GITHUB_RAW_MASTER_URL + "contributor.json";
/**
* DAU信息url
*/
public static final String DAU_URL = "https://gitee.com/zhoubochina/MooTool/raw/develop/dau.json";
public static final String DAU_URL = GITHUB_RAW_MASTER_URL + "dau.json";
}
@@ -82,7 +82,7 @@ public class UpdateDialog extends JDialog {
buttonDownloadFromWeb.addActionListener(e -> {
Desktop desktop = Desktop.getDesktop();
try {
desktop.browse(new URI("https://gitee.com/zhoubochina/MooTool/releases"));
desktop.browse(new URI("https://github.com/rememberber/MooTool/releases"));
} catch (IOException | URISyntaxException ex) {
ex.printStackTrace();
}
@@ -98,7 +98,7 @@ public class UpdateDialog extends JDialog {
String downloadLinkInfo = HttpUtil.get(UiConsts.DOWNLOAD_LINK_INFO_URL);
if (StringUtils.isEmpty(downloadLinkInfo) || downloadLinkInfo.contains("404: Not Found")) {
JOptionPane.showMessageDialog(App.mainFrame,
"获取下载链接失败,请关注Gitee Release", "网络错误",
"获取下载链接失败,请关注 GitHub Release", "网络错误",
JOptionPane.INFORMATION_MESSAGE);
return;
} else {
+8 -2
View File
@@ -1,5 +1,5 @@
{
"currentVersion": "v1.7.0",
"currentVersion": "v1.7.1",
"versionIndex": {
"v0.0.0": "0",
"v1.0.0": "1",
@@ -41,7 +41,8 @@
"v1.6.7": "37",
"v1.6.8": "38",
"v1.6.9": "39",
"v1.7.0": "40"
"v1.7.0": "40",
"v1.7.1": "41"
},
"versionDetailList": [
{
@@ -248,6 +249,11 @@
"version": "v1.7.0",
"title": "HTTP工具支持cURL导入",
"log": "● 新功能:HTTP工具支持cURL导入 by Cassian Florin\n\n"
},
{
"version": "v1.7.1",
"title": "近期累积的一些更新和优化",
"log": "● feat(cron): 支持Linux Cron表达式 by Cassian Florin\n● feat(cron): fix(undo): 修复 Ctrl+Z 撤销异常 by Cassian Florin\n● fix(tray): 修复多屏托盘菜单定位 by Cassian Florin\n● fix: support Apple Silicon macOS package selection by Cassian Florin\n● feat: 时间转换支持时区\n\n"
}
]
}