Skip to content

Commit 9e30c7c

Browse files
committed
feat: make a detailed release note for each release
Signed-off-by: WANG Xuerui <[email protected]>
1 parent 715d290 commit 9e30c7c

File tree

5 files changed

+265
-41
lines changed

5 files changed

+265
-41
lines changed

.editorconfig

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ end_of_line = lf
88
indent_style = space
99
insert_final_newline = true
1010

11-
[*.{md,sh}]
11+
[*.{md,py,sh}]
1212
indent_size = 4
1313

1414
[Dockerfile*]

.github/workflows/build.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,13 @@ jobs:
7878
- name: Check the artifacts
7979
run: ls -alF tmp/release/build-out
8080

81+
- name: Render the release notes
82+
run: ./scripts/render-release-notes.py "${{ github.repository }}" "${{ github.ref }}" tmp/release/build-out > tmp/release-notes.md
83+
8184
- name: Make the release
8285
uses: softprops/action-gh-release@v2
8386
with:
84-
body_path: release-notes.md
87+
body_path: tmp/release-notes.md
8588
files: tmp/release/build-out/*
8689
generate_release_notes: false
8790
prerelease: false # treat upstream preview releases as non-prerelease too

release-notes.md

-39
This file was deleted.

scripts/render-release-notes.py

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#!/usr/bin/env python3
2+
3+
from collections.abc import Mapping
4+
import pathlib
5+
import re
6+
from string import Template
7+
import sys
8+
from typing import Iterator, NamedTuple
9+
from urllib.parse import urljoin, quote
10+
11+
12+
TAG_REF_PREFIX = "refs/tags/"
13+
RE_PORTABLE_RID = re.compile(
14+
r"[-.]((?:android|ios|iossimulator|linux|linux-bionic|linux-musl|osx|win)-[0-9a-z]+)\.tar(?:\.gz|\.bz2|\.xz|\.zst)?$"
15+
)
16+
17+
def usage(argv0: str) -> int:
18+
print(
19+
f"usage: {argv0} <owner/repo> <{TAG_REF_PREFIX}/RELEASE-TAG-NAME> <path/to/out/dir>",
20+
file=sys.stderr,
21+
)
22+
return 1
23+
24+
25+
def extract_rid(name: str) -> str:
26+
if m := RE_PORTABLE_RID.search(name):
27+
return m.group(1)
28+
raise ValueError(f"not name of an archive with RID: {name}")
29+
30+
31+
class URLMaker:
32+
def __init__(self, owner_repo: str, release_name: str) -> None:
33+
release_name = quote(release_name)
34+
self._base = f"https://github.com/{owner_repo}/releases/download/{release_name}/"
35+
36+
def make_download_url(self, asset_name: str) -> str:
37+
return urljoin(self._base, quote(asset_name))
38+
39+
40+
class MiscFile(NamedTuple):
41+
name: str
42+
desc: str
43+
44+
45+
class TemplateCtx(Mapping[str, str]):
46+
def __init__(self, um: URLMaker) -> None:
47+
self._um = um
48+
self._known_rids: dict[str, None] = {}
49+
self._sdk_archives_by_rid: dict[str, str] = {}
50+
self._symbols_all_archives_by_rid: dict[str, str] = {}
51+
self._symbols_sdk_archives_by_rid: dict[str, str] = {}
52+
self._misc_files: list[MiscFile] = []
53+
54+
def _note_rid(self, rid: str) -> None:
55+
self._known_rids[rid] = None
56+
57+
def add_sdk_archive(self, name: str) -> None:
58+
# name is like "dotnet-sdk-9.0.101-linux-x64.tar.gz"
59+
rid = extract_rid(name)
60+
self._note_rid(rid)
61+
self._sdk_archives_by_rid[rid] = name
62+
63+
def add_symbol_archive(self, name: str) -> None:
64+
rid = extract_rid(name)
65+
self._note_rid(rid)
66+
if name.startswith("dotnet-symbols-all-"):
67+
self._symbols_all_archives_by_rid[rid] = name
68+
elif name.startswith("dotnet-symbols-sdk-"):
69+
self._symbols_sdk_archives_by_rid[rid] = name
70+
else:
71+
raise ValueError(f"unexpected symbol archive kind: {name}")
72+
73+
def _add_misc_file(self, f: MiscFile) -> None:
74+
self._misc_files.append(f)
75+
76+
def add_sb_artifacts_archive(self, name: str) -> None:
77+
rid = extract_rid(name)
78+
self._note_rid(rid)
79+
return self._add_misc_file(
80+
MiscFile(name, f"源码构建用产物包 / Source-build artifacts - `{rid}`"),
81+
)
82+
83+
def add_sdk_feed_archive(self, name: str) -> None:
84+
if name.startswith("sdk-feed-stage1"):
85+
return self._add_misc_file(
86+
MiscFile(name, "更新源用物料 / SDK feed material - `linux-x64`"),
87+
)
88+
elif name.startswith("sdk-feed-stage2"):
89+
rid = extract_rid(name)
90+
self._note_rid(rid)
91+
return self._add_misc_file(
92+
MiscFile(name, f"更新源用物料 / SDK feed material - `{rid}`"),
93+
)
94+
else:
95+
raise ValueError(f"unexpected sdk feed archive name: {name}")
96+
97+
def make_row(self, filename: str, desc: str) -> str:
98+
url = self._um.make_download_url(filename)
99+
sha256 = self._um.make_download_url(filename + ".sha256")
100+
sha512 = self._um.make_download_url(filename + ".sha512")
101+
return f"| [{desc}]({url}) | [SHA256]({sha256}), [SHA512]({sha512}) |\n"
102+
103+
def rid_assets_rows(self, rid: str) -> list[str]:
104+
result: list[str] = []
105+
if f := self._sdk_archives_by_rid.get(rid, None):
106+
result.append(self.make_row(f, ".NET SDK"))
107+
if f := self._symbols_all_archives_by_rid.get(rid, None):
108+
result.append(self.make_row(f, "调试符号 (所有) / Debug symbols (all)"))
109+
if f := self._symbols_sdk_archives_by_rid.get(rid, None):
110+
result.append(self.make_row(f, "调试符号 (SDK) / Debug symbols (SDK)"))
111+
return result
112+
113+
def misc_files_rows(self) -> list[str]:
114+
return [self.make_row(mf.name, mf.desc) for mf in sorted(self._misc_files)]
115+
116+
def __iter__(self) -> Iterator[str]:
117+
# "rid_*" and "misc"
118+
yield from self._known_rids.keys()
119+
if self._misc_files:
120+
yield "misc"
121+
122+
def __len__(self) -> int:
123+
has_misc = len(self._misc_files) > 0
124+
return len(self._known_rids) + (1 if has_misc else 0)
125+
126+
def __getitem__(self, k: str) -> str:
127+
if k.startswith("rid_"):
128+
rid = k[4:].replace("_","-")
129+
return "".join(self.rid_assets_rows(rid))
130+
elif k == "misc":
131+
return "".join(self.misc_files_rows())
132+
raise KeyError(f"unsupported key: {k}")
133+
134+
135+
def main(argv: list[str]) -> int:
136+
if len(argv) != 4:
137+
return usage(argv[0])
138+
139+
if not argv[2].startswith(TAG_REF_PREFIX):
140+
return usage(argv[0])
141+
142+
owner_repo = argv[1] # let's just trust the input from CI
143+
tag_name = argv[2][len(TAG_REF_PREFIX):]
144+
outdir = pathlib.Path(argv[3])
145+
146+
um = URLMaker(owner_repo, tag_name)
147+
tctx = TemplateCtx(um)
148+
for f in outdir.glob("*"):
149+
name = f.name
150+
is_sha256 = name.endswith(".sha256")
151+
is_sha512 = name.endswith(".sha512")
152+
if is_sha256 or is_sha512:
153+
# we assume every archive is accompanied by these, so no need to
154+
# track individually
155+
continue
156+
157+
if name.startswith("Private.SourceBuilt.Artifacts."):
158+
tctx.add_sb_artifacts_archive(name)
159+
elif name.startswith("dotnet-sdk-"):
160+
tctx.add_sdk_archive(name)
161+
elif name.startswith("dotnet-symbols-"):
162+
tctx.add_symbol_archive(name)
163+
elif name.startswith("sdk-feed-"):
164+
tctx.add_sdk_feed_archive(name)
165+
else:
166+
print(f"warning: unrecognized asset name {name}", file=sys.stderr)
167+
168+
my_dir = pathlib.Path(__file__).parent
169+
tmpl_str = (my_dir / '..' / 'templates' / 'release-notes.md').read_text()
170+
tmpl = Template(tmpl_str)
171+
print(tmpl.substitute(tctx).strip('\n'))
172+
173+
return 0
174+
175+
176+
if __name__ == "__main__":
177+
sys.exit(main(sys.argv))

templates/release-notes.md

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
## 平台兼容性说明 / Notes on platform compatibility
2+
3+
本服务提供的 .NET SDK 仅适用于 LoongArch 新世界(“ABI2.0”)。如您有 LoongArch 旧世界(“ABI1.0”)开发需求,请移步[龙芯公司 .NET 发布页面][loongnix-dotnet]。您可阅读[《新世界与旧世界》一文][awly-old-and-new-worlds]获知更多关于LoongArch“新旧世界”情况的细节。
4+
5+
.NET SDK 的个别组件是以 C/C++ 写作的,架构相关的二进制。我们采用 **LoongArch64 v1.00** 作为 LoongArch 的二进制兼容基准,遵循[《LoongArch 软件开发与构建约定》v0.1][la-softdev-convention](目前仅提供英文版)。这意味着本服务提供的 .NET SDK 可以兼容所有支持 LSX 的 LoongArch 硬件、操作系统组合。
6+
7+
> [!NOTE]
8+
> 这同时也意味着**本 SDK 无法在不提供 LSX 的平台上工作**,主要涉及那些龙芯等厂商将其归属于“嵌入式”或“工控”场景的型号,包括但不限于龙芯 1 系、2K1000LA、2K0500、2K0300 等。
9+
>
10+
> 如您有需要在这些平台上使用本 SDK,请在[工单系统][issue-tracker]上表明需求,但我们不一定有能力在短期内支持。
11+
12+
The .NET SDKs provided by this service conforms to the LoongArch new world ("ABI2.0") only. If you need to develop for the old world ("ABI1.0"), please consult [the Loongson .NET release page][loongnix-dotnet] for details. You can read more about the LoongArch "old and new worlds" situation in the [*The Old World and the New World*][awly-old-and-new-worlds] essay (only available in Chinese for now).
13+
14+
Some components of .NET SDK are written in C/C++ and architecture-dependent. We adopt **LoongArch64 v1.00** as the baseline of binary compatibility for LoongArch, and follow the [*Software Development and Build Convention for LoongArch™ Architectures* v0.1][la-softdev-convention]. This means the .NET SDKs provided by this service are compatible with any hardware and OS combination that supports LSX.
15+
16+
> [!NOTE]
17+
> This also means *our SDKs will not work on platforms without LSX*, that mainly involve models deemed for "embedded" or "industrial controls" use cases by Loongson and other vendors, including but not limited to the Loongson 1 series, 2K1000LA, 2K0500, and 2K0300.
18+
>
19+
> If you need to deploy our SDKs on such platforms, please post on [the issue tracker][issue-tracker], although we may not be able to implement such support in the short term.
20+
21+
[loongnix-dotnet]: http://www.loongnix.cn/zh/api/dotnet/
22+
[awly-old-and-new-worlds]: https://areweloongyet.com/docs/old-and-new-worlds/
23+
[la-softdev-convention]: https://github.com/loongson/la-softdev-convention/blob/v0.1/la-softdev-convention.adoc
24+
[issue-tracker]: https://github.com/loongson-community/dotnet-unofficial-build/issues
25+
26+
## 内容介绍 / Contents
27+
28+
### RID `linux-loongarch64`
29+
30+
这些构建产物适用于以 **glibc** 为 C 运行时库的 LoongArch64 Linux 发行版,也就是大多数发行版。所需的最低 glibc 版本为 **2.40**
31+
32+
These artifacts are suitable for use on LoongArch64 Linux distributions with **glibc** as the C runtime library, which include most distributions out there. A minimum glibc version of **2.40** is needed.
33+
34+
| 二进制包 / Binaries | 校验和 / Checksums |
35+
|---|---|
36+
$rid_linux_loongarch64
37+
38+
### RID `linux-musl-loongarch64`
39+
40+
这些构建产物适用于以 **musl** 为 C 运行时库的 LoongArch64 Linux 发行版,如 Alpine Linux 等。所需的最低 musl 版本为 **1.2.5**
41+
42+
These artifacts are suitable for use on LoongArch64 Linux distributions with **musl** as the C runtime library, such as Alpine Linux. A minimum musl version of **1.2.5** is needed.
43+
44+
> [!NOTE]
45+
> 在运行时,还需要一些额外依赖。以 Alpine Linux 的包名列举如下,至少需要这些:
46+
>
47+
> Additional dependencies are needed at runtime. At least the below are needed, in terms of Alpine Linux package names:
48+
>
49+
> * `libgcc`
50+
> * `libstdc++`
51+
> * `icu-libs`
52+
53+
| 二进制包 / Binaries | 校验和 / Checksums |
54+
|---|---|
55+
$rid_linux_musl_loongarch64
56+
57+
### RID `linux-x64`
58+
59+
这是适用于 x86\_64 Linux 系统的“第一阶段”(Stage 1)构建产物,与官方出品的区别在于包含了支持 LoongArch 平台所需的额外改动。可供需要在 x86\_64 系统上做实验或交叉编译的人员便捷取用。
60+
61+
These artifacts are "Stage 1" builds for x86\_64 Linux systems, different from the official product in that extra changes for LoongArch support are integrated. These are suitable for your experimentation or cross-compilation convenience on x86\_64 systems.
62+
63+
| 二进制包 / Binaries | 校验和 / Checksums |
64+
|---|---|
65+
$rid_linux_x64
66+
67+
### 其他:面向开发者与镜像源管理员 / Others: for developers and mirror admins
68+
69+
对于常规用途,仅需下载 `dotnet-sdk-*.tar.gz` 即可。
70+
71+
形如 `Private.SourceBuilt.Artifacts.*` 的压缩包,用于从源码构建 .NET SDK 本身,就像本项目所做的一样。如果您是 .NET 贡献者,您应该就已经明白如何利用它们了。
72+
73+
希望自行搭建 .NET 更新源(例如适合用于 `dotnet-install.sh` 脚本的 `--azure-feed` 选项的下载服务)的开发者,可基于所提供的 `sdk-feed-stage*.tar` 文件开展工作。
74+
75+
For general usage, only downloading `dotnet-sdk-*.tar.gz` would be enough.
76+
77+
The `Private.SourceBuilt.Artifacts.*` archives are used to build .NET SDK itself from source, just like this project does. You should already know how to make use of these if you are a .NET contributor.
78+
79+
Developers who wish to self-host their .NET update feed (for example a download service suitable for the `--azure-feed` option of `dotnet-install.sh`) can start from the `sdk-feed-stage*.tar` files provided.
80+
81+
| 二进制包 / Binaries | 校验和 / Checksums |
82+
|---|---|
83+
$misc

0 commit comments

Comments
 (0)