This commit is contained in:
Your Name
2026-08-27 14:54:18 +08:00
parent 2fa8492c56
commit 74ff568ba4
8 changed files with 89 additions and 52 deletions
+40 -2
View File
@@ -11,6 +11,7 @@ their platform media backends.
"""
import os
import re
import sys
from pathlib import Path
@@ -23,9 +24,46 @@ RESOURCES = PROJECT_ROOT / "resources"
WINDOWS_ICON = RESOURCES / "branding" / "app-icon.ico"
MACOS_ICON = RESOURCES / "branding" / "app-icon.icns"
ENTITLEMENTS = PROJECT_ROOT / "packaging" / "macos" / "entitlements.plist"
VERSION_FILE = PROJECT_ROOT / "packaging" / "windows" / "version_info.txt"
VERSION_SOURCE = SOURCE_ROOT / "doctor_workstation" / "__init__.py"
VERSION_TEMPLATE = PROJECT_ROOT / "packaging" / "windows" / "version_info.template.txt"
MEDIA_SMOKE_HOOK = PROJECT_ROOT / "packaging" / "runtime_media_smoke.py"
def read_application_version():
source = VERSION_SOURCE.read_text(encoding="utf-8")
match = re.search(r'^__version__\s*=\s*["\']([^"\']+)["\']', source, re.MULTILINE)
if not match:
raise SystemExit(f"Application version is missing from {VERSION_SOURCE}")
return match.group(1)
def windows_version_tuple(version):
match = re.match(r"^(\d+(?:\.\d+){0,3})", version)
if not match:
raise SystemExit(f"Application version is invalid for Windows resources: {version}")
parts = [int(part) for part in match.group(1).split(".")]
return tuple((parts + [0, 0, 0, 0])[:4])
def generate_windows_version_file(version):
template = VERSION_TEMPLATE.read_text(encoding="utf-8")
version_tuple = ", ".join(str(part) for part in windows_version_tuple(version))
rendered = template.replace("@VERSION_TUPLE@", version_tuple)
rendered = rendered.replace("@VERSION_STRING@", version)
output = PROJECT_ROOT / "build" / "generated" / "version_info.txt"
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(rendered, encoding="utf-8")
return output
APP_VERSION = read_application_version()
if sys.platform == "win32":
if not VERSION_TEMPLATE.is_file():
raise SystemExit(f"Windows version template is missing: {VERSION_TEMPLATE}")
VERSION_FILE = generate_windows_version_file(APP_VERSION)
else:
VERSION_FILE = None
if not ENTRY_POINT.is_file():
raise SystemExit(f"Application entry point is missing: {ENTRY_POINT}")
if not (VIDEO_DIST / "index.html").is_file():
@@ -107,7 +145,7 @@ exe = EXE(
icon=str(WINDOWS_ICON) if sys.platform == "win32" else None,
codesign_identity=os.environ.get("MACOS_CODESIGN_IDENTITY") if is_macos else None,
entitlements_file=str(ENTITLEMENTS) if is_macos else None,
version=str(VERSION_FILE) if sys.platform == "win32" else None,
version=str(VERSION_FILE) if VERSION_FILE else None,
)
collection = COLLECT(
-31
View File
@@ -1,31 +0,0 @@
# UTF-8
# Example PyInstaller version resource. Update all four version tuples together.
VSVersionInfo(
ffi=FixedFileInfo(
filevers=(0, 1, 0, 0),
prodvers=(0, 1, 0, 0),
mask=0x3f,
flags=0x0,
OS=0x40004,
fileType=0x1,
subtype=0x0,
date=(0, 0)
),
kids=[
StringFileInfo([
StringTable(
'080404B0',
[
StringStruct('CompanyName', 'ZYT'),
StringStruct('FileDescription', '医生工作台'),
StringStruct('FileVersion', '0.1.0.0'),
StringStruct('InternalName', 'DoctorWorkstation'),
StringStruct('OriginalFilename', 'DoctorWorkstation.exe'),
StringStruct('ProductName', '医生工作台'),
StringStruct('ProductVersion', '0.1.0.0')
]
)
]),
VarFileInfo([VarStruct('Translation', [2052, 1200])])
]
)
+4 -2
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "zhenyang-doctor-workstation"
version = "1.0.0"
dynamic = ["version"]
description = "Cross-platform doctor consultation workstation for Windows and macOS"
readme = "README.md"
requires-python = ">=3.11"
@@ -31,6 +31,9 @@ build = [
[project.scripts]
doctor-workstation = "doctor_workstation.app:main"
[tool.hatch.version]
path = "src/doctor_workstation/__init__.py"
[tool.hatch.build.targets.wheel]
packages = ["src/doctor_workstation"]
@@ -46,4 +49,3 @@ target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]
ignore = ["E501"]
+3 -3
View File
@@ -131,9 +131,9 @@ PYINSTALLER_PYTHON="$python_bin" SKIP_FRONTEND_INSTALL=1 \
/bin/bash "$script_dir/build_macos.sh"
artifact="$project_root/dist/DoctorWorkstation.app"
project_version="$(/usr/bin/awk -F '"' '/^version = "/ { print $2; exit }' \
"$project_root/pyproject.toml")"
[[ -n "$project_version" ]] || macos_die "无法从 pyproject.toml 读取版本号。"
project_version="$(/usr/bin/awk -F '"' '/^__version__ = "/ { print $2; exit }' \
"$project_root/src/doctor_workstation/__init__.py")"
[[ -n "$project_version" ]] || macos_die "无法从 doctor_workstation.__version__ 读取版本号。"
case "$(uname -m)" in
arm64) release_arch="arm64" ;;
x86_64) release_arch="x64" ;;
+12 -11
View File
@@ -20,7 +20,7 @@ $ReleaseLauncher = Join-Path $DistributionRoot "Start_DoctorWorkstation.bat"
$InstallerDefinition = Join-Path $ProjectRoot "packaging\windows\doctor_workstation.iss"
$EnsureInstallerCompiler = Join-Path $PSScriptRoot "ensure_inno_setup.ps1"
$InstallerMessagesFile = Join-Path $ProjectRoot ".build-tools\inno-languages\ChineseSimplified.isl"
$ProjectMetadata = Join-Path $ProjectRoot "pyproject.toml"
$VersionSource = Join-Path $ProjectRoot "src\doctor_workstation\__init__.py"
$MediaSmokeHook = Join-Path $ProjectRoot "packaging\runtime_media_smoke.py"
$WindowsIcon = Join-Path $ProjectRoot "resources\branding\app-icon.ico"
@@ -53,7 +53,7 @@ try {
$BuildScript,
$PackageLock,
(Join-Path $ProjectRoot "uv.lock"),
$ProjectMetadata,
$VersionSource,
$MediaSmokeHook,
$WindowsIcon,
$ReleaseLauncherTemplate,
@@ -65,6 +65,16 @@ try {
}
}
$VersionText = [System.IO.File]::ReadAllText($VersionSource)
$VersionMatch = [regex]::Match(
$VersionText,
'(?m)^\s*__version__\s*=\s*"([^"]+)"'
)
if (-not $VersionMatch.Success) {
throw "Unable to read the application version from $VersionSource"
}
$ProjectVersion = $VersionMatch.Groups[1].Value
$Npm = Find-Application -Name "npm.cmd"
$Node = Find-Application -Name "node.exe"
if (-not $Npm -or -not $Node) {
@@ -156,15 +166,6 @@ try {
throw "Build completed without the expected executable: $Executable"
}
$ProjectText = [System.IO.File]::ReadAllText($ProjectMetadata)
$VersionMatch = [regex]::Match(
$ProjectText,
'(?m)^\s*version\s*=\s*"([^"]+)"'
)
if (-not $VersionMatch.Success) {
throw "Unable to read the project version from $ProjectMetadata"
}
$ProjectVersion = $VersionMatch.Groups[1].Value
$ReleaseZip = Join-Path $DistributionRoot (
"DoctorWorkstation-Windows-x64-$ProjectVersion.zip"
)
+2 -1
View File
@@ -2,4 +2,5 @@
__all__ = ["__version__"]
__version__ = "0.1.0"
# Single source of truth for runtime, package, installer, and executable versions.
__version__ = "1.1.0"
+27
View File
@@ -1,7 +1,10 @@
from __future__ import annotations
import tomllib
from pathlib import Path
from doctor_workstation import __version__
PROJECT_ROOT = Path(__file__).resolve().parents[1]
@@ -9,6 +12,30 @@ def read(relative_path: str) -> str:
return (PROJECT_ROOT / relative_path).read_text(encoding="utf-8")
def test_application_version_has_one_source() -> None:
pyproject = tomllib.loads(read("pyproject.toml"))
project = pyproject["project"]
assert "version" not in project
assert "version" in project["dynamic"]
assert pyproject["tool"]["hatch"]["version"]["path"] == (
"src/doctor_workstation/__init__.py"
)
assert __version__
version_template = read("packaging/windows/version_info.template.txt")
assert "@VERSION_TUPLE@" in version_template
assert "@VERSION_STRING@" in version_template
assert "0.1.0" not in version_template
spec = read("packaging/doctor_workstation.spec")
windows_package_script = read("scripts/package_windows.ps1")
macos_package_script = read("scripts/package_macos.sh")
assert 'VERSION_SOURCE = SOURCE_ROOT / "doctor_workstation" / "__init__.py"' in spec
assert 'src\\doctor_workstation\\__init__.py' in windows_package_script
assert 'src/doctor_workstation/__init__.py' in macos_package_script
def test_windows_one_click_entrypoints_and_release_pipeline() -> None:
for name in (
"Run_DoctorWorkstation.bat",
Generated
+1 -2
View File
@@ -746,7 +746,6 @@ wheels = [
[[package]]
name = "zhenyang-doctor-workstation"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "httpx" },
@@ -778,7 +777,7 @@ requires-dist = [
{ name = "python-dotenv", specifier = ">=1.0.1,<2" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9,<1" },
]
provides-extras = ["dev", "build"]
provides-extras = ["build", "dev"]
[[package]]
name = "zipp"