diff --git a/.gitignore b/.gitignore index 6bdfd7b..9613505 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ __pycache__/ # Distribution / packaging .Python build/ +release/ develop-eggs/ dist/ downloads/ diff --git a/packaging/default_config/app_config.json b/packaging/default_config/app_config.json new file mode 100644 index 0000000..d587008 --- /dev/null +++ b/packaging/default_config/app_config.json @@ -0,0 +1,7 @@ +{ + "output_format": "PNG", + "output_quality": 95, + "output_dir": "", + "last_garment_dir": "", + "last_print_dir": "" +} diff --git a/packaging/default_config/templates.json b/packaging/default_config/templates.json new file mode 100644 index 0000000..da024f7 --- /dev/null +++ b/packaging/default_config/templates.json @@ -0,0 +1,3 @@ +{ + "templates": [] +} diff --git a/scripts/build.ps1 b/scripts/build.ps1 new file mode 100644 index 0000000..56e5344 --- /dev/null +++ b/scripts/build.ps1 @@ -0,0 +1,123 @@ +param( + [switch]$SkipBuild +) + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$RootDir = Split-Path -Parent $ScriptDir +$SrcDir = Join-Path $RootDir "src" +$VersionFile = Join-Path $SrcDir "version.py" +$DefaultConfigDir = Join-Path $RootDir "packaging\default_config" +$BuildDir = Join-Path $RootDir "build" +$DistDir = Join-Path $RootDir "dist" +$ReleaseRoot = Join-Path $RootDir "release" +$AppExeName = "AutoPrint" + +function Read-VersionValue { + param( + [string]$Path, + [string]$Name + ) + + $pattern = "^\s*$Name\s*=\s*`"([^`"]+)`"" + foreach ($line in Get-Content -LiteralPath $Path -Encoding UTF8) { + if ($line -match $pattern) { + return $Matches[1] + } + } + throw "Cannot find $Name in $Path" +} + +function Assert-InProject { + param([string]$Path) + + $resolvedRoot = (Resolve-Path -LiteralPath $RootDir).Path + if (Test-Path -LiteralPath $Path) { + $resolvedPath = (Resolve-Path -LiteralPath $Path).Path + if (-not $resolvedPath.StartsWith($resolvedRoot, [System.StringComparison]::OrdinalIgnoreCase)) { + throw "Refusing to modify path outside project: $resolvedPath" + } + } +} + +$AppName = Read-VersionValue -Path $VersionFile -Name "APP_NAME" +$AppVersion = Read-VersionValue -Path $VersionFile -Name "APP_VERSION" +$ReleaseName = "$AppExeName-$AppVersion" +$ReleaseDir = Join-Path $ReleaseRoot $ReleaseName +$PyInstallerOutputDir = Join-Path $DistDir $AppExeName +$ExePath = Join-Path $ReleaseDir "$AppExeName.exe" + +Write-Host "Building $AppName $AppVersion" + +$pythonVersionText = & python --version +if ($LASTEXITCODE -ne 0) { + throw "Python is not available." +} +if ($pythonVersionText -notmatch "Python 3\.7\.") { + throw "Python 3.7 is required, current version: $pythonVersionText" +} + +& python -c "import PyInstaller" +if ($LASTEXITCODE -ne 0) { + throw "PyInstaller is not installed. Run: pip install -r requirements.txt" +} + +if (-not (Test-Path -LiteralPath $DefaultConfigDir)) { + throw "Default config directory not found: $DefaultConfigDir" +} + +New-Item -ItemType Directory -Force -Path $ReleaseRoot | Out-Null + +foreach ($path in @($BuildDir, $PyInstallerOutputDir, $ReleaseDir)) { + Assert-InProject -Path $path + if (Test-Path -LiteralPath $path) { + Remove-Item -LiteralPath $path -Recurse -Force + } +} + +if (-not $SkipBuild) { + & python -m PyInstaller ` + --noconfirm ` + --clean ` + --onedir ` + --windowed ` + --name $AppExeName ` + --paths $SrcDir ` + --add-data "$SrcDir\resources;resources" ` + "$SrcDir\main.py" + if ($LASTEXITCODE -ne 0) { + throw "PyInstaller build failed." + } +} + +if (-not (Test-Path -LiteralPath $PyInstallerOutputDir)) { + throw "PyInstaller output not found: $PyInstallerOutputDir" +} + +Copy-Item -LiteralPath $PyInstallerOutputDir -Destination $ReleaseDir -Recurse +Copy-Item -LiteralPath $DefaultConfigDir -Destination (Join-Path $ReleaseDir "config") -Recurse +New-Item -ItemType Directory -Force -Path (Join-Path $ReleaseDir "logs") | Out-Null +New-Item -ItemType Directory -Force -Path (Join-Path $ReleaseDir "output") | Out-Null + +$ReadmePath = Join-Path $ReleaseDir "README.txt" +$Readme = @( + "$AppName $AppVersion", + "", + "Start:", + "Double-click $AppExeName.exe.", + "", + "Folders:", + "- config: default config and custom templates.", + "- logs: runtime logs.", + "- output: default export folder.", + "", + "Do not delete dependency files in this folder." +) +Set-Content -LiteralPath $ReadmePath -Value $Readme -Encoding UTF8 + +if (-not (Test-Path -LiteralPath $ExePath)) { + throw "Executable not found: $ExePath" +} + +Write-Host "Release created: $ReleaseDir" diff --git a/tasks.md b/tasks.md index a17c527..695762a 100644 --- a/tasks.md +++ b/tasks.md @@ -528,18 +528,18 @@ 任务: -- [ ] 创建打包脚本 -- [ ] 验证 PyInstaller `onedir` 打包 -- [ ] 打包资源文件 -- [ ] 打包默认配置和模板 -- [ ] 验证打包后程序启动 -- [ ] 验证标题栏版本号 -- [ ] 验证单张导出 +- [x] 创建打包脚本 +- [x] 验证 PyInstaller `onedir` 打包 +- [x] 打包资源文件 +- [x] 打包默认配置和模板 +- [x] 验证打包后程序启动 +- [x] 验证标题栏版本号 +- [x] 验证单张导出 验收: - [ ] 打包产物可在无 Python 环境的 Windows 电脑启动 -- [ ] 发布包不包含源代码、缓存、测试输出和私人配置 +- [x] 发布包不包含源代码、缓存、测试输出和私人配置 ## 17. 后续暂缓任务