Skip to content

Commit 9b4fa87

Browse files
FeodorFitsnerclaude
andcommitted
Add serious_python:main version [--json] + CI vars from registry
The Dart `_pythonReleases` registry is the source of truth for the Python version matrix, but until now CI (and any other external consumer) needed a hand-maintained lookup table to derive the full patch version + python-build release date from the short SERIOUS_PYTHON_VERSION. Last CI run failed exactly because the new SERIOUS_PYTHON_FULL_VERSION / SERIOUS_PYTHON_BUILD_DATE env vars were unset in CI, the plugin scripts fell back to their 3.14.6/20260611 defaults, and Linux's ninja then complained that the 3.12 tarball was missing libpython3.14.so.1.0. New `version` subcommand emits the registry as either a human-readable summary or `--json` (machine-readable). Self-version comes from pubspec.yaml looked up via `package_config` (no Dart constant to keep in sync). Registry rows are sorted descending in the text output to match `flet --version` ordering. `_pythonReleases` and `_PythonRelease` promoted to `pythonReleases` / `PythonRelease` so the new file can reference them. Internal callers in `package_command.dart` updated to match. CI: new composite action `.github/actions/resolve-python-vars` runs `dart run serious_python:main version --json` + `jq` to write SERIOUS_PYTHON_FULL_VERSION and SERIOUS_PYTHON_BUILD_DATE into `$GITHUB_ENV`. Inserted as a `Resolve Python version vars` step right after `Setup Flutter` in all 15 test jobs (publish job left untouched). Future Python registry bumps need no CI changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4329b6b commit 9b4fa87

6 files changed

Lines changed: 189 additions & 14 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Resolve serious_python Python-version vars
2+
description: |
3+
Derive `SERIOUS_PYTHON_FULL_VERSION` and `SERIOUS_PYTHON_BUILD_DATE` for
4+
the current `SERIOUS_PYTHON_VERSION` from `dart run serious_python:main
5+
version --json` and export them to `$GITHUB_ENV` so subsequent steps —
6+
including the platform plugin build scripts (Android `build.gradle`,
7+
Darwin `prepare_{ios,macos}.sh`, Linux/Windows `CMakeLists.txt`) — pick
8+
up the registry-correct values without having to hand-maintain a lookup
9+
table in the workflow.
10+
11+
Requires Flutter to be on PATH and `SERIOUS_PYTHON_VERSION` to be set in
12+
env (typically by the caller's `env:` block, keyed off
13+
`matrix.python_version`).
14+
15+
runs:
16+
using: composite
17+
steps:
18+
- name: Resolve full version + python-build date
19+
shell: bash
20+
working-directory: src/serious_python
21+
run: |
22+
set -euo pipefail
23+
if [[ -z "${SERIOUS_PYTHON_VERSION:-}" ]]; then
24+
echo "::error::SERIOUS_PYTHON_VERSION is not set in env."
25+
exit 1
26+
fi
27+
flutter pub get >/dev/null
28+
json=$(dart run serious_python:main version --json)
29+
full=$(echo "$json" | jq -r ".python_releases.\"${SERIOUS_PYTHON_VERSION}\".standalone_version")
30+
date=$(echo "$json" | jq -r ".python_releases.\"${SERIOUS_PYTHON_VERSION}\".python_build_release_date")
31+
if [[ -z "$full" || "$full" == "null" || -z "$date" || "$date" == "null" ]]; then
32+
echo "::error::serious_python:main version --json did not return values for SERIOUS_PYTHON_VERSION=${SERIOUS_PYTHON_VERSION}"
33+
echo "$json"
34+
exit 1
35+
fi
36+
echo "SERIOUS_PYTHON_FULL_VERSION=$full" >> "$GITHUB_ENV"
37+
echo "SERIOUS_PYTHON_BUILD_DATE=$date" >> "$GITHUB_ENV"
38+
echo "Resolved Python $SERIOUS_PYTHON_VERSION: full=$full, python-build date=$date"

.github/workflows/ci.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ jobs:
6060
path: '.fvmrc'
6161
cache: true
6262

63+
- name: Resolve Python version vars
64+
uses: ./.github/actions/resolve-python-vars
65+
6366
- name: Run tests
6467
working-directory: "src/serious_python/example/flet_example"
6568
run: |
@@ -86,6 +89,9 @@ jobs:
8689
path: '.fvmrc'
8790
cache: true
8891

92+
- name: Resolve Python version vars
93+
uses: ./.github/actions/resolve-python-vars
94+
8995
- name: Setup iOS Simulator
9096
id: simulator
9197
uses: futureware-tech/simulator-action@v4
@@ -123,6 +129,9 @@ jobs:
123129
path: '.fvmrc'
124130
cache: true
125131

