Skip to content

Commit 6dddc59

Browse files
authored
release stable (#229)
1 parent 1c2542f commit 6dddc59

16 files changed

+415
-163
lines changed

.github/workflows/ci.yml

+29-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ on:
44
push:
55
branches:
66
- master
7+
tags:
8+
- '[0-9]+.*'
79
pull_request:
810
workflow_dispatch:
911

@@ -112,18 +114,30 @@ jobs:
112114
path: |
113115
meta.json
114116
117+
- name: Generate version.json
118+
if: ${{ matrix.arch == 'arm64' && matrix.type == 'Release' }}
119+
run: python scripts/generate-version.py
120+
121+
- name: Upload version.json
122+
if: ${{ matrix.arch == 'arm64' && matrix.type == 'Release' }}
123+
uses: actions/upload-artifact@v4
124+
with:
125+
name: version.json
126+
path: |
127+
version.json
128+
115129
- name: Setup tmate session
116130
if: ${{ failure() }}
117131
uses: mxschmitt/action-tmate@v3
118132

119133
compare:
120-
if: ${{ github.ref != 'refs/heads/master' }}
134+
if: ${{ github.event_name == 'pull_request' }}
121135
needs: build
122136
uses: ./.github/workflows/compare.yml
123137

124138
release:
125139
needs: build
126-
if: ${{ github.ref == 'refs/heads/master' && !contains(github.event.head_commit.message, '!release') }}
140+
if: ${{ github.event_name != 'pull_request' }}
127141
runs-on: ubuntu-latest
128142
steps:
129143
- name: Download artifact
@@ -132,6 +146,7 @@ jobs:
132146
merge-multiple: true
133147

134148
- name: Create Nightly release
149+
if: ${{ github.ref == 'refs/heads/master' }}
135150
uses: 'marvinpinto/action-automatic-releases@latest'
136151
with:
137152
repo_token: ${{ secrets.GITHUB_TOKEN }}
@@ -141,3 +156,15 @@ jobs:
141156
files: |
142157
Fcitx5-*.tar.bz2
143158
meta.json
159+
version.json
160+
161+
- name: Create Stable release
162+
if: ${{ github.ref != 'refs/heads/master' }}
163+
uses: 'marvinpinto/action-automatic-releases@latest'
164+
with:
165+
repo_token: ${{ secrets.GITHUB_TOKEN }}
166+
draft: true
167+
prerelease: false
168+
title: ${{ github.ref_name }}
169+
files: |
170+
Fcitx5-*.tar.bz2

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ assets/en.lproj/Localizable.strings
99
assets/po/base.pot
1010
meta.swift
1111
*~
12+
version.json
13+
*.pyc

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ English
66

77
[Fcitx5](https://github.com/fcitx/fcitx5) input method framework ported to macOS.
88

9-
Public beta: please download [installer](https://github.com/fcitx-contrib/fcitx5-macos-installer).
9+
Please download [installer](https://github.com/fcitx-contrib/fcitx5-macos-installer).
1010

1111
## Build
1212
Native build on Intel and Apple Silicon is supported.

README.zh-CN.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66

77
[Fcitx5](https://github.com/fcitx/fcitx5) 输入框架的 macOS 移植。
88

9-
公测进行中,请下载[安装器](https://github.com/fcitx-contrib/fcitx5-macos-installer/blob/master/README.zh-CN.md)
9+
请下载[安装器](https://github.com/fcitx-contrib/fcitx5-macos-installer/blob/master/README.zh-CN.md)
754 Bytes
Binary file not shown.

docs/release.zh-CN.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# 发布新版本
2+
3+
* 确保当前位于和 GitHub 同步的 master 分支,工作树干净。
4+
* `python scripts/prepare-release.py`,它会
5+
* 对当前 commit 打 tag;
6+
* 将新版本插入 [version.jsonl](../version.jsonl) 首行;
7+
* 更新 [CMakeLists.txt](../CMakeLists.txt) 中的版本号;
8+
* 将上述两个文件的更改加入暂存区。
9+
* `git push origin 版本号`,这会在 GitHub 上创建一个新的 draft release。
10+
* 编辑更新日志,删除 debug tar,点击 Publish release。
11+
*[fcitx5-plugins](https://github.com/fcitx-contrib/fcitx5-plugins) 中发布新版插件。
12+
* 如果下一个版本将抛弃 macOS 的主/次版本,更改 CMakeLists.txt 中 project 的主/次版本和 CMAKE_OSX_DEPLOYMENT_TARGET。
13+
* 提交更改,`git push origin master`,这将更新 latest 中的 version.json,用户检查更新时将获取到新版信息。
14+
*[fcitx5-macos-installer](https://github.com/fcitx-contrib/fcitx5-macos-installer) 中发布新版安装包。

scripts/common.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import re
2+
import subprocess
3+
4+
def dollar(command: str):
5+
return subprocess.check_output(command, shell=True, text=True).strip()
6+
7+
8+
def get_json(tag: str):
9+
cmake_osx_deployment_target_line = dollar('grep "set(CMAKE_OSX_DEPLOYMENT_TARGET" CMakeLists.txt')
10+
match = re.search(r'CMAKE_OSX_DEPLOYMENT_TARGET ([\d\.]+)\)', cmake_osx_deployment_target_line)
11+
if match is None:
12+
raise Exception('CMakeLists.txt should set CMAKE_OSX_DEPLOYMENT_TARGET properly.')
13+
macos = match.group(1)
14+
return {
15+
'tag': tag,
16+
'macos': macos,
17+
'sha': dollar('git rev-parse HEAD'),
18+
'time': int(dollar('git show --no-patch --format=%ct'))
19+
}

scripts/generate-version.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import json
2+
from common import get_json
3+
4+
versions = []
5+
6+
with open('version.jsonl') as f:
7+
while line := f.readline():
8+
versions.append(json.loads(line))
9+
10+
# Generate sha and time for tag latest.
11+
latest = get_json('latest')
12+
13+
with open('version.json', 'w') as f:
14+
json.dump({
15+
'versions': [latest] + versions
16+
}, f)

scripts/prepare-release.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import json
2+
import os
3+
import re
4+
from common import dollar, get_json
5+
6+
def get_version():
7+
project_line = dollar('grep "project(fcitx5-macos VERSION" CMakeLists.txt')
8+
match = re.search(r'VERSION ([\d\.]+)', project_line)
9+
if match is None:
10+
raise Exception('CMakeLists.txt should set VERSION properly.')
11+
return match.group(1)
12+
13+
version = get_version()
14+
if os.system(f'git tag -a {version} -m "release {version}"') != 0:
15+
raise Exception('Failed to create git tag.')
16+
17+
with open('version.jsonl') as f:
18+
content = f.read()
19+
20+
with open('version.jsonl', 'w') as f:
21+
json.dump(get_json(version), f)
22+
f.write('\n' + content)
23+
24+
major, minor, patch = version.split('.')
25+
26+
if os.system(f'sed -i.bak "s/fcitx5-macos VERSION {major}\\.{minor}\\.{patch}/fcitx5-macos VERSION {major}.{minor}.{int(patch) + 1}/" CMakeLists.txt') != 0:
27+
raise Exception('Failed to update version in CMakeLists.txt.')
28+
os.system('rm CMakeLists.txt.bak')
29+
30+
os.system('git add version.jsonl CMakeLists.txt')

src/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ execute_process(COMMAND git show --no-patch --format=%ct
66
OUTPUT_VARIABLE UNIX_TIME
77
OUTPUT_STRIP_TRAILING_WHITESPACE
88
)
9+
execute_process(COMMAND bash -c "git describe --exact-match || echo latest"
10+
OUTPUT_VARIABLE RELEASE_TAG
11+
OUTPUT_STRIP_TRAILING_WHITESPACE
12+
)
913
configure_file(config/meta.swift.in ${CMAKE_CURRENT_SOURCE_DIR}/config/meta.swift @ONLY)
1014

1115
file(GLOB CONFIG_UI_FILES CONFIGURE_DEPENDS config/*.swift)

src/config/about.swift

+51-35
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,6 @@ func disableInputMethod() {
3232
}
3333
}
3434

35-
struct Object: Codable {
36-
let sha: String
37-
}
38-
39-
struct Tag: Codable {
40-
let object: Object
41-
}
42-
4335
enum UpdateState {
4436
case notChecked // Clickable "Check update"
4537
case checking // Disabled "Checking"
@@ -56,6 +48,7 @@ struct AboutView: View {
5648
@State private var downloadProgress = 0.0
5749

5850
@State private var showUpToDate = false
51+
@State private var showSystemNotSupported = false
5952
@State private var showCheckFailed = false
6053
@State private var showDownloadFailed = false
6154
@State private var showInstallFailed = false
@@ -64,6 +57,7 @@ struct AboutView: View {
6457
@State private var removeUserData = false
6558
@State private var uninstalling = false
6659
@State private var uninstallFailed = false
60+
@State private var targetTag: String? = nil
6761

6862
var body: some View {
6963
VStack {
@@ -87,7 +81,11 @@ struct AboutView: View {
8781
}
8882

8983
Spacer().frame(height: gapSize)
90-
urlButton(String(commit.prefix(7)), sourceRepo + "/commit/" + commit)
84+
if releaseTag == "latest" {
85+
urlButton(String(commit.prefix(7)), sourceRepo + "/commit/" + commit)
86+
} else {
87+
urlButton(releaseTag, sourceRepo + "/tree/" + releaseTag)
88+
}
9189

9290
Spacer().frame(height: gapSize)
9391
Text(getDate())
@@ -117,7 +115,7 @@ struct AboutView: View {
117115
if viewModel.state == .notChecked {
118116
checkUpdate()
119117
} else if viewModel.state == .available {
120-
update(debug: isDebug)
118+
update(debug: isDebug && targetTag == "latest")
121119
}
122120
} label: {
123121
if viewModel.state == .notChecked || viewModel.state == .upToDate {
@@ -144,9 +142,15 @@ struct AboutView: View {
144142
}
145143
) {
146144
VStack {
147-
Text("Update available")
145+
if let tag = targetTag { // Should always be true.
146+
if tag == "latest" {
147+
Text("Latest (unstable) version available")
148+
} else {
149+
Text("Version \(tag) available")
150+
}
151+
}
148152
Button {
149-
update(debug: isDebug)
153+
update(debug: isDebug && targetTag == "latest")
150154
} label: {
151155
Text("Update now")
152156
}.buttonStyle(.borderedProminent)
@@ -164,20 +168,21 @@ struct AboutView: View {
164168
} label: {
165169
Text("Switch to Release")
166170
}.disabled(
167-
viewModel.state == .downloading || viewModel.state == .installing
171+
targetTag == nil || viewModel.state == .downloading || viewModel.state == .installing
168172
)
169173
} else {
170174
Button {
171175
showSwitchToDebug = true
172176
} label: {
173177
Text("Switch to Debug")
174178
}.disabled(
175-
viewModel.state == .downloading || viewModel.state == .installing
179+
targetTag != "latest" || viewModel.state == .downloading
180+
|| viewModel.state == .installing
176181
).sheet(
177182
isPresented: $showSwitchToDebug
178183
) {
179184
VStack {
180-
Text("Switch to debug only if Fctix5 crashes and you want to help debug.")
185+
Text("Switch to debug only if Fcitx5 crashes and you want to help debug.")
181186
HStack {
182187
Button {
183188
showSwitchToDebug = false
@@ -250,6 +255,11 @@ struct AboutView: View {
250255
displayMode: .hud, type: .complete(Color.green),
251256
title: NSLocalizedString("Fcitx5 is up to date", comment: ""))
252257
}
258+
.toast(isPresenting: $showSystemNotSupported) {
259+
AlertToast(
260+
displayMode: .hud, type: .error(Color.red),
261+
title: NSLocalizedString("Your system version is no longer supported", comment: ""))
262+
}
253263
.toast(isPresenting: $showCheckFailed) {
254264
AlertToast(
255265
displayMode: .hud, type: .error(Color.red),
@@ -292,37 +302,43 @@ struct AboutView: View {
292302
}
293303

294304
func checkUpdate() {
295-
guard
296-
// https://api.github.com/repos/fcitx-contrib/fcitx5-macos/git/ref/tags/latest
297-
// GitHub API may be blocked in China and is unstable in general.
298-
let url = URL(
299-
string: "\(sourceRepo)/releases/download/latest/meta.json")
300-
else {
301-
return
302-
}
303305
viewModel.state = .checking
304-
URLSession.shared.dataTask(with: url) { data, response, error in
305-
if let data = data,
306-
let tag = try? JSONDecoder().decode(Tag.self, from: data)
307-
{
308-
if tag.object.sha == commit {
309-
viewModel.state = .upToDate
310-
showUpToDate = true
311-
} else {
306+
checkMainUpdate { success, latestCompatible, latest, stable in
307+
if success {
308+
if let stable = stable {
309+
// latest >= stable > current
310+
targetTag = stable.tag
312311
viewModel.state = .availableSheet
312+
} else {
313+
if !latestCompatible {
314+
viewModel.state = .upToDate
315+
showSystemNotSupported = true
316+
} else if latest == nil {
317+
// latest == current >= stable
318+
viewModel.state = .upToDate
319+
showUpToDate = true
320+
} else {
321+
// latest > current >= stable
322+
targetTag = "latest"
323+
viewModel.state = .availableSheet
324+
}
313325
}
314326
} else {
315327
viewModel.state = .notChecked
316328
showCheckFailed = true
317329
}
318-
}.resume()
330+
}
319331
}
320332

321333
func update(debug: Bool) {
334+
guard let tag = targetTag else {
335+
FCITX_ERROR("Calling update with nil tag")
336+
return
337+
}
322338
viewModel.state = .downloading
323-
checkPluginUpdate({ success, nativePlugins, dataPlugins in
339+
checkPluginUpdate(tag) { success, nativePlugins, dataPlugins in
324340
let updater = Updater(
325-
main: true, debug: debug, nativePlugins: nativePlugins, dataPlugins: dataPlugins)
341+
tag: tag, main: true, debug: debug, nativePlugins: nativePlugins, dataPlugins: dataPlugins)
326342
updater.update(
327343
// Install plugin in a best-effort manner. No need to check plugin status.
328344
onFinish: { result, _, _ in
@@ -336,7 +352,7 @@ struct AboutView: View {
336352
onProgress: { progress in
337353
downloadProgress = progress
338354
})
339-
})
355+
}
340356
}
341357

342358
func install(debug: Bool) {

0 commit comments

Comments
 (0)