diff --git a/scripts/ci/classify.sh b/scripts/ci/classify.sh index 707bc43..0319048 100755 --- a/scripts/ci/classify.sh +++ b/scripts/ci/classify.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# shellcheck disable=SC2034,SC2154 set -euo pipefail # shellcheck source=scripts/lib/contract.sh @@ -48,18 +49,28 @@ SUMMARY_PATH="${SUMMARY_PATH:-$(make_artifact_dir classify)/classify-summary.jso require_command git jq RULES_PATH="$TOOL_ROOT/scripts/ci/classification-rules.json" -classification_categories=( - code - ui - script - product_code - test_code - ci_config - release - dependency_manifest - tooling_config - docs +classification_categories=(code ui script product_code test_code ci_config release dependency_manifest tooling_config docs) +relevant_flags=() +for category in "${classification_categories[@]}"; do + [[ "$category" == "docs" ]] || relevant_flags+=("${category}_relevant") +done +classification_flags=("${relevant_flags[@]}" docs_only unknown_relevant) +requirement_flags=( + requires_static + requires_dependency_review + requires_unit + requires_xcode_build + requires_ui_smoke + requires_release_smoke +) +push_required_flags=( + requires_static + requires_unit + requires_xcode_build + requires_ui_smoke + requires_release_smoke ) +output_fields=("${classification_flags[@]:0:3}" change_scope "${classification_flags[@]:3}" "${requirement_flags[@]}") [[ -f "$RULES_PATH" ]] || die "Missing classification rules: $RULES_PATH" jq -e '.categories | type == "object"' "$RULES_PATH" >/dev/null || die "Invalid classification rules: $RULES_PATH" @@ -112,62 +123,20 @@ path_matches_category() { return 1 } -is_code_path() { - path_matches_category code "$1" -} - -is_ui_path() { - path_matches_category ui "$1" -} - -is_script_path() { - path_matches_category script "$1" -} - -is_product_code_path() { - path_matches_category product_code "$1" -} - -is_test_code_path() { - path_matches_category test_code "$1" -} - -is_ci_config_path() { - path_matches_category ci_config "$1" -} - -is_release_path() { - path_matches_category release "$1" -} - -is_dependency_manifest_path() { - path_matches_category dependency_manifest "$1" -} - -is_tooling_config_path() { - path_matches_category tooling_config "$1" -} - -is_docs_path() { - path_matches_category docs "$1" -} +changed_files=() +changed_entry_json_items=() +classification_reason="diff" -is_known_path() { - local file_path="$1" - local category +set_flags() { + local value="$1" + local flag + shift - for category in "${classification_categories[@]}"; do - path_matches_category "$category" "$file_path" && return 0 + for flag in "$@"; do + printf -v "$flag" '%s' "$value" done - return 1 } -changed_files=() -changed_entry_status=() -changed_entry_old_path=() -changed_entry_new_path=() -classification_reason="diff" - append_changed_file() { local file_path="$1" if [[ -n "$file_path" ]]; then @@ -180,9 +149,11 @@ append_changed_entry() { local old_path="$2" local new_path="${3:-}" - changed_entry_status+=("$status") - changed_entry_old_path+=("$old_path") - changed_entry_new_path+=("$new_path") + changed_entry_json_items+=("$(jq -cn \ + --arg status "$status" \ + --arg old_path "$old_path" \ + --arg new_path "$new_path" \ + '{status: $status, old_path: $old_path, new_path: (if $new_path == "" then null else $new_path end)}')") append_changed_file "$old_path" append_changed_file "$new_path" } @@ -225,71 +196,36 @@ deduplicate_changed_files() { if is_zero_sha "$BASE_SHA" || ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null || ! git cat-file -e "${HEAD_SHA}^{commit}" 2>/dev/null; then classification_reason="full_scan" - code_relevant="true" - ui_relevant="true" - script_relevant="true" - product_code_relevant="true" - test_code_relevant="true" - ci_config_relevant="true" - release_relevant="true" - dependency_manifest_relevant="true" - tooling_config_relevant="true" + set_flags true "${relevant_flags[@]}" docs_only="false" unknown_relevant="false" else collect_changed_entries deduplicate_changed_files - code_relevant="false" - ui_relevant="false" - script_relevant="false" - product_code_relevant="false" - test_code_relevant="false" - ci_config_relevant="false" - release_relevant="false" - dependency_manifest_relevant="false" - tooling_config_relevant="false" - docs_only="false" - unknown_relevant="false" + set_flags false "${classification_flags[@]}" if [[ "${#changed_files[@]}" -gt 0 ]]; then all_docs="true" for file_path in "${changed_files[@]}"; do - if is_code_path "$file_path"; then - code_relevant="true" - fi - if is_ui_path "$file_path"; then - ui_relevant="true" - fi - if is_script_path "$file_path"; then - script_relevant="true" - fi - if is_product_code_path "$file_path"; then - product_code_relevant="true" - fi - if is_test_code_path "$file_path"; then - test_code_relevant="true" - fi - if is_ci_config_path "$file_path"; then - ci_config_relevant="true" - fi - if is_release_path "$file_path"; then - release_relevant="true" - fi - if is_dependency_manifest_path "$file_path"; then - dependency_manifest_relevant="true" - fi - if is_tooling_config_path "$file_path"; then - tooling_config_relevant="true" - fi - if ! is_docs_path "$file_path"; then + matched_any="false" + file_docs="false" + for category in "${classification_categories[@]}"; do + if path_matches_category "$category" "$file_path"; then + matched_any="true" + if [[ "$category" == "docs" ]]; then + file_docs="true" + else + printf -v "${category}_relevant" '%s' true + fi + fi + done + if [[ "$file_docs" != "true" ]]; then all_docs="false" fi - if ! is_known_path "$file_path"; then + if [[ "$matched_any" != "true" ]]; then unknown_relevant="true" fi done - if [[ "$all_docs" == "true" ]]; then - docs_only="true" - fi + [[ "$all_docs" == "true" ]] && docs_only="true" fi fi @@ -300,19 +236,10 @@ elif [[ "$code_relevant" == "true" ]]; then change_scope="code" fi -requires_static="false" -requires_dependency_review="false" -requires_unit="false" -requires_xcode_build="false" -requires_ui_smoke="false" -requires_release_smoke="false" +set_flags false "${requirement_flags[@]}" if [[ "$EVENT_NAME" == "push" ]]; then - requires_static="true" - requires_unit="true" - requires_xcode_build="true" - requires_ui_smoke="true" - requires_release_smoke="true" + set_flags true "${push_required_flags[@]}" elif [[ "$docs_only" != "true" ]]; then if [[ "$code_relevant" == "true" || "$unknown_relevant" == "true" ]]; then requires_static="true" @@ -335,24 +262,9 @@ elif [[ "$docs_only" != "true" ]]; then fi fi -append_github_output code_relevant "$code_relevant" "$GITHUB_OUTPUT_PATH" -append_github_output ui_relevant "$ui_relevant" "$GITHUB_OUTPUT_PATH" -append_github_output script_relevant "$script_relevant" "$GITHUB_OUTPUT_PATH" -append_github_output change_scope "$change_scope" "$GITHUB_OUTPUT_PATH" -append_github_output product_code_relevant "$product_code_relevant" "$GITHUB_OUTPUT_PATH" -append_github_output test_code_relevant "$test_code_relevant" "$GITHUB_OUTPUT_PATH" -append_github_output ci_config_relevant "$ci_config_relevant" "$GITHUB_OUTPUT_PATH" -append_github_output release_relevant "$release_relevant" "$GITHUB_OUTPUT_PATH" -append_github_output dependency_manifest_relevant "$dependency_manifest_relevant" "$GITHUB_OUTPUT_PATH" -append_github_output tooling_config_relevant "$tooling_config_relevant" "$GITHUB_OUTPUT_PATH" -append_github_output docs_only "$docs_only" "$GITHUB_OUTPUT_PATH" -append_github_output unknown_relevant "$unknown_relevant" "$GITHUB_OUTPUT_PATH" -append_github_output requires_static "$requires_static" "$GITHUB_OUTPUT_PATH" -append_github_output requires_dependency_review "$requires_dependency_review" "$GITHUB_OUTPUT_PATH" -append_github_output requires_unit "$requires_unit" "$GITHUB_OUTPUT_PATH" -append_github_output requires_xcode_build "$requires_xcode_build" "$GITHUB_OUTPUT_PATH" -append_github_output requires_ui_smoke "$requires_ui_smoke" "$GITHUB_OUTPUT_PATH" -append_github_output requires_release_smoke "$requires_release_smoke" "$GITHUB_OUTPUT_PATH" +for field in "${output_fields[@]}"; do + append_github_output "$field" "${!field}" "$GITHUB_OUTPUT_PATH" +done if [[ "${#changed_files[@]}" -gt 0 ]]; then changed_files_json="$(printf '%s\n' "${changed_files[@]}" | jq -R . | jq -s .)" @@ -360,69 +272,34 @@ else changed_files_json="[]" fi -if [[ "${#changed_entry_status[@]}" -gt 0 ]]; then - changed_entries_json="$( - for index in "${!changed_entry_status[@]}"; do - jq -n \ - --arg status "${changed_entry_status[$index]}" \ - --arg old_path "${changed_entry_old_path[$index]}" \ - --arg new_path "${changed_entry_new_path[$index]}" \ - '{status: $status, old_path: $old_path, new_path: (if $new_path == "" then null else $new_path end)}' - done | jq -s . - )" +if [[ "${#changed_entry_json_items[@]}" -gt 0 ]]; then + changed_entries_json="$(printf '%s\n' "${changed_entry_json_items[@]}" | jq -s .)" else changed_entries_json="[]" fi +summary_flags_json="$( + for field in "${classification_flags[@]}" "${requirement_flags[@]}"; do + jq -n --arg field "$field" --arg value "${!field}" '{($field): ($value == "true")}' + done | jq -s add +)" + write_json_file "$SUMMARY_PATH" \ --arg base "$BASE_SHA" \ --arg head "$HEAD_SHA" \ - --arg code_relevant "$code_relevant" \ - --arg ui_relevant "$ui_relevant" \ - --arg script_relevant "$script_relevant" \ - --arg product_code_relevant "$product_code_relevant" \ - --arg test_code_relevant "$test_code_relevant" \ - --arg ci_config_relevant "$ci_config_relevant" \ - --arg release_relevant "$release_relevant" \ - --arg dependency_manifest_relevant "$dependency_manifest_relevant" \ - --arg tooling_config_relevant "$tooling_config_relevant" \ - --arg docs_only "$docs_only" \ - --arg unknown_relevant "$unknown_relevant" \ - --arg requires_static "$requires_static" \ - --arg requires_dependency_review "$requires_dependency_review" \ - --arg requires_unit "$requires_unit" \ - --arg requires_xcode_build "$requires_xcode_build" \ - --arg requires_ui_smoke "$requires_ui_smoke" \ - --arg requires_release_smoke "$requires_release_smoke" \ --arg change_scope "$change_scope" \ --arg reason "$classification_reason" \ + --argjson flags "$summary_flags_json" \ --argjson changed_files "$changed_files_json" \ --argjson changed_entries "$changed_entries_json" \ '{ base: $base, head: $head, - code_relevant: ($code_relevant == "true"), - ui_relevant: ($ui_relevant == "true"), - script_relevant: ($script_relevant == "true"), - product_code_relevant: ($product_code_relevant == "true"), - test_code_relevant: ($test_code_relevant == "true"), - ci_config_relevant: ($ci_config_relevant == "true"), - release_relevant: ($release_relevant == "true"), - dependency_manifest_relevant: ($dependency_manifest_relevant == "true"), - tooling_config_relevant: ($tooling_config_relevant == "true"), - docs_only: ($docs_only == "true"), - unknown_relevant: ($unknown_relevant == "true"), - requires_static: ($requires_static == "true"), - requires_dependency_review: ($requires_dependency_review == "true"), - requires_unit: ($requires_unit == "true"), - requires_xcode_build: ($requires_xcode_build == "true"), - requires_ui_smoke: ($requires_ui_smoke == "true"), - requires_release_smoke: ($requires_release_smoke == "true"), - change_scope: $change_scope, + change_scope: $change_scope, reason: $reason, changed_files: $changed_files, changed_entries: $changed_entries - }' + } + $flags' info "Change scope: $change_scope code_relevant=$code_relevant ui_relevant=$ui_relevant requires_unit=$requires_unit requires_xcode_build=$requires_xcode_build" info "Classification summary: $SUMMARY_PATH" diff --git a/scripts/ci/download_relay_modules.sh b/scripts/ci/download_relay_modules.sh deleted file mode 100755 index 51e245f..0000000 --- a/scripts/ci/download_relay_modules.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/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" - -RELAY_DIR="$ROOT_DIR/Tools/VoidDisplayRelay" -GOPROXY_VALUE="${GOPROXY:-https://proxy.golang.org|https://goproxy.cn|direct}" -MAX_ATTEMPTS="${MAX_ATTEMPTS:-3}" - -if ! [[ "$MAX_ATTEMPTS" =~ ^[0-9]+$ ]] || [ "$MAX_ATTEMPTS" -lt 1 ]; then - echo "Invalid MAX_ATTEMPTS=${MAX_ATTEMPTS}." >&2 - exit 1 -fi - -cd "$RELAY_DIR" - -for attempt in $(seq 1 "$MAX_ATTEMPTS"); do - if env GOPROXY="$GOPROXY_VALUE" go mod download; then - exit 0 - fi - - if [ "$attempt" -eq "$MAX_ATTEMPTS" ]; then - exit 1 - fi - - sleep "$((attempt * 10))" -done diff --git a/scripts/ci/release_arch_check.sh b/scripts/ci/release_arch_check.sh deleted file mode 100755 index 37889d3..0000000 --- a/scripts/ci/release_arch_check.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/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/architecture.sh -source "$TOOL_ROOT/scripts/lib/architecture.sh" -ARCH="" -LABEL="" -DERIVED_DATA_PATH="" -APP_OUTPUT_FILE="" -LOG_PATH="" - -while [[ $# -gt 0 ]]; do - case "$1" in - --arch) - ARCH="$2" - shift 2 - ;; - --label) - LABEL="$2" - shift 2 - ;; - --derived-data-path) - DERIVED_DATA_PATH="$2" - shift 2 - ;; - --app-output-file) - APP_OUTPUT_FILE="$2" - shift 2 - ;; - --log-path) - LOG_PATH="$2" - shift 2 - ;; - *) - echo "Unknown argument: $1" >&2 - exit 1 - ;; - esac -done - -[[ -n "$ARCH" ]] || die "--arch is required." -validate_release_arch "$ARCH" -LABEL="${LABEL:-$(release_label_for_arch "$ARCH")}" -require_release_label_for_arch "$ARCH" "$LABEL" - -if [ -z "$DERIVED_DATA_PATH" ]; then - DERIVED_DATA_PATH=".ai-tmp/release-${LABEL}/DerivedData" -fi -DESTINATION="$(xcode_destination_for_arch "$ARCH")" - -cd "$ROOT_DIR" - -LOG_PATH="${LOG_PATH:-.ai-tmp/release-${LABEL}/xcode-release-build.log}" -mkdir -p "$(dirname "$LOG_PATH")" - -env ROOT_DIR="$ROOT_DIR" TOOL_ROOT="$TOOL_ROOT" bash "$TOOL_ROOT/scripts/ci/download_relay_modules.sh" - -set +e -xcodebuild \ - -scheme VoidDisplay \ - -project Apps/VoidDisplay/VoidDisplay.xcodeproj \ - -configuration Release \ - -destination "$DESTINATION" \ - -derivedDataPath "$DERIVED_DATA_PATH" \ - -skipPackageUpdates \ - -onlyUsePackageVersionsFromResolvedFile \ - ROOT_DIR="$ROOT_DIR" TOOL_ROOT="$TOOL_ROOT" \ - CODE_SIGNING_ALLOWED=NO \ - CODE_SIGNING_REQUIRED=NO \ - ARCHS="$ARCH" \ - build \ - 2>&1 | tee "$LOG_PATH" -build_status=${PIPESTATUS[0]} -set -e - -if [ "$build_status" -ne 0 ]; then - echo "xcodebuild Release exited with non-zero status: $build_status. Log: $LOG_PATH" >&2 - exit "$build_status" -fi - -scan_xcode_log_for_diagnostics "Xcode Release build" "$LOG_PATH" - -app_path="${DERIVED_DATA_PATH}/Build/Products/Release/VoidDisplay.app" -if [ ! -d "$app_path" ]; then - echo "Expected app not found: $app_path" >&2 - exit 1 -fi - -env ROOT_DIR="$ROOT_DIR" TOOL_ROOT="$TOOL_ROOT" bash "$TOOL_ROOT/scripts/release/thin_webrtc_and_sign.sh" "$app_path" "$ARCH" - -if [ -n "$APP_OUTPUT_FILE" ]; then - mkdir -p "$(dirname "$APP_OUTPUT_FILE")" - printf '%s\n' "$app_path" >"$APP_OUTPUT_FILE" -fi - -echo "Release app path: $app_path" diff --git a/scripts/ci/release_smoke.sh b/scripts/ci/release_smoke.sh index 0687eeb..5752f5f 100755 --- a/scripts/ci/release_smoke.sh +++ b/scripts/ci/release_smoke.sh @@ -22,6 +22,7 @@ OUT_DIR="$(make_artifact_dir ci-release-smoke)" DERIVED_DATA_PATH="" APP_OUTPUT_FILE="" SUMMARY_PATH="" +LOG_PATH="" while [[ $# -gt 0 ]]; do case "$1" in @@ -63,24 +64,47 @@ mkdir -p "$OUT_DIR" DERIVED_DATA_PATH="${DERIVED_DATA_PATH:-$OUT_DIR/DerivedData}" APP_OUTPUT_FILE="${APP_OUTPUT_FILE:-$OUT_DIR/app-path.txt}" SUMMARY_PATH="${SUMMARY_PATH:-$OUT_DIR/release-smoke-summary.json}" +LOG_PATH="$OUT_DIR/xcode-release-build.log" select_required_xcode +go_mod_download_with_retry "$ROOT_DIR/Tools/VoidDisplayRelay" -env ROOT_DIR="$ROOT_DIR" TOOL_ROOT="$TOOL_ROOT" bash "$TOOL_ROOT/scripts/ci/release_arch_check.sh" \ - --arch "$ARCH" \ - --label "$LABEL" \ - --derived-data-path "$DERIVED_DATA_PATH" \ - --app-output-file "$APP_OUTPUT_FILE" \ - --log-path "$OUT_DIR/xcode-release-build.log" +set +e +xcodebuild \ + -scheme VoidDisplay \ + -project Apps/VoidDisplay/VoidDisplay.xcodeproj \ + -configuration Release \ + -destination "$(xcode_destination_for_arch "$ARCH")" \ + -derivedDataPath "$DERIVED_DATA_PATH" \ + -skipPackageUpdates \ + -onlyUsePackageVersionsFromResolvedFile \ + ROOT_DIR="$ROOT_DIR" TOOL_ROOT="$TOOL_ROOT" \ + CODE_SIGNING_ALLOWED=NO \ + CODE_SIGNING_REQUIRED=NO \ + ARCHS="$ARCH" \ + build \ + 2>&1 | tee "$LOG_PATH" +build_status=${PIPESTATUS[0]} +set -e -app_path="$(cat "$APP_OUTPUT_FILE")" -validate_release_app_binaries "$app_path" "$ARCH" +if [[ "$build_status" -ne 0 ]]; then + die "xcodebuild Release exited with non-zero status: $build_status. Log: $LOG_PATH" +fi + +scan_xcode_log_for_diagnostics "Xcode Release build" "$LOG_PATH" + +app_path="${DERIVED_DATA_PATH}/Build/Products/Release/VoidDisplay.app" +[[ -d "$app_path" ]] || die "Expected app not found: $app_path" +thin_and_sign_release_app "$app_path" "$ARCH" + +mkdir -p "$(dirname "$APP_OUTPUT_FILE")" +printf '%s\n' "$app_path" >"$APP_OUTPUT_FILE" write_json_file "$SUMMARY_PATH" \ --arg status "passed" \ --arg arch "$ARCH" \ --arg label "$LABEL" \ --arg app_path "$app_path" \ - --arg log_path "$OUT_DIR/xcode-release-build.log" \ + --arg log_path "$LOG_PATH" \ '{status: $status, arch: $arch, label: $label, app_path: $app_path, log_path: $log_path}' info "Release smoke passed for $LABEL." diff --git a/scripts/ci/static.sh b/scripts/ci/static.sh index 995d62a..0263a27 100755 --- a/scripts/ci/static.sh +++ b/scripts/ci/static.sh @@ -136,9 +136,6 @@ validate_workflow_script_contract() { workflow_files+=("$workflow_file") done < <(find .github/workflows .github/actions -type f \( -name '*.yml' -o -name '*.yaml' \) -print | sort) - assert_no_match "Trusted/base CI script model must be removed." \ - '\.ai-tmp/trusted-ci|head-script-self-test|requires_head_script_self_test|trusted_files|test_trusted_files|Checkout trusted' \ - .github/workflows .github/actions scripts --glob '!scripts/ci/static.sh' assert_no_match "Workflow script invocations must execute scripts through TOOL_ROOT." \ '\$GITHUB_WORKSPACE/scripts/' .github/workflows .github/actions diff --git a/scripts/ci/ui_smoke.sh b/scripts/ci/ui_smoke.sh index a10e7b6..546b8e4 100755 --- a/scripts/ci/ui_smoke.sh +++ b/scripts/ci/ui_smoke.sh @@ -101,7 +101,7 @@ write_summary() { select_required_xcode require_command jq go -env ROOT_DIR="$ROOT_DIR" TOOL_ROOT="$TOOL_ROOT" bash "$TOOL_ROOT/scripts/ci/download_relay_modules.sh" +go_mod_download_with_retry "$ROOT_DIR/Tools/VoidDisplayRelay" last_reason="not_run" last_log_file="" diff --git a/scripts/lib/common.sh b/scripts/lib/common.sh index 91dd105..9343d19 100755 --- a/scripts/lib/common.sh +++ b/scripts/lib/common.sh @@ -75,6 +75,13 @@ if [[ -z "${VOIDDISPLAY_COMMON_SH_SOURCED:-}" ]]; then done } + go_mod_download_with_retry() { + local module_dir="$1" + local goproxy_value="${GOPROXY:-https://proxy.golang.org|https://goproxy.cn|direct}" + + (cd "$module_dir" && run_with_retry 3 env GOPROXY="$goproxy_value" go mod download) + } + collect_build_log_diagnostics() { local log_path="$1" diff --git a/scripts/lib/release_binaries.sh b/scripts/lib/release_binaries.sh index 01bf590..040ecab 100644 --- a/scripts/lib/release_binaries.sh +++ b/scripts/lib/release_binaries.sh @@ -79,4 +79,36 @@ if [[ -z "${VOIDDISPLAY_RELEASE_BINARIES_SH_SOURCED:-}" ]]; then require_binary_arch "WebRTC" "$webrtc_binary" "$expected_arch" require_binary_arch "Relay" "$relay_binary" "$expected_arch" } + + thin_and_sign_release_app() { + local app_path="$1" + local expected_arch="$2" + local webrtc_framework="$app_path/Contents/Frameworks/WebRTC.framework" + local webrtc_binary + local temp_binary + local relay_binary="$app_path/Contents/Resources/voiddisplay-relay" + + [[ -d "$app_path" ]] || die "Expected app not found: $app_path" + [[ -x "$relay_binary" ]] || die "Expected relay binary to be executable: $relay_binary" + webrtc_binary="$(resolve_webrtc_binary "$webrtc_framework")" + + info "WebRTC binary before thin: $webrtc_binary" + lipo -archs "$webrtc_binary" + temp_binary="$webrtc_binary.thin" + lipo -thin "$expected_arch" "$webrtc_binary" -output "$temp_binary" + mv "$temp_binary" "$webrtc_binary" + chmod +x "$webrtc_binary" + info "WebRTC binary after thin:" + lipo -archs "$webrtc_binary" + + require_binary_arch "Relay" "$relay_binary" "$expected_arch" + info "Applying ad hoc signature for local release packaging. Developer ID signing and notarization are not configured." + codesign --force --sign - --timestamp=none "$relay_binary" + codesign --force --sign - --timestamp=none "$webrtc_framework" + codesign --force --sign - --timestamp=none --deep "$app_path" + codesign --verify --deep --strict --verbose=2 "$app_path" + codesign --verify --strict --verbose=2 "$relay_binary" + + validate_release_app_binaries "$app_path" "$expected_arch" + } fi diff --git a/scripts/release/build.sh b/scripts/release/build.sh index f834e3c..82b5ccd 100755 --- a/scripts/release/build.sh +++ b/scripts/release/build.sh @@ -53,7 +53,6 @@ dmg_name="$(release_dmg_name "$TAG" "$LABEL")" dmg_path="$OUT_DIR/release-assets/$dmg_name" sbom_path="$OUT_DIR/release-assets/$dmg_name.spdx.json" summary_path="$OUT_DIR/release-assets/$dmg_name.summary.json" -dmg_summary_path="$OUT_DIR/release-assets/$dmg_name.create-dmg-summary.json" release_stage="initialization" checksum="" release_summary_written="false" @@ -114,13 +113,11 @@ app_path="$(cat "$app_output")" release_stage="create_dmg" if ! env ROOT_DIR="$ROOT_DIR" TOOL_ROOT="$TOOL_ROOT" "$TOOL_ROOT/scripts/release/create_dmg.sh" \ - --summary "$dmg_summary_path" \ "$app_path" \ "$dmg_path" \ "VoidDisplay"; then - dmg_reason="$(jq -r '.reason // "dmg_failed"' "$dmg_summary_path" 2>/dev/null || printf 'dmg_failed\n')" - write_release_build_summary "failed" "$dmg_reason" "DMG creation failed. See $(basename "$dmg_summary_path")." - die "DMG creation failed: $dmg_reason" + write_release_build_summary "failed" "dmg_failed" "DMG creation failed." + die "DMG creation failed." fi release_stage="checksum" diff --git a/scripts/release/create_dmg.sh b/scripts/release/create_dmg.sh index e7d43b7..63d7f49 100755 --- a/scripts/release/create_dmg.sh +++ b/scripts/release/create_dmg.sh @@ -5,32 +5,20 @@ set -euo pipefail source "${BASH_SOURCE[0]%/*}/../lib/contract.sh" # shellcheck source=scripts/lib/common.sh source "$TOOL_ROOT/scripts/lib/common.sh" -# shellcheck source=scripts/lib/artifacts.sh -source "$TOOL_ROOT/scripts/lib/artifacts.sh" # shellcheck source=scripts/lib/release.sh source "$TOOL_ROOT/scripts/lib/release.sh" usage() { cat <<'EOF' -Usage: create_dmg.sh [--summary ] +Usage: create_dmg.sh EOF } -SUMMARY_PATH="" POSITIONAL_ARGS=() while [[ $# -gt 0 ]]; do - case "$1" in - --summary) - [[ $# -ge 2 && -n "${2:-}" ]] || die "--summary requires a path." - SUMMARY_PATH="$(normalize_path "$2")" - shift 2 - ;; - *) - POSITIONAL_ARGS+=("$1") - shift - ;; - esac + POSITIONAL_ARGS+=("$1") + shift done if [ "${#POSITIONAL_ARGS[@]}" -ne 3 ]; then @@ -43,35 +31,8 @@ output_dmg="${POSITIONAL_ARGS[1]}" volume_name="${POSITIONAL_ARGS[2]}" stage="argument_validation" -if [[ -n "$SUMMARY_PATH" ]]; then - require_command jq -fi - -write_dmg_summary() { - local status="$1" - local reason="$2" - local detail="$3" - - [[ -n "$SUMMARY_PATH" ]] || return 0 - write_json_file "$SUMMARY_PATH" \ - --arg status "$status" \ - --arg reason "$reason" \ - --arg detail "$detail" \ - --arg stage "$stage" \ - --arg app_path "$app_path" \ - --arg output_dmg "$output_dmg" \ - --arg volume_name "$volume_name" \ - '{status: $status, reason: $reason, detail: $detail, stage: $stage, app_path: $app_path, output_dmg: $output_dmg, volume_name: $volume_name}' -} - fail_dmg() { - local reason="$1" - local detail="$2" - local exit_code="${3:-1}" - - write_dmg_summary "failed" "$reason" "$detail" - echo "$detail" >&2 - exit "$exit_code" + die "$1" } handle_unexpected_dmg_error() { @@ -79,14 +40,14 @@ handle_unexpected_dmg_error() { local line_number="$2" trap - ERR - write_dmg_summary "failed" "${stage}_failed" "DMG creation failed unexpectedly at line ${line_number}." + printf '[ERROR] DMG creation failed in stage %s at line %s.\n' "$stage" "$line_number" >&2 exit "$exit_code" } trap 'handle_unexpected_dmg_error $? $LINENO' ERR if [ ! -d "${app_path}" ]; then - fail_dmg "missing_app" "App bundle not found: ${app_path}" + fail_dmg "App bundle not found: ${app_path}" fi output_dir="$(dirname "${output_dmg}")" @@ -114,26 +75,26 @@ mkdir -p "${background_dir}" # Render the DMG background at build time from a fixed template. stage="render_background" if ! swift "$TOOL_ROOT/scripts/release/render_dmg_background.swift" "${background_path}"; then - fail_dmg "render_background_failed" "Failed to render DMG background image." + fail_dmg "Failed to render DMG background image." fi stage="stage_payload" if ! cp -R "${app_path}" "${stage_dir}/"; then - fail_dmg "stage_payload_failed" "Failed to copy app bundle into DMG staging directory." + fail_dmg "Failed to copy app bundle into DMG staging directory." fi if ! ln -s /Applications "${stage_dir}/Applications"; then - fail_dmg "stage_payload_failed" "Failed to create Applications symlink in DMG staging directory." + fail_dmg "Failed to create Applications symlink in DMG staging directory." fi if ! SetFile -a V "${background_dir}"; then - fail_dmg "dmg_metadata_failed" "Failed to hide DMG background directory with SetFile." + fail_dmg "Failed to hide DMG background directory with SetFile." fi stage="dmg_size" if ! app_kb="$(du -sk "${app_path}" | awk '{print $1}')"; then - fail_dmg "dmg_size_failed" "Failed to measure app bundle size." + fail_dmg "Failed to measure app bundle size." fi if ! background_kb="$(du -sk "${background_path}" | awk '{print $1}')"; then - fail_dmg "dmg_size_failed" "Failed to measure DMG background size." + fail_dmg "Failed to measure DMG background size." fi size_kb="$((app_kb + background_kb + 65536))" @@ -147,7 +108,7 @@ if ! hdiutil create \ -size "${size_kb}k" \ -ov \ "${rw_dmg}"; then - fail_dmg "hdiutil_failed" "Failed to create writable DMG." + fail_dmg "Failed to create writable DMG." fi stage="hdiutil_attach" @@ -156,13 +117,13 @@ attach_output="$(hdiutil attach -readwrite -noverify -noautoopen "${rw_dmg}" 2>& attach_status="$?" set -e if [ "$attach_status" -ne 0 ]; then - fail_dmg "hdiutil_failed" "Failed to attach writable DMG: ${attach_output}" + fail_dmg "Failed to attach writable DMG: ${attach_output}" fi device="$(printf '%s\n' "${attach_output}" | release_parse_attach_device)" mount_path="$(printf '%s\n' "${attach_output}" | release_parse_attach_mount_path)" if [ -z "${device}" ] || [ -z "${mount_path}" ]; then - fail_dmg "hdiutil_failed" "Failed to locate mounted DMG device or mount path: ${attach_output}" + fail_dmg "Failed to locate mounted DMG device or mount path: ${attach_output}" fi mounted_volume_name="$(basename "${mount_path}")" @@ -172,9 +133,11 @@ layout_exit_code=0 release_run_with_timeout 45 osascript "$TOOL_ROOT/scripts/release/apply_dmg_layout.applescript" "${mounted_volume_name}" "${app_name}" || layout_exit_code="$?" if [ "${layout_exit_code}" -ne 0 ]; then if [ "${layout_exit_code}" -eq 124 ]; then - fail_dmg "dmg_layout_timeout" "DMG layout timed out after 45 seconds." 124 + printf '[ERROR] DMG layout timed out after 45 seconds.\n' >&2 + exit 124 else - fail_dmg "dmg_layout_failed" "DMG layout failed with exit code ${layout_exit_code}." "${layout_exit_code}" + printf '[ERROR] DMG layout failed with exit code %s.\n' "${layout_exit_code}" >&2 + exit "${layout_exit_code}" fi fi @@ -188,8 +151,5 @@ device="" rm -f "${output_dmg}" stage="hdiutil_convert" if ! hdiutil convert "${rw_dmg}" -format UDZO -imagekey zlib-level=9 -ov -o "${output_dmg}"; then - fail_dmg "hdiutil_failed" "Failed to convert writable DMG to compressed DMG." + fail_dmg "Failed to convert writable DMG to compressed DMG." fi - -stage="completed" -write_dmg_summary "passed" "passed" "DMG created successfully." diff --git a/scripts/release/thin_webrtc_and_sign.sh b/scripts/release/thin_webrtc_and_sign.sh deleted file mode 100644 index 78acf4d..0000000 --- a/scripts/release/thin_webrtc_and_sign.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/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/architecture.sh -source "$TOOL_ROOT/scripts/lib/architecture.sh" -# shellcheck source=scripts/lib/release_binaries.sh -source "$TOOL_ROOT/scripts/lib/release_binaries.sh" - -usage() { - cat <<'EOF' -Usage: thin_webrtc_and_sign.sh -EOF -} - -if [ "$#" -ne 2 ]; then - usage >&2 - exit 1 -fi - -app_path="$1" -target_arch="$2" -validate_release_arch "$target_arch" -webrtc_framework="${app_path}/Contents/Frameworks/WebRTC.framework" -root_webrtc_binary="${webrtc_framework}/WebRTC" -root_webrtc_was_symlink=false -relay_binary="${app_path}/Contents/Resources/voiddisplay-relay" - -if [ ! -d "${app_path}" ]; then - echo "Expected app not found: ${app_path}" >&2 - exit 1 -fi - -if [ ! -d "${webrtc_framework}" ]; then - echo "Expected WebRTC framework not found: ${webrtc_framework}" >&2 - exit 1 -fi - -if [ ! -f "${relay_binary}" ]; then - echo "Expected relay binary not found: ${relay_binary}" >&2 - exit 1 -fi - -if [ ! -x "${relay_binary}" ]; then - echo "Expected relay binary to be executable: ${relay_binary}" >&2 - exit 1 -fi - -if [ -L "${root_webrtc_binary}" ]; then - root_webrtc_was_symlink=true -fi - -webrtc_binary="$(resolve_webrtc_binary "${webrtc_framework}")" -if [ -z "${webrtc_binary}" ] || [ ! -f "${webrtc_binary}" ]; then - echo "Failed to locate WebRTC binary under: ${webrtc_framework}" >&2 - exit 1 -fi - -webrtc_binary_real="${webrtc_binary}" -if [ -L "${webrtc_binary}" ]; then - link_target="$(readlink "${webrtc_binary}")" - if [ -z "${link_target}" ]; then - echo "Failed to resolve symlink target: ${webrtc_binary}" >&2 - exit 1 - fi - - if [[ "${link_target}" = /* ]]; then - webrtc_binary_real="${link_target}" - else - webrtc_binary_real="$(cd "$(dirname "${webrtc_binary}")" && pwd)/${link_target}" - fi -fi - -if [ ! -f "${webrtc_binary_real}" ]; then - echo "Resolved WebRTC binary does not exist: ${webrtc_binary_real}" >&2 - exit 1 -fi - -echo "WebRTC binary before thin: ${webrtc_binary_real}" -lipo -archs "${webrtc_binary_real}" -temp_binary="${webrtc_binary_real}.thin" -lipo -thin "${target_arch}" "${webrtc_binary_real}" -output "${temp_binary}" -mv "${temp_binary}" "${webrtc_binary_real}" -chmod +x "${webrtc_binary_real}" -echo "WebRTC binary after thin:" -lipo -archs "${webrtc_binary_real}" - -if [ "${root_webrtc_was_symlink}" = true ] && [ ! -L "${root_webrtc_binary}" ]; then - echo "Expected ${root_webrtc_binary} to remain a symlink after thinning." >&2 - exit 1 -fi - -require_binary_arch "Relay" "${relay_binary}" "${target_arch}" - -echo "Applying ad hoc signature for local release packaging. Developer ID signing and notarization are not configured." -codesign --force --sign - --timestamp=none "${relay_binary}" -codesign --force --sign - --timestamp=none "${webrtc_framework}" -codesign --force --sign - --timestamp=none --deep "${app_path}" -codesign --verify --deep --strict --verbose=2 "${app_path}" -codesign --verify --strict --verbose=2 "${relay_binary}" - -validate_release_app_binaries "${app_path}" "${target_arch}"