From 3f27441fd6d9635cf9f073f5e622778ae2527b54 Mon Sep 17 00:00:00 2001 From: Chen Date: Sun, 10 May 2026 18:13:38 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ci):=20=E7=B2=BE=E7=AE=80=20static=20g?= =?UTF-8?q?ate=20=E9=9D=99=E6=80=81=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 release workflow 与 WebRTC 细节重复断言 - 保留脚本契约、日志扫描与 Swift 静态门禁 - 更新 WebRTC overlay 文档中的验证边界 --- Vendor/WebRTCHeaders/M147/SOURCES.md | 4 +- scripts/ci/static.sh | 235 +-------------------------- 2 files changed, 7 insertions(+), 232 deletions(-) diff --git a/Vendor/WebRTCHeaders/M147/SOURCES.md b/Vendor/WebRTCHeaders/M147/SOURCES.md index 056a3a8..01cbf47 100644 --- a/Vendor/WebRTCHeaders/M147/SOURCES.md +++ b/Vendor/WebRTCHeaders/M147/SOURCES.md @@ -37,8 +37,8 @@ Official WebRTC source branch used for macOS-only headers: and normalize its WebRTC-local include. 5. Regenerate `SHA256SUMS` from `include/WebRTC/*.h`. -The static gate validates recursive imports, iOS-only header exclusions, guarded -UIKit usage, and checksum coverage. +`Package.swift` owns the binary pin. `SHA256SUMS` covers the overlay headers. +SwiftPM and Xcode builds validate header import compatibility. `WebRTCHeaderOverlayAnchor.c` is an empty compilation unit that lets SwiftPM model this directory as a C target while all public API surface comes from the diff --git a/scripts/ci/static.sh b/scripts/ci/static.sh index 050c0b9..995d62a 100755 --- a/scripts/ci/static.sh +++ b/scripts/ci/static.sh @@ -8,7 +8,7 @@ source "$TOOL_ROOT/scripts/lib/common.sh" cd "$ROOT_DIR" -require_command actionlint jq shellcheck shfmt shasum swift swiftformat swiftlint rg xcrun +require_command actionlint jq shellcheck shfmt swiftformat swiftlint rg xcrun validate_runner_labels() { assert_no_match "Workflow uses a non-macOS, paid, or larger runner label." \ @@ -123,53 +123,6 @@ assert_file_contains_all() { done } -assert_paths_exist() { - local message="$1" - local path - shift - - for path in "$@"; do - [[ -e "$path" ]] || die "$message: $path" - done -} - -workflow_job_block() { - local file="$1" - local job="$2" - - awk -v job="$job" ' - $0 ~ "^[[:space:]]{2}" job ":" { inside = 1; print; next } - inside && /^[[:space:]]{2}[[:alnum:]_-]+:/ { exit } - inside { print } - ' "$file" -} - -assert_job_contains() { - local file="$1" - local job="$2" - local pattern="$3" - local message="$4" - local block - - block="$(workflow_job_block "$file" "$job")" - [[ -n "$block" ]] || die "$message" - if ! rg -n "$pattern" <<<"$block" >/dev/null; then - die "$message" - fi -} - -assert_job_no_match() { - local file="$1" - local job="$2" - local pattern="$3" - local message="$4" - local block - - block="$(workflow_job_block "$file" "$job")" - [[ -n "$block" ]] || die "$message" - fail_on_output "$message" "$(rg -n "$pattern" <<<"$block" || true)" -} - validate_workflow_script_contract() { local invalid local workflow_files=() @@ -189,19 +142,7 @@ validate_workflow_script_contract() { assert_no_match "Workflow script invocations must execute scripts through TOOL_ROOT." \ '\$GITHUB_WORKSPACE/scripts/' .github/workflows .github/actions - invalid="$( - awk ' - /scripts\// { - if ($0 ~ /^[[:space:]]*- '\''scripts\//) { - next - } - if ($0 ~ /"\$tool_root\/scripts\//) { - next - } - print FILENAME ":" FNR ":" $0 - } - ' "${workflow_files[@]}" || true - )" + invalid="$(awk '/scripts\// && $0 !~ /^[[:space:]]*- '\''scripts\// && $0 !~ /"\$tool_root\/scripts\// { print FILENAME ":" FNR ":" $0 }' "${workflow_files[@]}" || true)" fail_on_output "Workflow script references must be path filters or TOOL_ROOT executions." "$invalid" invalid="$(rg -n 'ROOT_DIR=.*scripts/' .github/workflows .github/actions | rg -v 'TOOL_ROOT=.*"\$tool_root/scripts/' || true)" @@ -212,29 +153,26 @@ validate_workflow_script_contract() { invalid="$( awk ' function finish_checkout() { - if (in_checkout && !saw_persist_credentials) { + if (checkout_line && !saw_persist_credentials) { print current_file ":" checkout_line ":actions/checkout must set persist-credentials: false" } } FNR == 1 { finish_checkout() current_file = FILENAME - in_checkout = 0 saw_persist_credentials = 0 checkout_line = 0 } - /^[[:space:]]*-[[:space:]]+name:/ { + /^[[:space:]]*-[[:space:]]+(name|uses):/ { finish_checkout() - in_checkout = 0 saw_persist_credentials = 0 checkout_line = 0 } /uses:[[:space:]]*actions\/checkout@/ { - in_checkout = 1 saw_persist_credentials = 0 checkout_line = FNR } - in_checkout && /persist-credentials:[[:space:]]*false/ { + checkout_line && /persist-credentials:[[:space:]]*false/ { saw_persist_credentials = 1 } END { @@ -243,28 +181,6 @@ validate_workflow_script_contract() { ' "${pr_ci_workflow_files[@]}" || true )" fail_on_output "PR CI checkouts must disable persisted credentials." "$invalid" - assert_no_match "Release smoke must stay split into arm64 and intel64 jobs." \ - '^[[:space:]]{2}release_build_check:' .github/workflows/ci.yml - - assert_job_contains .github/workflows/ci.yml release_build_check_arm64 \ - 'if:.*requires_release_smoke.*true' \ - "arm64 release smoke must run whenever release smoke is required" - assert_job_contains .github/workflows/ci.yml release_build_check_intel64 \ - 'if:.*github\.event_name.*push.*requires_release_smoke.*true' \ - "intel64 release smoke must be push-only" - - assert_job_no_match .github/workflows/release.yml require_ci_gate \ - 'actions\/checkout@|scripts\/release\/require_ci_gate\.sh|scripts\/dev\/bootstrap\.sh|prepare_release|needs\.prepare_release' \ - "Release ci-gate verification must stay workflow-owned and must not execute target checkout scripts." - assert_job_no_match .github/workflows/release.yml resolve_release_target \ - 'actions\/checkout@|scripts\/' \ - "resolve-release-target must not checkout or execute repository scripts" - assert_job_contains .github/workflows/release.yml prepare_release \ - '^[[:space:]]*-[[:space:]]*require_ci_gate[[:space:]]*$' \ - "prepare-release must depend on require-ci-gate" - assert_job_contains .github/workflows/release.yml prepare_release \ - 'ref:[[:space:]]*\$\{\{[[:space:]]*needs\.resolve_release_target\.outputs\.target_sha[[:space:]]*\}\}' \ - "prepare-release must checkout the resolved target SHA" } validate_xcode_shell_build_phase() { @@ -362,124 +278,6 @@ validate_classify_fixtures() { env ROOT_DIR="$ROOT_DIR" TOOL_ROOT="$TOOL_ROOT" "$TOOL_ROOT/scripts/ci/test_classify.sh" } -validate_webrtc_header_overlay() { - local overlay_root="$ROOT_DIR/Vendor/WebRTCHeaders/M147" - local include_dir="$overlay_root/include/WebRTC" - local checksum_file="$overlay_root/SHA256SUMS" - local forbidden_header - local invalid - local expected_paths - local actual_paths - local manifest_json - - if ! manifest_json="$(swift package dump-package 2>/dev/null)"; then - die "Package.swift must be readable by SwiftPM." - fi - - if ! jq -e \ - --arg url 'https://github.com/stasel/WebRTC/releases/download/147.0.0/WebRTC-M147.xcframework.zip' \ - --arg checksum '49f9b1713432c19f408e3218fc8526c7692fafca5869f7ec5f5991614276ed40' \ - '( - any(.targets[]; .name == "WebRTCBinary" and .type == "binary" and .url == $url and .checksum == $checksum) and - any(.targets[]; .name == "WebRTC" and .type == "regular" and .path == "Vendor/WebRTCHeaders/M147" and .publicHeadersPath == "include" and any(.dependencies[]?; .byName[0] == "WebRTCBinary")) and - any(.targets[]; .name == "VoidDisplaySharing" and any(.dependencies[]?; .byName[0] == "WebRTC")) and - ((.dependencies // []) | length == 0) - )' \ - <<<"$manifest_json" >/dev/null; then - die "Package.swift must use the local WebRTC M147 wrapper target and no remote source package dependencies." - fi - assert_no_match "Package.swift must use the local WebRTC wrapper target instead of the remote stasel package." \ - 'https://github.com/stasel/WebRTC.git' "$ROOT_DIR/Package.swift" - invalid="$(rg -n 'https://github.com/stasel/WebRTC.git|\"identity\"[[:space:]]*:[[:space:]]*\"webrtc\"' \ - "$ROOT_DIR/Package.resolved" \ - "$ROOT_DIR/Apps/VoidDisplay/VoidDisplay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" \ - "$ROOT_DIR/VoidDisplay.xcworkspace/xcshareddata/swiftpm/Package.resolved" || true)" - fail_on_output "Package.resolved files must not retain stale stasel/WebRTC source pins." "$invalid" - - assert_paths_exist "WebRTC M147 header overlay is missing required file" \ - "$overlay_root/SOURCES.md" \ - "$checksum_file" \ - "$overlay_root/WebRTCHeaderOverlayAnchor.c" \ - "$include_dir/WebRTC.h" \ - "$include_dir/RTCMTLNSVideoView.h" - - assert_file_contains_all "$overlay_root/SOURCES.md" "WebRTC M147 SOURCES.md is missing required source detail" \ - 'https://github.com/stasel/WebRTC/releases/download/147.0.0/WebRTC-M147.xcframework.zip' \ - '49f9b1713432c19f408e3218fc8526c7692fafca5869f7ec5f5991614276ed40' \ - 'refs/branch-heads/7727' \ - 'macos-x86_64_arm64/WebRTC.framework/Versions/A/Headers/WebRTC.h' \ - 'RTCMTLNSVideoView.h' - - invalid="$( - find "$overlay_root" -type f \ - ! -path "$include_dir/*.h" \ - ! -path "$overlay_root/SOURCES.md" \ - ! -path "$overlay_root/SHA256SUMS" \ - ! -path "$overlay_root/WebRTCHeaderOverlayAnchor.c" \ - -print - )" - fail_on_output "WebRTC M147 overlay may only contain headers, source metadata, checksums, and the anchor C file." "$invalid" - - assert_file_contains_all "$include_dir/RTCMTLNSVideoView.h" \ - "RTCMTLNSVideoView.h must import RTCVideoRenderer.h through the WebRTC framework path" \ - '#import ' - - for forbidden_header in RTCEAGLVideoView.h RTCCameraPreviewView.h UIDevice+RTCDevice.h; do - [[ ! -e "$include_dir/$forbidden_header" ]] || die "WebRTC M147 overlay must not include iOS-only header: $forbidden_header" - done - - assert_no_match "WebRTC M147 overlay must not use WebRTC-local quoted imports." \ - '^[[:space:]]*#(import|include)[[:space:]]+"[^"]+"' "$include_dir" - - invalid="$( - while IFS=: read -r file line_number import_path; do - [[ -n "$import_path" ]] || continue - import_path="${import_path#\n' "$file" "$line_number" "WebRTC/$import_path" - done < <(rg -n -o ']+>' "$include_dir" || true) - )" - fail_on_output "WebRTC M147 overlay has unresolved recursive WebRTC imports." "$invalid" - - invalid="$( - awk ' - FNR == 1 { - depth = 0 - iphone_guard_depth = 0 - } - /^[[:space:]]*#[[:space:]]*if/ { - depth += 1 - if ($0 ~ /TARGET_OS_IPHONE/) { - iphone_guard_depth = depth - } - } - /UIKit/ && iphone_guard_depth == 0 { - print FILENAME ":" FNR ":" $0 - } - /^[[:space:]]*#[[:space:]]*endif/ { - if (iphone_guard_depth == depth) { - iphone_guard_depth = 0 - } - if (depth > 0) { - depth -= 1 - } - } - ' "$include_dir"/*.h - )" - fail_on_output "WebRTC M147 overlay may only reference UIKit inside TARGET_OS_IPHONE guards." "$invalid" - - if ! (cd "$overlay_root" && shasum -a 256 -c SHA256SUMS >/dev/null); then - (cd "$overlay_root" && shasum -a 256 -c SHA256SUMS) >&2 || true - die "WebRTC M147 overlay checksums do not match." - fi - - expected_paths="$(awk '{ print $2 }' "$checksum_file" | sort)" - actual_paths="$(cd "$overlay_root" && find include/WebRTC -type f -name '*.h' -print | sort)" - if ! diff -u <(printf '%s\n' "$expected_paths") <(printf '%s\n' "$actual_paths") >&2; then - die "WebRTC M147 overlay SHA256SUMS must cover every header and only headers." - fi -} - 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 @@ -489,27 +287,6 @@ validate_swift_scripts() { xcrun swiftc -typecheck "$ROOT_DIR/scripts/release/render_dmg_background.swift" } -validate_create_dmg_failure_summary() { - local out_dir - local summary_path - local missing_app_path - - out_dir="$AI_TMP_DIR/static-dmg-summary/$(timestamp)" - summary_path="$out_dir/create-dmg-summary.json" - missing_app_path="$out_dir/Missing.app" - mkdir -p "$out_dir" - - if env ROOT_DIR="$ROOT_DIR" TOOL_ROOT="$TOOL_ROOT" "$TOOL_ROOT/scripts/release/create_dmg.sh" \ - --summary "$summary_path" \ - "$missing_app_path" \ - "$out_dir/Missing.dmg" \ - "VoidDisplay" >/dev/null 2>&1; then - die "create_dmg missing-app fixture unexpectedly passed." - fi - jq -e '.status == "failed" and .reason == "missing_app" and .stage == "argument_validation"' "$summary_path" >/dev/null || - die "create_dmg missing-app fixture did not write the expected summary." -} - actionlint validate_runner_labels validate_action_pinning @@ -520,9 +297,7 @@ validate_xcode_shell_build_phase validate_xcode_log_scanner validate_swiftpm_log_scanner validate_classify_fixtures -validate_webrtc_header_overlay validate_swift_style validate_swift_scripts -validate_create_dmg_failure_summary info "Static gate passed."