132+
- name: Resolve Python version vars
133+
uses: ./.github/actions/resolve-python-vars
134+
126135
- name: Enable KVM
127136
run: |
128137
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
@@ -183,6 +192,9 @@ jobs:
183192
path: '.fvmrc'
184193
cache: true
185194

195+
- name: Resolve Python version vars
196+
uses: ./.github/actions/resolve-python-vars
197+
186198
- name: Run tests
187199
working-directory: "src/serious_python/example/flet_example"
188200
run: |
@@ -227,6 +239,9 @@ jobs:
227239
channel: ${{ matrix.arch == 'arm64' && 'master' || 'stable' }} # https://github.com/subosito/flutter-action/issues/345#issuecomment-2657332687
228240
cache: true
229241

242+
- name: Resolve Python version vars
243+
uses: ./.github/actions/resolve-python-vars
244+
230245
- name: Install dependencies
231246
run: |
232247
sudo apt-get update --allow-releaseinfo-change
@@ -285,6 +300,9 @@ jobs:
285300
path: '.fvmrc'
286301
cache: true
287302

303+
- name: Resolve Python version vars
304+
uses: ./.github/actions/resolve-python-vars
305+
288306
- name: Cache flet downloads
289307
uses: actions/cache@v4
290308
with:
@@ -324,6 +342,9 @@ jobs:
324342
path: '.fvmrc'
325343
cache: true
326344

345+
- name: Resolve Python version vars
346+
uses: ./.github/actions/resolve-python-vars
347+
327348
- name: Cache flet downloads
328349
uses: actions/cache@v4
329350
with:
@@ -373,6 +394,9 @@ jobs:
373394
path: '.fvmrc'
374395
cache: true
375396

397+
- name: Resolve Python version vars
398+
uses: ./.github/actions/resolve-python-vars
399+
376400
- name: Cache flet downloads
377401
uses: actions/cache@v4
378402
with:
@@ -446,6 +470,9 @@ jobs:
446470
path: '.fvmrc'
447471
cache: true
448472

473+
- name: Resolve Python version vars
474+
uses: ./.github/actions/resolve-python-vars
475+
449476
- name: Cache flet downloads
450477
uses: actions/cache@v4
451478
with:
@@ -505,6 +532,9 @@ jobs:
505532
channel: ${{ matrix.arch == 'arm64' && 'master' || 'stable' }}
506533
cache: true
507534

535+
- name: Resolve Python version vars
536+
uses: ./.github/actions/resolve-python-vars
537+
508538
- name: Cache flet downloads
509539
uses: actions/cache@v4
510540
with:
@@ -576,6 +606,9 @@ jobs:
576606
path: '.fvmrc'
577607
cache: true
578608

609+
- name: Resolve Python version vars
610+
uses: ./.github/actions/resolve-python-vars
611+
579612
- name: Package + run integration test
580613
working-directory: "src/serious_python/example/bridge_example"
581614
run: |
@@ -603,6 +636,9 @@ jobs:
603636
path: '.fvmrc'
604637
cache: true
605638

639+
- name: Resolve Python version vars
640+
uses: ./.github/actions/resolve-python-vars
641+
606642
# Replaced by the unified ~/.flet/cache step lifted to every job; left
607643
# the comment for git-blame breadcrumb. The bridge_example matrix is
608644
# gated off pending re-enable; once flipped back on, add the cache
@@ -655,6 +691,9 @@ jobs:
655691
path: '.fvmrc'
656692
cache: true
657693

694+
- name: Resolve Python version vars
695+
uses: ./.github/actions/resolve-python-vars
696+
658697
- name: Enable KVM
659698
run: |
660699
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
@@ -733,6 +772,9 @@ jobs:
733772
path: '.fvmrc'
734773
cache: true
735774

775+
- name: Resolve Python version vars
776+
uses: ./.github/actions/resolve-python-vars
777+
736778
- name: Package + run integration test
737779
working-directory: "src/serious_python/example/bridge_example"
738780
shell: bash
@@ -798,6 +840,9 @@ jobs:
798840
channel: ${{ matrix.arch == 'arm64' && 'master' || 'stable' }}
799841
cache: true
800842

843+
- name: Resolve Python version vars
844+
uses: ./.github/actions/resolve-python-vars
845+
801846
- name: Install Linux desktop build deps
802847
run: |
803848
sudo apt-get update --allow-releaseinfo-change

