From 10c510482fcae1159a86a6ca879340170c952d86 Mon Sep 17 00:00:00 2001 From: Chen Date: Fri, 17 Jul 2026 01:54:44 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(xcode):=20=E5=BD=BB=E5=BA=95=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E9=A1=B9=E7=9B=AE=E8=87=AA=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 Xcode 工程迁移到仓库根目录,修正同步根组与 SwiftPM 路径 - 更新构建、测试、发布与 CI 路径,兼容首次重命名后的版本比较 - 添加静态守卫与发布路径 fixture,防止自引用和旧路径回归 --- .../workflows/_reusable-ui-smoke-tests.yml | 2 +- .github/workflows/ci.yml | 1 + .github/workflows/nightly.yml | 2 +- .github/workflows/release.yml | 2 +- UITests/VoidDisplayTests.xctestplan | 4 +- .../project.pbxproj | 22 ++++---- .../contents.xcworkspacedata | 0 .../xcshareddata/swiftpm/Package.resolved | 0 .../xcschemes/VoidDisplay.xcscheme | 0 .../contents.xcworkspacedata | 2 +- docs/ci-final-plan.md | 2 +- docs/display-runtime-phase-1-plan.md | 6 +-- docs/display-runtime-refactor-plan.md | 2 +- .../macos-syspolicy-startup-crash-playbook.md | 2 +- scripts/build-relay.sh | 2 +- scripts/ci/classification-rules.json | 8 +-- scripts/ci/release_smoke.sh | 2 +- scripts/ci/static_project.sh | 54 +++++++++++++++++-- scripts/ci/test_classify.sh | 5 +- scripts/ci/test_release_project_paths.sh | 41 ++++++++++++++ scripts/ci/ui_smoke.sh | 2 +- scripts/ci/xcode.sh | 2 +- scripts/dev/doctor.sh | 2 +- scripts/lib/release.sh | 34 ++++++++++-- scripts/release/prepare.sh | 2 +- 25 files changed, 159 insertions(+), 42 deletions(-) rename {Apps/VoidDisplay/VoidDisplay.xcodeproj => VoidDisplay.xcodeproj}/project.pbxproj (98%) rename {Apps/VoidDisplay/VoidDisplay.xcodeproj => VoidDisplay.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (100%) rename {Apps/VoidDisplay/VoidDisplay.xcodeproj => VoidDisplay.xcodeproj}/project.xcworkspace/xcshareddata/swiftpm/Package.resolved (100%) rename {Apps/VoidDisplay/VoidDisplay.xcodeproj => VoidDisplay.xcodeproj}/xcshareddata/xcschemes/VoidDisplay.xcscheme (100%) create mode 100755 scripts/ci/test_release_project_paths.sh diff --git a/.github/workflows/_reusable-ui-smoke-tests.yml b/.github/workflows/_reusable-ui-smoke-tests.yml index 103828ba..e472f927 100644 --- a/.github/workflows/_reusable-ui-smoke-tests.yml +++ b/.github/workflows/_reusable-ui-smoke-tests.yml @@ -79,7 +79,7 @@ jobs: path: | .ai-tmp/ci-ui-smoke/DerivedData/SourcePackages .ai-tmp/ci-ui-smoke/DerivedData/ModuleCache.noindex - key: xcode-meta-ui-${{ runner.os }}-${{ hashFiles('Apps/VoidDisplay/VoidDisplay.xcodeproj/**', '**/Package.resolved') }} + key: xcode-meta-ui-${{ runner.os }}-${{ hashFiles('VoidDisplay.xcodeproj/**', '**/Package.resolved') }} restore-keys: | xcode-meta-ui-${{ runner.os }}- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f25e16e1..b3d16108 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,7 @@ on: - 'Tests/**' - 'UITests/**' - 'Apps/VoidDisplay/**' + - 'VoidDisplay.xcodeproj/**' - 'Tools/VoidDisplayRelay/**' - 'Package.swift' - 'Package.resolved' diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a8b80deb..c8e857d5 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -167,7 +167,7 @@ jobs: shell: bash run: | set -euo pipefail - version="$(sed -nE 's/^[[:space:]]*MARKETING_VERSION = ([^;]+);/\1/p' Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj | sort -u)" + version="$(sed -nE 's/^[[:space:]]*MARKETING_VERSION = ([^;]+);/\1/p' VoidDisplay.xcodeproj/project.pbxproj | sort -u)" test -n "$version" tool_root="$GITHUB_WORKSPACE" ROOT_DIR="$GITHUB_WORKSPACE" TOOL_ROOT="$tool_root" "$tool_root/scripts/release/build.sh" \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 247d2d67..50a185a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ on: branches: - main paths: - - 'Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj' + - 'VoidDisplay.xcodeproj/project.pbxproj' - 'Tools/VoidDisplayRelay/**' - 'scripts/build-relay.sh' - 'scripts/ci/**' diff --git a/UITests/VoidDisplayTests.xctestplan b/UITests/VoidDisplayTests.xctestplan index 7c9f532a..edf91691 100644 --- a/UITests/VoidDisplayTests.xctestplan +++ b/UITests/VoidDisplayTests.xctestplan @@ -10,7 +10,7 @@ ], "defaultOptions" : { "targetForVariableExpansion" : { - "containerPath" : "container:../Apps/VoidDisplay/VoidDisplay.xcodeproj", + "containerPath" : "container:../VoidDisplay.xcodeproj", "identifier" : "BA0465CF2E90E53C00012958", "name" : "VoidDisplay" } @@ -18,7 +18,7 @@ "testTargets" : [ { "target" : { - "containerPath" : "container:../Apps/VoidDisplay/VoidDisplay.xcodeproj", + "containerPath" : "container:../VoidDisplay.xcodeproj", "identifier" : "BA0465E62E90E53D00012958", "name" : "VoidDisplayUITests" } diff --git a/Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj b/VoidDisplay.xcodeproj/project.pbxproj similarity index 98% rename from Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj rename to VoidDisplay.xcodeproj/project.pbxproj index 72302908..4c0e1367 100644 --- a/Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj +++ b/VoidDisplay.xcodeproj/project.pbxproj @@ -29,25 +29,25 @@ BA0465D22E90E53C00012958 /* VoidDisplay */ = { isa = PBXFileSystemSynchronizedRootGroup; name = VoidDisplay; - path = .; + path = Apps/VoidDisplay; sourceTree = ""; }; BA0465EA2E90E53D00012958 /* VoidDisplayUITests */ = { isa = PBXFileSystemSynchronizedRootGroup; name = VoidDisplayUITests; - path = ../../UITests/VoidDisplayUITests; + path = UITests/VoidDisplayUITests; sourceTree = ""; }; CDAA10012F01A00100000001 /* Sources */ = { isa = PBXFileSystemSynchronizedRootGroup; name = Sources; - path = ../../Sources; + path = Sources; sourceTree = ""; }; CDAA10022F01A00100000001 /* Tests */ = { isa = PBXFileSystemSynchronizedRootGroup; name = Tests; - path = ../../Tests; + path = Tests; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ @@ -163,7 +163,7 @@ mainGroup = BA0465C72E90E53C00012958; minimizedProjectReferenceProxies = 1; packageReferences = ( - 837EFB7AC90CC3286DF0B571 /* XCLocalSwiftPackageReference "../.." */, + 837EFB7AC90CC3286DF0B571 /* XCLocalSwiftPackageReference "." */, ); preferredProjectObjectVersion = 90; productRefGroup = BA0465D12E90E53C00012958 /* Products */; @@ -212,7 +212,7 @@ ); shellPath = /bin/bash; shellScript = ( - "cd \"$SRCROOT/../..\"", + "cd \"$SRCROOT\"", "export ROOT_DIR=\"${ROOT_DIR:-$PWD}\"", "export TOOL_ROOT=\"${TOOL_ROOT:-$ROOT_DIR}\"", "\"$TOOL_ROOT/scripts/build-relay.sh\"", @@ -425,7 +425,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.developerchen.voiddisplay; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; - ROOT_DIR = "$(SRCROOT)/../.."; + ROOT_DIR = "$(SRCROOT)"; SDKROOT = auto; STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = macosx; @@ -494,7 +494,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.developerchen.voiddisplay; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; - ROOT_DIR = "$(SRCROOT)/../.."; + ROOT_DIR = "$(SRCROOT)"; SDKROOT = auto; STRING_CATALOG_GENERATE_SYMBOLS = YES; SUPPORTED_PLATFORMS = macosx; @@ -609,16 +609,16 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 837EFB7AC90CC3286DF0B571 /* XCLocalSwiftPackageReference "../.." */ = { + 837EFB7AC90CC3286DF0B571 /* XCLocalSwiftPackageReference "." */ = { isa = XCLocalSwiftPackageReference; - relativePath = ../..; + relativePath = .; }; /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 9FED0F7AAD6710D42320E832 /* VoidDisplayApp */ = { isa = XCSwiftPackageProductDependency; - package = 837EFB7AC90CC3286DF0B571 /* XCLocalSwiftPackageReference "../.." */; + package = 837EFB7AC90CC3286DF0B571 /* XCLocalSwiftPackageReference "." */; productName = VoidDisplayApp; }; /* End XCSwiftPackageProductDependency section */ diff --git a/Apps/VoidDisplay/VoidDisplay.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/VoidDisplay.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Apps/VoidDisplay/VoidDisplay.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to VoidDisplay.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/Apps/VoidDisplay/VoidDisplay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/VoidDisplay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved similarity index 100% rename from Apps/VoidDisplay/VoidDisplay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved rename to VoidDisplay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Apps/VoidDisplay/VoidDisplay.xcodeproj/xcshareddata/xcschemes/VoidDisplay.xcscheme b/VoidDisplay.xcodeproj/xcshareddata/xcschemes/VoidDisplay.xcscheme similarity index 100% rename from Apps/VoidDisplay/VoidDisplay.xcodeproj/xcshareddata/xcschemes/VoidDisplay.xcscheme rename to VoidDisplay.xcodeproj/xcshareddata/xcschemes/VoidDisplay.xcscheme diff --git a/VoidDisplay.xcworkspace/contents.xcworkspacedata b/VoidDisplay.xcworkspace/contents.xcworkspacedata index 84f852b4..32c3a2c9 100644 --- a/VoidDisplay.xcworkspace/contents.xcworkspacedata +++ b/VoidDisplay.xcworkspace/contents.xcworkspacedata @@ -2,7 +2,7 @@ + location = "group:VoidDisplay.xcodeproj"> diff --git a/docs/ci-final-plan.md b/docs/ci-final-plan.md index 8dc19230..16645784 100644 --- a/docs/ci-final-plan.md +++ b/docs/ci-final-plan.md @@ -138,7 +138,7 @@ Nightly 失败不应直接阻断普通 PR,但必须在仓库首页、issue 或 Release workflow 支持两种入口。workflow 先不 checkout 目标代码,只用 GitHub API 解析目标 SHA,并用内联 GitHub API 逻辑验证目标 SHA 的 `ci-gate`。验证成功后,workflow 才 checkout 目标 commit 并执行 release 脚本。版本判断、tag 判断、build number 规则都由 `scripts/release/prepare.sh` 执行。`prepare` 输出 `should_release=true` 后才允许 build 和 publish。 -第一种是 push 到 `main`。当 `Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj` 中的版本字段变化时触发 release 判断。 +第一种是 push 到 `main`。当 `VoidDisplay.xcodeproj/project.pbxproj` 中的版本字段变化时触发 release 判断。 第二种是 `workflow_dispatch`。人工输入 tag 和 target ref 后,workflow 校验输入 tag 是否等于 `v${MARKETING_VERSION}`。 diff --git a/docs/display-runtime-phase-1-plan.md b/docs/display-runtime-phase-1-plan.md index f07d5df0..20972f24 100644 --- a/docs/display-runtime-phase-1-plan.md +++ b/docs/display-runtime-phase-1-plan.md @@ -36,13 +36,13 @@ Phase 1 只建立模型、端口、只读快照和对照验证,不迁移命令 - 保留现有 `CaptureSnapshotProvider`、`SharingSnapshotProvider`、`VirtualDisplaySnapshotProvider`、`ScreenCatalogSnapshotProvider` 作为 Phase 1 parity 对照。 - 不处理 LAN Web View token、访问密码、账号体系或鉴权层。 - 不修改 `ObservabilitySnapshotProvider` 协议形状;Phase 1 runtime provider 必须适配现有 `@MainActor` 同步 `makeSnapshot()`。 -- 不主动修改 `Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj`;现有 app scheme 已通过 SwiftPM product `VoidDisplayApp` 消费 package。 +- 不主动修改 `VoidDisplay.xcodeproj/project.pbxproj`;现有 app scheme 已通过 SwiftPM product `VoidDisplayApp` 消费 package。 ## Implementation Plan ### 1. Preflight -- 先处理当前未提交的 `Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj` 变更,避免和新增 target 修改混在一起。 +- 先处理当前未提交的 `VoidDisplay.xcodeproj/project.pbxproj` 变更,避免和新增 target 修改混在一起。 - 确认 `docs/product-positioning.md` 和 `docs/display-runtime-refactor-plan.md` 已作为 Phase 1 依据提交或单独保留。 - 记录 Phase 1 开工前基线验证结果。 - 若 Phase 1 实现时 `project.pbxproj` 仍有未处理改动,停止实现并先处理该工作区状态。 @@ -53,7 +53,7 @@ Phase 1 只建立模型、端口、只读快照和对照验证,不迁移命令 - 修改 `VoidDisplayApp` dependencies,加入 `VoidDisplayRuntime`。 - 新增 `VoidDisplayRuntimeTests`,依赖 `VoidDisplayRuntime`、`VoidDisplayFoundation`、`VoidDisplayObservability`、`VoidDisplayTestingSupport`。 - `VoidDisplayRuntime` 不新增 public product,所有跨 target API 使用 `package` 访问级别。 -- 不修改 `Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj`,除非 Xcode build 证明现有 local Swift package product 无法自动解析更新后的 `VoidDisplayApp` dependency graph。 +- 不修改 `VoidDisplay.xcodeproj/project.pbxproj`,除非 Xcode build 证明现有 local Swift package product 无法自动解析更新后的 `VoidDisplayApp` dependency graph。 - 不修改 app-facing localization。 ### 3. Runtime Model diff --git a/docs/display-runtime-refactor-plan.md b/docs/display-runtime-refactor-plan.md index 5be6533d..c05f5084 100644 --- a/docs/display-runtime-refactor-plan.md +++ b/docs/display-runtime-refactor-plan.md @@ -162,7 +162,7 @@ LAN Web View 路由规则: 执行项: -- 先处理当前未提交的 `Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj` 变更,避免和新增 target、Xcode 工程修改混在一起。 +- 先处理当前未提交的 `VoidDisplay.xcodeproj/project.pbxproj` 变更,避免和新增 target、Xcode 工程修改混在一起。 - 记录当前基线验证结果:SwiftPM targeted tests 和 Xcode Debug build。 - 为 LAN Web View 记录基线表现,输出到 `.ai-tmp/display-runtime-baseline/`。 - 基线记录包含目标分辨率、帧率、viewer 数、CPU、内存、明显延迟体感、测试设备和网络条件。 diff --git a/docs/testing/macos-syspolicy-startup-crash-playbook.md b/docs/testing/macos-syspolicy-startup-crash-playbook.md index d9195aaa..a8e25902 100644 --- a/docs/testing/macos-syspolicy-startup-crash-playbook.md +++ b/docs/testing/macos-syspolicy-startup-crash-playbook.md @@ -67,7 +67,7 @@ sudo killall syspolicyd ```bash xcodebuild test \ - -project Apps/VoidDisplay/VoidDisplay.xcodeproj \ + -project VoidDisplay.xcodeproj \ -scheme VoidDisplay \ -destination 'platform=macOS,arch=arm64' \ -derivedDataPath .ai-tmp/syspolicy-playbook/DerivedData \ diff --git a/scripts/build-relay.sh b/scripts/build-relay.sh index 1f70d04c..bd8cd71f 100755 --- a/scripts/build-relay.sh +++ b/scripts/build-relay.sh @@ -62,6 +62,6 @@ mkdir -p "$OUTPUT_DIR" cd "$RELAY_DIR" env GOPROXY="$GOPROXY_VALUE" "$GO_BIN" test ./... -env GOPROXY="$GOPROXY_VALUE" GOOS=darwin GOARCH="$GOARCH_VALUE" "$GO_BIN" build -trimpath -o "$OUTPUT_PATH" ./cmd/voiddisplay-relay +env GOPROXY="$GOPROXY_VALUE" GOOS=darwin GOARCH="$GOARCH_VALUE" "$GO_BIN" build -buildvcs=false -trimpath -o "$OUTPUT_PATH" ./cmd/voiddisplay-relay chmod +x "$OUTPUT_PATH" require_binary_arch "Relay" "$OUTPUT_PATH" "$TARGET_ARCH" diff --git a/scripts/ci/classification-rules.json b/scripts/ci/classification-rules.json index e6bdaa22..54f573f1 100644 --- a/scripts/ci/classification-rules.json +++ b/scripts/ci/classification-rules.json @@ -2,12 +2,12 @@ "categories": { "code": { "exact": ["Package.swift", "Package.resolved", "mise.toml", "mise.lock", "Brewfile", ".swiftformat", ".swiftlint.yml", ".github/dependabot.yml"], - "prefixes": ["Sources/", "Tests/", "UITests/", "Apps/VoidDisplay/", "Tools/VoidDisplayRelay/", "scripts/", ".github/workflows/", ".github/actions/"], + "prefixes": ["Sources/", "Tests/", "UITests/", "Apps/VoidDisplay/", "VoidDisplay.xcodeproj/", "Tools/VoidDisplayRelay/", "scripts/", ".github/workflows/", ".github/actions/"], "globs": ["*/Package.resolved"] }, "ui": { "exact": [], - "prefixes": ["UITests/", "Apps/VoidDisplay/", "Sources/VoidDisplayApp/", "Sources/VoidDisplayDesignSystem/", "Sources/VoidDisplayCapture/", "Sources/VoidDisplaySharing/", "Sources/VoidDisplaySupport/", "Sources/VoidDisplayVirtualDisplay/"], + "prefixes": ["UITests/", "Apps/VoidDisplay/", "VoidDisplay.xcodeproj/", "Sources/VoidDisplayApp/", "Sources/VoidDisplayDesignSystem/", "Sources/VoidDisplayCapture/", "Sources/VoidDisplaySharing/", "Sources/VoidDisplaySupport/", "Sources/VoidDisplayVirtualDisplay/"], "globs": [] }, "script": { @@ -17,7 +17,7 @@ }, "product_code": { "exact": ["Package.swift", "Package.resolved"], - "prefixes": ["Sources/", "Apps/VoidDisplay/", "Tools/VoidDisplayRelay/"], + "prefixes": ["Sources/", "Apps/VoidDisplay/", "VoidDisplay.xcodeproj/", "Tools/VoidDisplayRelay/"], "globs": ["*/Package.resolved"] }, "test_code": { @@ -32,7 +32,7 @@ }, "release": { "exact": [".github/workflows/release.yml", "scripts/lib/architecture.sh", "scripts/lib/release_binaries.sh"], - "prefixes": ["Apps/VoidDisplay/", "Tools/VoidDisplayRelay/", "scripts/release/"], + "prefixes": ["Apps/VoidDisplay/", "VoidDisplay.xcodeproj/", "Tools/VoidDisplayRelay/", "scripts/release/"], "globs": ["scripts/ci/release_*.sh"] }, "dependency_manifest": { diff --git a/scripts/ci/release_smoke.sh b/scripts/ci/release_smoke.sh index c4c4f05c..a9661e08 100755 --- a/scripts/ci/release_smoke.sh +++ b/scripts/ci/release_smoke.sh @@ -73,7 +73,7 @@ go_mod_download_with_retry "$ROOT_DIR/Tools/VoidDisplayRelay" set +e xcodebuild \ -scheme VoidDisplay \ - -project Apps/VoidDisplay/VoidDisplay.xcodeproj \ + -project VoidDisplay.xcodeproj \ -configuration Release \ -destination "$(xcode_destination_for_arch "$ARCH")" \ -derivedDataPath "$DERIVED_DATA_PATH" \ diff --git a/scripts/ci/static_project.sh b/scripts/ci/static_project.sh index 7705dca4..3fcf7bf5 100755 --- a/scripts/ci/static_project.sh +++ b/scripts/ci/static_project.sh @@ -10,6 +10,9 @@ cd "$ROOT_DIR" require_command git jq swiftformat swiftlint xcrun rg awk diff wc tr +XCODE_PROJECT_DIR="VoidDisplay.xcodeproj" +XCODE_PROJECT_FILE="$XCODE_PROJECT_DIR/project.pbxproj" + fail_on_output() { local message="$1" local output="$2" @@ -31,8 +34,37 @@ assert_file_contains_all() { done } +validate_xcode_project_layout() { + local legacy_project_dir="Apps/VoidDisplay/""VoidDisplay.xcodeproj" + local legacy_references + local invalid_self_references + + [[ -d "$XCODE_PROJECT_DIR" ]] || die "Canonical Xcode project is missing: $XCODE_PROJECT_DIR" + [[ -f "$XCODE_PROJECT_FILE" ]] || die "Canonical Xcode project file is missing: $XCODE_PROJECT_FILE" + [[ ! -e "$legacy_project_dir" ]] || die "Xcode project must stay outside the synchronized app content directory." + + assert_file_contains_all "$XCODE_PROJECT_FILE" "Xcode synchronized roots do not match the repository layout" \ + 'path = Apps/VoidDisplay;' \ + 'path = UITests/VoidDisplayUITests;' \ + 'path = Sources;' \ + 'path = Tests;' \ + 'relativePath = .;' + + invalid_self_references="$( + rg -n '^[[:space:]]*(path = \.;|path = "?VoidDisplay\.xcodeproj"?;)' "$XCODE_PROJECT_FILE" || true + )" + fail_on_output \ + "Xcode project must not synchronize or reference its own project bundle." \ + "$invalid_self_references" + + legacy_references="$(git grep -n -F "$legacy_project_dir" -- . || true)" + fail_on_output \ + "Repository still contains references to the former nested Xcode project path." \ + "$legacy_references" +} + validate_xcode_shell_build_phase() { - local project_file="Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj" + local project_file="$XCODE_PROJECT_FILE" local shell_phase_count local invalid_inputs local root_setting_count @@ -68,7 +100,7 @@ validate_xcode_shell_build_phase() { assert_file_contains_all "$project_file" "Build Relay phase is missing required line" \ 'name = "Build Relay";' \ 'shellPath = /bin/bash;' \ - '"cd \"$SRCROOT/../..\"",' + '"cd \"$SRCROOT\"",' assert_file_contains_all "$project_file" "Build Relay phase is missing required tool input or build setting" \ '"$(TOOL_ROOT)/scripts/build-relay.sh",' \ @@ -77,13 +109,13 @@ validate_xcode_shell_build_phase() { '"$(TOOL_ROOT)/scripts/lib/architecture.sh",' \ '"$(TOOL_ROOT)/scripts/lib/release_binaries.sh",' - root_setting_count="$(rg -F 'ROOT_DIR = "$(SRCROOT)/../..";' "$project_file" | wc -l | tr -d '[:space:]')" + root_setting_count="$(rg -F 'ROOT_DIR = "$(SRCROOT)";' "$project_file" | wc -l | tr -d '[:space:]')" tool_setting_count="$(rg -F 'TOOL_ROOT = "$(ROOT_DIR)";' "$project_file" | wc -l | tr -d '[:space:]')" [[ "$root_setting_count" == "2" ]] || die "ROOT_DIR build setting must be present in Debug and Release." [[ "$tool_setting_count" == "2" ]] || die "TOOL_ROOT build setting must be present in Debug and Release." assert_pbx_array_exact shellScript "Build Relay shellScript" \ - 'cd \"$SRCROOT/../..\"' \ + 'cd \"$SRCROOT\"' \ 'export ROOT_DIR=\"${ROOT_DIR:-$PWD}\"' \ 'export TOOL_ROOT=\"${TOOL_ROOT:-$ROOT_DIR}\"' \ '\"$TOOL_ROOT/scripts/build-relay.sh\"' \ @@ -99,6 +131,13 @@ validate_xcode_shell_build_phase() { fail_on_output "Build Relay input paths must stay under allowed prefixes." "$invalid_inputs" } +validate_relay_build_is_script_sandbox_compatible() { + assert_file_contains_all \ + "$TOOL_ROOT/scripts/build-relay.sh" \ + "Relay build must not inspect undeclared Git metadata inside the Xcode user script sandbox" \ + 'build -buildvcs=false -trimpath' +} + validate_xcode_runner_disables_signing() { local runner="$TOOL_ROOT/scripts/ci/xcode.sh" local base_command @@ -152,6 +191,10 @@ validate_classify_fixtures() { env ROOT_DIR="$ROOT_DIR" TOOL_ROOT="$TOOL_ROOT" "$TOOL_ROOT/scripts/ci/test_classify.sh" } +validate_release_project_path_fixtures() { + env ROOT_DIR="$ROOT_DIR" TOOL_ROOT="$TOOL_ROOT" "$TOOL_ROOT/scripts/ci/test_release_project_paths.sh" +} + validate_swift_style() { swiftformat --lint --config "$ROOT_DIR/.swiftformat" Sources Tests UITests Apps Package.swift scripts/release/render_dmg_background.swift swiftlint lint --config "$ROOT_DIR/.swiftlint.yml" --quiet @@ -172,12 +215,15 @@ validate_ui_tests_do_not_synthesize_keyboard_input() { "$violations" } +validate_xcode_project_layout validate_xcode_shell_build_phase +validate_relay_build_is_script_sandbox_compatible validate_xcode_runner_disables_signing validate_xcode_log_scanner validate_swiftpm_log_scanner validate_bootstrap_profile_fixtures validate_classify_fixtures +validate_release_project_path_fixtures validate_swift_style validate_swift_scripts validate_ui_tests_do_not_synthesize_keyboard_input diff --git a/scripts/ci/test_classify.sh b/scripts/ci/test_classify.sh index 1b85f247..bb19ae8e 100755 --- a/scripts/ci/test_classify.sh +++ b/scripts/ci/test_classify.sh @@ -132,9 +132,12 @@ run_file_case mise_lock pull_request main \ run_file_case package_manifest pull_request main \ "product_code_relevant=true dependency_manifest_relevant=true requires_dependency_review=true requires_unit=true requires_xcode_build=true" \ Package.swift +run_file_case xcode_project pull_request main \ + "code_relevant=true product_code_relevant=true ui_relevant=true release_relevant=true requires_release_smoke=true requires_ui_smoke=true requires_unit=true requires_xcode_build=true unknown_relevant=false" \ + VoidDisplay.xcodeproj/project.pbxproj run_file_case nested_package_resolved pull_request main \ "code_relevant=true product_code_relevant=true dependency_manifest_relevant=true requires_dependency_review=true requires_unit=true requires_xcode_build=true unknown_relevant=false" \ - Apps/VoidDisplay/VoidDisplay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved + VoidDisplay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved run_file_case go_manifest pull_request main \ "product_code_relevant=true dependency_manifest_relevant=true release_relevant=true requires_release_smoke=true requires_unit=true requires_xcode_build=true" \ Tools/VoidDisplayRelay/go.mod diff --git a/scripts/ci/test_release_project_paths.sh b/scripts/ci/test_release_project_paths.sh new file mode 100755 index 00000000..b83c6ad2 --- /dev/null +++ b/scripts/ci/test_release_project_paths.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +# shellcheck source=scripts/lib/contract.sh +source "${BASH_SOURCE[0]%/*}/../lib/contract.sh" +# shellcheck source=scripts/lib/common.sh +source "$TOOL_ROOT/scripts/lib/common.sh" +# shellcheck source=scripts/lib/release.sh +source "$TOOL_ROOT/scripts/lib/release.sh" + +require_command git + +WORK_DIR="${WORK_DIR:-$(make_artifact_dir release-project-path-tests)}" +FIXTURE_REPO="$WORK_DIR/repo" +CURRENT_PROJECT_PATH="VoidDisplay.xcodeproj/project.pbxproj" +PREVIOUS_PROJECT_PATH="LegacyProject/VoidDisplay.xcodeproj/project.pbxproj" + +mkdir -p "$(dirname "$FIXTURE_REPO/$PREVIOUS_PROJECT_PATH")" +git -C "$FIXTURE_REPO" init -q +git -C "$FIXTURE_REPO" config user.email "ci@example.invalid" +git -C "$FIXTURE_REPO" config user.name "CI Release Path Test" +printf 'MARKETING_VERSION = 1.2.3;\nCURRENT_PROJECT_VERSION = 7;\n' \ + >"$FIXTURE_REPO/$PREVIOUS_PROJECT_PATH" +git -C "$FIXTURE_REPO" add "$PREVIOUS_PROJECT_PATH" +git -C "$FIXTURE_REPO" commit -qm "add nested project" +PREVIOUS_COMMIT="$(git -C "$FIXTURE_REPO" rev-parse HEAD)" + +mkdir -p "$(dirname "$FIXTURE_REPO/$CURRENT_PROJECT_PATH")" +git -C "$FIXTURE_REPO" mv "$PREVIOUS_PROJECT_PATH" "$CURRENT_PROJECT_PATH" +git -C "$FIXTURE_REPO" commit -qm "move project to repository root" +CURRENT_COMMIT="$(git -C "$FIXTURE_REPO" rev-parse HEAD)" + +( + cd "$FIXTURE_REPO" + previous_version="$(release_read_project_value_from_git MARKETING_VERSION "$PREVIOUS_COMMIT" "$CURRENT_PROJECT_PATH")" + current_build_number="$(release_read_project_value_from_git CURRENT_PROJECT_VERSION "$CURRENT_COMMIT" "$CURRENT_PROJECT_PATH")" + [[ "$previous_version" == "1.2.3" ]] || die "Renamed project version lookup returned: $previous_version" + [[ "$current_build_number" == "7" ]] || die "Current project build lookup returned: $current_build_number" +) + +info "Release project path fixtures passed." diff --git a/scripts/ci/ui_smoke.sh b/scripts/ci/ui_smoke.sh index 83ca3137..b98c9551 100755 --- a/scripts/ci/ui_smoke.sh +++ b/scripts/ci/ui_smoke.sh @@ -115,7 +115,7 @@ for attempt in $(seq 1 "$MAX_ATTEMPTS"); do set +e xcodebuild \ -scheme VoidDisplay \ - -project Apps/VoidDisplay/VoidDisplay.xcodeproj \ + -project VoidDisplay.xcodeproj \ -destination "$DESTINATION" \ -derivedDataPath "$DERIVED_DATA_PATH" \ -resultBundlePath "$result_bundle" \ diff --git a/scripts/ci/xcode.sh b/scripts/ci/xcode.sh index db518167..06936a94 100755 --- a/scripts/ci/xcode.sh +++ b/scripts/ci/xcode.sh @@ -20,7 +20,7 @@ require_command go jq rg xcodebuild xcrun awk tr tail ACTION="build" CONFIGURATION="Debug" -PROJECT_PATH="Apps/VoidDisplay/VoidDisplay.xcodeproj" +PROJECT_PATH="VoidDisplay.xcodeproj" SCHEME="VoidDisplay" DESTINATION="$(xcode_destination_for_arch arm64)" OUT_DIR="$(make_artifact_dir ci-xcode)" diff --git a/scripts/dev/doctor.sh b/scripts/dev/doctor.sh index 75cf6522..81ce5d60 100755 --- a/scripts/dev/doctor.sh +++ b/scripts/dev/doctor.sh @@ -62,7 +62,7 @@ fi package_resolved_ok="true" for resolved_path in \ Package.resolved \ - Apps/VoidDisplay/VoidDisplay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved \ + VoidDisplay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved \ VoidDisplay.xcworkspace/xcshareddata/swiftpm/Package.resolved; do if [[ ! -f "$resolved_path" ]]; then package_resolved_ok="false" diff --git a/scripts/lib/release.sh b/scripts/lib/release.sh index 0ef20a59..502e2b27 100644 --- a/scripts/lib/release.sh +++ b/scripts/lib/release.sh @@ -9,7 +9,7 @@ if [[ -z "${VOIDDISPLAY_RELEASE_SH_SOURCED:-}" ]]; then source "$TOOL_ROOT/scripts/lib/architecture.sh" release_project_file() { - printf '%s\n' "$ROOT_DIR/Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj" + printf '%s\n' "$ROOT_DIR/VoidDisplay.xcodeproj/project.pbxproj" } release_read_project_value_stream() { @@ -31,13 +31,39 @@ if [[ -z "${VOIDDISPLAY_RELEASE_SH_SOURCED:-}" ]]; then release_read_project_value_stream "$key" <"$source" } + release_resolve_project_path_at_git_commit() { + local commit="$1" + local current_path="${2:-VoidDisplay.xcodeproj/project.pbxproj}" + local comparison_commit="${3:-HEAD}" + local renamed_paths + local renamed_path_count + + if git cat-file -e "${commit}:${current_path}" 2>/dev/null; then + printf '%s\n' "$current_path" + return + fi + + renamed_paths="$( + git diff --find-renames --name-status "$commit" "$comparison_commit" -- | + awk -F '\t' -v current_path="$current_path" \ + '$1 ~ /^R[0-9]+$/ && $3 == current_path { print $2 }' + )" + renamed_path_count="$(printf '%s\n' "$renamed_paths" | sed '/^$/d' | wc -l | tr -d ' ')" + [[ "$renamed_path_count" == "1" ]] || + die "Unable to resolve the previous path for $current_path at $commit." + git cat-file -e "${commit}:${renamed_paths}" 2>/dev/null || + die "Unable to read renamed project path $renamed_paths at $commit." + printf '%s\n' "$renamed_paths" + } + release_read_project_value_from_git() { local key="$1" local commit="$2" - local path="${3:-Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj}" + local path="${3:-VoidDisplay.xcodeproj/project.pbxproj}" + local resolved_path - git cat-file -e "${commit}:${path}" 2>/dev/null || die "Unable to read $path at $commit." - git show "${commit}:${path}" | release_read_project_value_stream "$key" "at $commit" + resolved_path="$(release_resolve_project_path_at_git_commit "$commit" "$path")" + git show "${commit}:${resolved_path}" | release_read_project_value_stream "$key" "at $commit" } release_require_semver() { diff --git a/scripts/release/prepare.sh b/scripts/release/prepare.sh index f7c31a7a..55708036 100755 --- a/scripts/release/prepare.sh +++ b/scripts/release/prepare.sh @@ -94,7 +94,7 @@ SUMMARY_PATH="$(normalize_target_path "$SUMMARY_PATH")" cd "$TARGET_REPO_DIR" -project_file="Apps/VoidDisplay/VoidDisplay.xcodeproj/project.pbxproj" +project_file="VoidDisplay.xcodeproj/project.pbxproj" find_existing_tag_sha() { local release_tag="$1" From b319eb190ce7b037162c28a2b13719d2afbbaae9 Mon Sep 17 00:00:00 2001 From: Chen Date: Fri, 17 Jul 2026 02:15:08 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(ci):=20=E5=A3=B0=E6=98=8E=20mise=20?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E8=BE=93=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 mise 配置与锁文件加入 Build Relay 输入依赖 - 对齐 Xcode 脚本沙箱与 CI 工具链读取权限 - 精确校验全部 Relay 模块输入以阻止遗漏回归 --- VoidDisplay.xcodeproj/project.pbxproj | 6 ++++-- scripts/ci/static_project.sh | 29 ++++++++++++++++----------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/VoidDisplay.xcodeproj/project.pbxproj b/VoidDisplay.xcodeproj/project.pbxproj index 4c0e1367..db6bb69c 100644 --- a/VoidDisplay.xcodeproj/project.pbxproj +++ b/VoidDisplay.xcodeproj/project.pbxproj @@ -194,15 +194,17 @@ isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; inputPaths = ( + "$(TOOL_ROOT)/mise.toml", + "$(TOOL_ROOT)/mise.lock", "$(TOOL_ROOT)/scripts/build-relay.sh", "$(TOOL_ROOT)/scripts/lib/contract.sh", "$(TOOL_ROOT)/scripts/lib/common.sh", "$(TOOL_ROOT)/scripts/lib/architecture.sh", "$(TOOL_ROOT)/scripts/lib/release_binaries.sh", - "$(ROOT_DIR)/Tools/VoidDisplayRelay/go.mod", - "$(ROOT_DIR)/Tools/VoidDisplayRelay/go.sum", "$(ROOT_DIR)/Tools/VoidDisplayRelay/cmd/voiddisplay-relay/main.go", "$(ROOT_DIR)/Tools/VoidDisplayRelay/cmd/voiddisplay-relay/main_test.go", + "$(ROOT_DIR)/Tools/VoidDisplayRelay/go.mod", + "$(ROOT_DIR)/Tools/VoidDisplayRelay/go.sum", "$(ROOT_DIR)/Tools/VoidDisplayRelay/internal/relay/server.go", "$(ROOT_DIR)/Tools/VoidDisplayRelay/internal/relay/server_test.go", ); diff --git a/scripts/ci/static_project.sh b/scripts/ci/static_project.sh index 3fcf7bf5..a3dc8a86 100755 --- a/scripts/ci/static_project.sh +++ b/scripts/ci/static_project.sh @@ -65,8 +65,18 @@ validate_xcode_project_layout() { validate_xcode_shell_build_phase() { local project_file="$XCODE_PROJECT_FILE" + local -a expected_inputs=( + '$(TOOL_ROOT)/mise.toml' + '$(TOOL_ROOT)/mise.lock' + '$(TOOL_ROOT)/scripts/build-relay.sh' + '$(TOOL_ROOT)/scripts/lib/contract.sh' + '$(TOOL_ROOT)/scripts/lib/common.sh' + '$(TOOL_ROOT)/scripts/lib/architecture.sh' + '$(TOOL_ROOT)/scripts/lib/release_binaries.sh' + ) + local relay_file + local relay_file_count=0 local shell_phase_count - local invalid_inputs local root_setting_count local tool_setting_count @@ -102,12 +112,11 @@ validate_xcode_shell_build_phase() { 'shellPath = /bin/bash;' \ '"cd \"$SRCROOT\"",' - assert_file_contains_all "$project_file" "Build Relay phase is missing required tool input or build setting" \ - '"$(TOOL_ROOT)/scripts/build-relay.sh",' \ - '"$(TOOL_ROOT)/scripts/lib/contract.sh",' \ - '"$(TOOL_ROOT)/scripts/lib/common.sh",' \ - '"$(TOOL_ROOT)/scripts/lib/architecture.sh",' \ - '"$(TOOL_ROOT)/scripts/lib/release_binaries.sh",' + while IFS= read -r relay_file; do + expected_inputs+=('$(ROOT_DIR)/'"$relay_file") + relay_file_count=$((relay_file_count + 1)) + done < <(git ls-files -- Tools/VoidDisplayRelay) + ((relay_file_count > 0)) || die "Relay module has no tracked files to declare as Xcode build inputs." root_setting_count="$(rg -F 'ROOT_DIR = "$(SRCROOT)";' "$project_file" | wc -l | tr -d '[:space:]')" tool_setting_count="$(rg -F 'TOOL_ROOT = "$(ROOT_DIR)";' "$project_file" | wc -l | tr -d '[:space:]')" @@ -124,11 +133,7 @@ validate_xcode_shell_build_phase() { assert_pbx_array_exact outputPaths "Build Relay outputPaths" \ '$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/voiddisplay-relay' - invalid_inputs="$( - extract_pbx_array_values inputPaths | - rg -v '^\$\(TOOL_ROOT\)/scripts/(build-relay\.sh|lib/(contract|common|architecture|release_binaries)\.sh)$|^\$\(ROOT_DIR\)/Tools/VoidDisplayRelay/' || true - )" - fail_on_output "Build Relay input paths must stay under allowed prefixes." "$invalid_inputs" + assert_pbx_array_exact inputPaths "Build Relay inputPaths" "${expected_inputs[@]}" } validate_relay_build_is_script_sandbox_compatible() {