更新
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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
@@ -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"]
|
||||
|
||||
|
||||
@@ -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" ;;
|
||||
|
||||
@@ -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,4 +2,5 @@
|
||||
|
||||
__all__ = ["__version__"]
|
||||
|
||||
__version__ = "0.1.0"
|
||||
# Single source of truth for runtime, package, installer, and executable versions.
|
||||
__version__ = "1.1.0"
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user