src/serious_python/bin/main.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import 'package:args/command_runner.dart';
22

33
import 'package_command.dart';
4+
import 'version_command.dart';
45

56
void main(List<String> arguments) async {
67
var runner = CommandRunner("dart run serious_python:main",
78
"A tool for packaging Python apps to work with serious_python package.")
8-
..addCommand(PackageCommand());
9+
..addCommand(PackageCommand())
10+
..addCommand(VersionCommand());
911

1012
await runner.run(arguments);
1113
}

src/serious_python/bin/package_command.dart

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ const pyodideVersionEnvironmentVariable = "SERIOUS_PYTHON_PYODIDE_VERSION";
3232

3333
const defaultPythonVersion = "3.14";
3434

35-
class _PythonRelease {
36-
const _PythonRelease({
35+
class PythonRelease {
36+
const PythonRelease({
3737
required this.standaloneVersion,
3838
required this.standaloneReleaseDate,
3939
required this.pythonBuildReleaseDate,
@@ -61,24 +61,24 @@ class _PythonRelease {
6161

6262
// Source of truth for the Python <-> CPython standalone <-> Pyodide mapping.
6363
// Mirror any change here in flet-cli's python_versions.py.
64-
const _pythonReleases = <String, _PythonRelease>{
65-
"3.12": _PythonRelease(
64+
const pythonReleases = <String, PythonRelease>{
65+
"3.12": PythonRelease(
6666
standaloneVersion: "3.12.13",
6767
standaloneReleaseDate: "20260610",
6868
pythonBuildReleaseDate: "20260611",
6969
pyodideVersion: "0.27.7",
7070
pyodidePlatformTag: "pyodide-2024.0-wasm32",
7171
prerelease: false,
7272
),
73-
"3.13": _PythonRelease(
73+
"3.13": PythonRelease(
7474
standaloneVersion: "3.13.14",
7575
standaloneReleaseDate: "20260610",
7676
pythonBuildReleaseDate: "20260611",
7777
pyodideVersion: "0.29.4",
7878
pyodidePlatformTag: "pyemscripten-2025.0-wasm32",
7979
prerelease: false,
8080
),
81-
"3.14": _PythonRelease(
81+
"3.14": PythonRelease(
8282
standaloneVersion: "3.14.6",
8383
standaloneReleaseDate: "20260610",
8484
pythonBuildReleaseDate: "20260611",
@@ -91,7 +91,7 @@ const _pythonReleases = <String, _PythonRelease>{
9191
// `requires-python = "==3.15.*"` on the Flet CLI side) without becoming
9292
// the default or matching open-ended `requires-python` specifiers.
9393
//
94-
// "3.15": _PythonRelease(
94+
// "3.15": PythonRelease(
9595
// standaloneVersion: "3.15.0",
9696
// standaloneReleaseDate: "...",
9797
// pythonBuildReleaseDate: "...",
@@ -125,7 +125,7 @@ const platforms = {
125125
},
126126
"Emscripten": {
127127
// The actual wheel platform tag is resolved per Python release from
128-
// `_pythonReleases[...].pyodidePlatformTag` (see sitecustomize wiring
128+
// `pythonReleases[...].pyodidePlatformTag` (see sitecustomize wiring
129129
// below) since it changes with each Pyodide ABI bump.
130130
"": {"tag": "", "mac_ver": ""}
131131
},
@@ -168,7 +168,7 @@ class PackageCommand extends Command {
168168
Directory? _buildDir;
169169
Directory? _pythonDir;
170170
late String _pythonShortVersion;
171-
late _PythonRelease _release;
171+
late PythonRelease _release;
172172

173173
String get _pyodideRootUrl =>
174174
"https://cdn.jsdelivr.net/pyodide/v${_release.pyodideVersion}/full";
@@ -200,7 +200,7 @@ class PackageCommand extends Command {
200200
mandatory: true,
201201
help: "Install dependencies for specific platform, e.g. 'Android'.");
202202
argParser.addOption('python-version',
203-
allowed: _pythonReleases.keys.toList(),
203+
allowed: pythonReleases.keys.toList(),
204204
help: "Short Python version to bundle (e.g. 3.13). Defaults to "
205205
"\$$pythonVersionEnvironmentVariable env var or "
206206
"'$defaultPythonVersion'.");
@@ -280,13 +280,13 @@ class PackageCommand extends Command {
280280
_pythonShortVersion = argResults?['python-version'] ??
281281
Platform.environment[pythonVersionEnvironmentVariable] ??
282282
defaultPythonVersion;
283-
final baseRelease = _pythonReleases[_pythonShortVersion];
283+
final baseRelease = pythonReleases[_pythonShortVersion];
284284
if (baseRelease == null) {
285285
stderr.writeln(
286-
"Unknown Python version: $_pythonShortVersion. Supported: ${_pythonReleases.keys.join(", ")}");
286+
"Unknown Python version: $_pythonShortVersion. Supported: ${pythonReleases.keys.join(", ")}");
287287
exit(2);
288288
}
289-
_release = _PythonRelease(
289+
_release = PythonRelease(
290290
standaloneVersion:
291291
Platform.environment[pythonFullVersionEnvironmentVariable] ??
292292
baseRelease.standaloneVersion,
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:args/command_runner.dart';
5+
import 'package:package_config/package_config.dart';
6+
7+
import 'package_command.dart';
8+
9+
/// `version` subcommand: prints the serious_python package version, the
10+
/// default Python version, and the supported-Python matrix sourced from
11+
/// [pythonReleases] in `package_command.dart`. With `--json`, emits a
12+
/// machine-readable document for CI / tooling consumption.
13+
class VersionCommand extends Command {
14+
@override
15+
final name = "version";
16+
17+
@override
18+
final description =
19+
"Print serious_python version and the supported Python matrix.";
20+
21+
VersionCommand() {
22+
argParser.addFlag(
23+
"json",
24+
help: "Emit machine-readable JSON instead of the human-readable summary.",
25+
negatable: false,
26+
);
27+
}
28+
29+
@override
30+
Future<void> run() async {
31+
final version = await _readSeriousPythonVersion() ?? "unknown";
32+
final jsonMode = argResults?["json"] ?? false;
33+
34+
if (jsonMode) {
35+
final doc = <String, dynamic>{
36+
"serious_python_version": version,
37+
"default_python_version": defaultPythonVersion,
38+
"python_releases": <String, dynamic>{
39+
for (final entry in pythonReleases.entries)
40+
entry.key: {
41+
"standalone_version": entry.value.standaloneVersion,
42+
"standalone_release_date": entry.value.standaloneReleaseDate,
43+
"python_build_release_date": entry.value.pythonBuildReleaseDate,
44+
"pyodide_version": entry.value.pyodideVersion,
45+
"pyodide_platform_tag": entry.value.pyodidePlatformTag,
46+
"prerelease": entry.value.prerelease,
47+
},
48+
},
49+
};
50+
stdout.writeln(const JsonEncoder.withIndent(" ").convert(doc));
51+
return;
52+
}
53+
54+
stdout.writeln("serious_python $version");
55+
stdout.writeln("Default Python: $defaultPythonVersion");
56+
stdout.writeln("Supported Python versions:");
57+
// Sort descending so the default + newest stable is on top, matching the
58+
// ordering convention used by `flet --version`.
59+
final keys = pythonReleases.keys.toList()
60+
..sort((a, b) => b.compareTo(a));
61+
for (final k in keys) {
62+
final r = pythonReleases[k]!;
63+
final markers = <String>[];
64+
if (k == defaultPythonVersion) markers.add("default");
65+
if (r.prerelease) markers.add("pre-release");
66+
final markerSuffix = markers.isEmpty ? "" : " (${markers.join(", ")})";
67+
stdout.writeln(
68+
" $k$markerSuffix: CPython ${r.standaloneVersion} / Pyodide ${r.pyodideVersion}");
69+
}
70+
}
71+
72+
/// Read this package's `version:` from its `pubspec.yaml`, found via the
73+
/// caller's `package_config.json`. Returns `null` if the package_config
74+
/// can't be located (e.g. invoked from a compiled snapshot outside any
75+
/// pub workspace).
76+
Future<String?> _readSeriousPythonVersion() async {
77+
final config = await findPackageConfig(Directory.current);
78+
final pkg = config?["serious_python"];
79+
if (pkg == null) return null;
80+
final pubspec = File.fromUri(pkg.root.resolve("pubspec.yaml"));
81+
if (!pubspec.existsSync()) return null;
82+
final versionPattern = RegExp(r'^version:\s*(\S+)\s*$');
83+
for (final line in pubspec.readAsLinesSync()) {
84+
final match = versionPattern.firstMatch(line);
85+
if (match != null) return match.group(1);
86+
}
87+
return null;
88+
}
89+
}

src/serious_python/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ dependencies:
5353
crypto: ^3.0.5
5454
glob: ^2.1.3
5555
ffi: ^2.1.2
56+
package_config: ^2.1.0
5657

5758
dev_dependencies:
5859
flutter_test:

0 commit comments

Comments
 (0)