Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .acecode/skills/acecode-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ powershell -NoProfile -ExecutionPolicy Bypass `
-Repo .
```

To validate release arguments, the selected build directory, and the dynamically
calculated parallelism without building, packaging, touching Git, or contacting
the update server, add `-DryRun` (optionally with `-BuildJobs 3`). For quick
validation, `-DryRun -NoPublish` is accepted because no publish occurs:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
-File .acecode\skills\acecode-release\scripts\publish_acecode_release.ps1 `
-QuickValidation -Version 0.9.13-pre.1 -Repo . `
-DryRun -NoPublish -BuildJobs 3
```

With stable `0.8.6` and no existing `0.8.7-pre.N` record, this publishes `0.8.7-pre.1`. A later stable release should normally use the same numeric core, `0.8.7`.

## Seed Upgrade Compatibility (Required)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ param(
[switch]$SkipBuild,
[switch]$SkipTests,
[switch]$NoPublish,
[switch]$AllowDirtyBuild
[switch]$AllowDirtyBuild,
[string]$BuildDir = 'build/windows-x64-release',
[int]$BuildJobs = 0,
[switch]$DryRun
)

$ErrorActionPreference = 'Stop'
Expand Down Expand Up @@ -442,7 +445,7 @@ if (-not (Test-Path -LiteralPath (Join-Path $Repo 'CMakeLists.txt'))) {
throw "Not an ACECode repo root: $Repo"
}

$manifestPath = Join-Path $UpdateDir 'aceupdate.json'
$manifestPath = [System.IO.Path]::Combine($UpdateDir, 'aceupdate.json')
if ($QuickValidation) {
if ([string]::IsNullOrWhiteSpace($Version)) {
$Version = Get-NextQuickValidationVersion -RepoRoot $Repo -ManifestPath $manifestPath
Expand All @@ -453,7 +456,7 @@ if ($QuickValidation) {
if ($Push) {
throw 'Quick validation never pushes Git commits or tags. Remove -Push.'
}
if ($NoPublish) {
if ($NoPublish -and -not $DryRun) {
throw 'Quick validation must publish its Windows package. Remove -NoPublish.'
}
if ($Target -ne 'windows-x64') {
Expand Down Expand Up @@ -498,6 +501,51 @@ if ($QuickValidation) {
Write-Host "Repo: $Repo"
Write-Host "UpdateDir: $UpdateDir"

if ($DryRun) {
$dryRunBuildRoot = $BuildDir
if (-not [System.IO.Path]::IsPathRooted($dryRunBuildRoot)) {
$dryRunBuildRoot = Join-Path $Repo $dryRunBuildRoot
}
$dryRunBuildRoot = [System.IO.Path]::GetFullPath($dryRunBuildRoot)
$dryRunJobs = if ($BuildJobs -gt 0) { $BuildJobs } else { [Environment]::ProcessorCount }
if ($dryRunJobs -lt 1) {
throw 'BuildJobs must be a positive integer.'
}
$dryRunTargetList = if ($QuickValidation) {
@('acecode', 'acecode-desktop')
} else {
@('acecode', 'acecode-desktop', 'acecode_unit_tests')
}
$dryRunBuildCommand = "cmake --build `"$dryRunBuildRoot`" --config $Configuration --target $($dryRunTargetList -join ' ') -- -j $dryRunJobs"
Write-Host 'DRY RUN: no files, builds, Git state, package, or update server will be changed.'
Write-Host "BuildDir: $dryRunBuildRoot"
Write-Host "BuildJobs: $dryRunJobs"
Write-Host "Lock: $(Join-Path $dryRunBuildRoot '.acecode-build.lock') (not acquired)"
Write-Host "Version: $Version"
if (-not $SkipBuild) {
Write-Host "Would build:"
Write-Host " $dryRunBuildCommand"
} else {
Write-Host 'Would build: skipped because -SkipBuild was supplied.'
}
if (-not $QuickValidation -and -not $SkipTests) {
Write-Host "Would test:"
Write-Host " $dryRunBuildRoot\tests\$Configuration\acecode_unit_tests.exe --gtest_filter=Upgrade*:*ConfigUpgrade*"
}
if (-not $NoPublish) {
$dryRunPackage = Join-Path $dryRunBuildRoot "package\acecode-$Version-$Target"
Write-Host "Would package:"
Write-Host " stage binaries/resources under $dryRunPackage"
Write-Host " create package archive under $UpdateDir (no archive or manifest will be written)"
if ($RemoteBaseUrl) {
Write-Host " verify package URL: $RemoteBaseUrl"
}
} else {
Write-Host 'Would package: skipped because -NoPublish was supplied.'
}
exit 0
}

$dirtyBeforeVersionOverride = @()
$versionSnapshots = $null
if ($QuickValidation) {
Expand Down Expand Up @@ -546,21 +594,37 @@ try {
}
}

$buildRoot = $BuildDir
if (-not [System.IO.Path]::IsPathRooted($buildRoot)) {
$buildRoot = Join-Path $Repo $buildRoot
}
$buildRoot = [System.IO.Path]::GetFullPath($buildRoot)
$jobs = if ($BuildJobs -gt 0) { $BuildJobs } else { [Environment]::ProcessorCount }
if ($jobs -lt 1) {
throw 'BuildJobs must be a positive integer.'
}

$lockPath = Join-Path $buildRoot '.acecode-build.lock'
New-Item -ItemType Directory -Force -Path $buildRoot | Out-Null
$lockStream = [System.IO.File]::Open($lockPath, [System.IO.FileMode]::OpenOrCreate,
[System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
if (-not $SkipBuild) {
if ($QuickValidation) {
Invoke-Native cmake --build (Join-Path $Repo 'build') --config $Configuration --target acecode acecode-desktop
Invoke-Native cmake --build $buildRoot --config $Configuration --target acecode acecode-desktop -- -j $jobs
} else {
Invoke-Native cmake --build (Join-Path $Repo 'build') --config $Configuration --target acecode acecode-desktop acecode_unit_tests
Invoke-Native cmake --build $buildRoot --config $Configuration --target acecode acecode-desktop acecode_unit_tests -- -j $jobs
}
} else {
Write-Host 'Skipping build because -SkipBuild was supplied.'
}

if (-not $QuickValidation -and -not $SkipTests) {
$testExe = Join-Path $Repo "build\tests\$Configuration\acecode_unit_tests.exe"
$testExe = Join-Path $buildRoot "tests\$Configuration\acecode_unit_tests.exe"
Invoke-Native $testExe '--gtest_filter=Upgrade*:*ConfigUpgrade*'
}

$exe = Join-Path $Repo "build\$Configuration\acecode.exe"
$desktopExe = Join-Path $Repo "build\$Configuration\acecode-desktop.exe"
$exe = Join-Path $buildRoot "$Configuration\acecode.exe"
$desktopExe = Join-Path $buildRoot "$Configuration\acecode-desktop.exe"
$versionOutput = (& $exe --version).Trim()
if ($versionOutput -ne "acecode v$Version") {
throw "Built executable reports '$versionOutput', expected 'acecode v$Version'."
Expand Down Expand Up @@ -607,7 +671,7 @@ try {

if (-not $NoPublish) {
$pkgName = "acecode-$Version-$Target"
$packageRoot = Join-Path $Repo 'build\package'
$packageRoot = Join-Path $buildRoot 'package'
$stage = Join-Path $packageRoot $pkgName
$zipPath = Join-Path $UpdateDir "$pkgName.zip"

Expand Down Expand Up @@ -646,6 +710,9 @@ try {

$head = (& git -C $Repo rev-parse --short HEAD).Trim()
} finally {
if ($null -ne $lockStream) {
$lockStream.Dispose()
}
if ($QuickValidation -and $null -ne $versionSnapshots) {
Restore-FileSnapshots -Snapshots $versionSnapshots
}
Expand Down
26 changes: 17 additions & 9 deletions .acecode/skills/verify-package/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ system temp dir.

## Required Inputs

None. The script detects the repo root from its own location, defaults the
build directory to `<repo>/build`, and configures it if missing (MinSizeRel,
`BUILD_TESTING=OFF`, `ACECODE_BUILD_DESKTOP=ON`, Ninja when available, vcpkg
toolchain when `VCPKG_ROOT` is set).
None. The script detects the repo root from its own location and defaults
packaging verification to the isolated `<repo>/build/windows-x64-package`
build directory. Configure and build tasks hold an exclusive lock for that
directory, so a second task fails instead of corrupting CMake/Ninja state.

Prerequisite: `web/dist` must exist. If it is missing the script fails with
the exact rebuild command (`cd web && pnpm install --frozen-lockfile &&
Expand Down Expand Up @@ -56,19 +56,27 @@ python3 .../verify_package.py --skip-build
python3 .../verify_package.py --target tui
python3 .../verify_package.py --target desktop

# Existing non-default build tree (e.g. build/windows-x64-release)
python3 .../verify_package.py --build-dir build/windows-x64-release
# Existing non-default build tree (for example, an explicitly prepared release tree)
python3 .../verify_package.py --build-dir build/windows-x64-package

# Limit build parallelism (defaults to detected logical CPUs)
python3 .../verify_package.py --jobs 8

# Print the planned configure/build/install commands without changing files
python3 .../verify_package.py --dry-run
python3 .../verify_package.py --dry-run --target tui --jobs 3

# Where the staged package lands (default: <build-dir>/verify-package-staging)
python3 .../verify_package.py --staging-dir /tmp/ace-verify
```

## What The Script Does

1. Preflight: `web/dist/index.html` exists; cmake is on PATH; the build dir
is configured when `--skip-build` is used.
1. Preflight: `web/dist/index.html` exists; cmake is on PATH; the isolated
package build dir is configured when `--skip-build` is used.
2. Configure + incremental build of `acecode` (and `acecode-desktop` for the
desktop target). Skipped under `--skip-build`.
desktop target), using detected logical CPUs or `--jobs`. A per-build-dir
lock prevents concurrent CMake/Ninja/package operations.
3. Staging, mirroring the CI Package step: binaries (or the macOS
`ACECode.app` bundle) + READMEs, then
`cmake --install --component models_dev_registry` and
Expand Down
87 changes: 75 additions & 12 deletions .acecode/skills/verify-package/scripts/verify_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
import time
from pathlib import Path

REPO_ROOT_FOR_TOOLS = Path(__file__).resolve().parents[4]
if str(REPO_ROOT_FOR_TOOLS) not in sys.path:
sys.path.insert(0, str(REPO_ROOT_FOR_TOOLS))
from scripts.build_lock import BuildDirectoryBusy, build_directory_lock
MODELS_DEV_FILES = ("api.json", "MANIFEST.json", "LICENSE")
README_FILES = ("README.md", "README_CN.md")
BUILD_CONFIGS = ("MinSizeRel", "Release", "RelWithDebInfo", "Debug")
Expand Down Expand Up @@ -222,8 +226,49 @@ def preflight(report: Report, repo: Path, cmake: str | None, skip_build: bool,
return ok


def print_dry_run(repo: Path, build_dir: Path, staging: Path, platform: str,
targets: list[str], jobs: int, cmake: str | None,
skip_build: bool) -> None:
"""Describe package verification without creating files or running processes."""
cmake_command = cmake or "cmake"
print("DRY RUN: no commands will be executed and no files will be changed.")
print(f"Repository: {repo}")
print(f"Build directory: {build_dir}")
print(f"Staging directory: {staging}")
print(f"Lock: {build_dir / '.acecode-build.lock'} (not acquired)")
print(f"Platform: {platform}")
print(f"Jobs: {jobs}")
if skip_build:
print("Would reuse the existing build tree (--skip-build).")
else:
configure = [cmake_command, "-S", str(repo), "-B", str(build_dir),
"-DCMAKE_BUILD_TYPE=MinSizeRel", "-DBUILD_TESTING=OFF",
"-DACECODE_BUILD_DESKTOP=ON"]
if platform != "windows" and shutil.which("ninja"):
configure[4:4] = ["-G", "Ninja"]
vcpkg_root = os.environ.get("VCPKG_ROOT")
if vcpkg_root:
configure.append(
f"-DCMAKE_TOOLCHAIN_FILE={Path(vcpkg_root) / 'scripts' / 'buildsystems' / 'vcpkg.cmake'}")
print("Would run:")
print(" " + " ".join(configure))
for target in targets:
print(" " + " ".join([
cmake_command, "--build", str(build_dir), "--config", "MinSizeRel",
"--target", CMAKE_TARGETS[target], "--", "-j", str(jobs),
]))
print("Would stage package files and run structural/runtime checks.")
if "tui" in targets or platform != "darwin":
for component in ("models_dev_registry", "default_seed_bundle"):
print(" " + " ".join([
cmake_command, "--install", str(build_dir), "--config", "MinSizeRel",
"--prefix", str(staging), "--component", component,
]))
print("Desktop/TUI runtime probes would be skipped.")


def configure_and_build(report: Report, repo: Path, build_dir: Path, cmake: str,
targets: list[str], platform: str) -> bool:
targets: list[str], platform: str, jobs: int) -> bool:
if not (build_dir / "CMakeCache.txt").is_file():
command = [cmake, "-S", str(repo), "-B", str(build_dir),
"-DCMAKE_BUILD_TYPE=MinSizeRel", "-DBUILD_TESTING=OFF",
Expand All @@ -245,7 +290,7 @@ def configure_and_build(report: Report, repo: Path, build_dir: Path, cmake: str,
cmake_target = CMAKE_TARGETS[target]
if not run_tool(report, f"cmake build {cmake_target}",
[cmake, "--build", str(build_dir), "--config", "MinSizeRel",
"--target", cmake_target]):
"--target", cmake_target, "--", "-j", str(jobs)]):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] 使用 CMake 通用并行参数,避免 Windows/MSBuild 构建必然失败。此函数在 Windows 明确保留 Visual Studio 生成器,但新增的 -- -j <jobs> 会原样传给 MSBuild;本机实际运行得到 MSBUILD : error MSB1001: 未知开关 -j。发布脚本新增的构建命令也有同样问题。建议统一改成 CMake 自身的 --parallel <jobs>,同步四份 verifier 和发布脚本,并增加 Visual Studio 生成器验证。当前单测只断言参数包含 -j,因此没有发现真实构建失败。

return False
return True

Expand Down Expand Up @@ -484,13 +529,17 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
help="which artifact set to verify (default: all)")
parser.add_argument("--skip-build", action="store_true",
help="reuse the existing build tree; stage and verify only")
parser.add_argument("--dry-run", action="store_true",
help="print planned commands without executing or changing files")
parser.add_argument("--platform", choices=("auto", "darwin", "windows", "linux"),
default="auto",
help="override platform detection (mainly for tests)")
parser.add_argument("--repo", type=Path, default=None,
help="ACECode repo root (default: detected from this script)")
parser.add_argument("--build-dir", type=Path, default=None,
help="CMake build directory (default: <repo>/build)")
help="CMake build directory (default: build/windows-x64-package)")
parser.add_argument("--jobs", type=int, default=None,
help="parallel build jobs (default: detected logical CPUs)")
parser.add_argument("--staging-dir", type=Path, default=None,
help="staging output directory "
"(default: <build-dir>/verify-package-staging)")
Expand All @@ -504,26 +553,40 @@ def main(argv: list[str]) -> int:
args = parse_args(argv)
report = Report()
repo = (args.repo or find_repo_root(Path(__file__).resolve())).resolve()
build_dir = (args.build_dir or repo / "build").resolve()
build_dir = (args.build_dir or repo / "build" / "windows-x64-package").resolve()
staging = (args.staging_dir or build_dir / STAGING_DIRNAME).resolve()
platform = detect_platform(args.platform)
jobs = args.jobs if args.jobs is not None else os.cpu_count() or 1
if jobs < 1:
print("verify-package: --jobs must be a positive integer", file=sys.stderr)
return 2
cmake = shutil.which("cmake")
targets = ["tui", "desktop"] if args.target == "all" else [args.target]
print(f"verify-package: repo={repo} build={build_dir} platform={platform} "
f"target={args.target} skip-build={args.skip_build}")

if args.dry_run:
print_dry_run(repo, build_dir, staging, platform, targets, jobs, cmake,
args.skip_build)
return 0

if not preflight(report, repo, cmake, args.skip_build, build_dir):
print(f"verify-package: FAIL ({report.failed} check(s) failed)")
return 1

if not args.skip_build:
assert cmake is not None
if not configure_and_build(
report, repo, build_dir, cmake, targets, platform):
print(f"verify-package: FAIL ({report.failed} check(s) failed)")
return 1

if not stage(report, repo, build_dir, staging, platform, targets, cmake or "cmake"):
try:
with build_directory_lock(build_dir):
if not args.skip_build:
assert cmake is not None
if not configure_and_build(
report, repo, build_dir, cmake, targets, platform, jobs):
print(f"verify-package: FAIL ({report.failed} check(s) failed)")
return 1
if not stage(report, repo, build_dir, staging, platform, targets, cmake or "cmake"):
print(f"verify-package: FAIL ({report.failed} check(s) failed)")
return 1
except BuildDirectoryBusy as error:
report.add("build directory safety", "fail", str(error))
print(f"verify-package: FAIL ({report.failed} check(s) failed)")
return 1

Expand Down
Loading
Loading