diff --git a/Makefile b/Makefile index 89013dda..ad713e88 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ DART ?= dart DEVICE ?= macos APP_STORE_DART_DEFINE ?= --dart-define=XWORKMATE_APP_STORE=true -.PHONY: help deps analyze test check format run build-linux build-macos build-ios-sim package-deb package-rpm package-linux package-mac install-mac clean build-go-core render-release-docs +.PHONY: help deps analyze test check format run build-linux build-macos build-ios-sim package-deb package-rpm package-linux package-mac install-mac clean build-go-core render-release-docs check-export-compliance help: ## Show available targets @grep -E '^[a-zA-Z0-9_.-]+:.*?## ' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "%-18s %s\n", $$1, $$2}' @@ -38,9 +38,11 @@ build-linux: ## Build the Linux app in release mode build-macos: ## Build the macOS app in release mode $(FLUTTER) build macos --release $(APP_STORE_DART_DEFINE) + bash scripts/check-apple-export-compliance.sh build/macos/Build/Products/Release/XWorkmate.app build-ios-sim: ## Build the iOS app for the simulator $(FLUTTER) build ios --simulator $(APP_STORE_DART_DEFINE) + bash scripts/check-apple-export-compliance.sh build/ios/iphonesimulator/Runner.app build-go-core: ## Build the Go core helper bash scripts/build-go-core.sh @@ -65,6 +67,9 @@ clean: ## Remove generated artifacts $(FLUTTER) clean rm -rf build dist +check-export-compliance: ## Verify source and built Apple plist export-compliance flags + bash scripts/check-apple-export-compliance.sh + # Rust FFI targets .PHONY: rust-build rust-build-release rust-build-debug rust-test ffi-copy ffi-generate diff --git a/scripts/check-apple-export-compliance.sh b/scripts/check-apple-export-compliance.sh new file mode 100755 index 00000000..0565783c --- /dev/null +++ b/scripts/check-apple-export-compliance.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +IOS_SOURCE_PLIST="$ROOT_DIR/ios/Runner/Info.plist" +MACOS_SOURCE_PLIST="$ROOT_DIR/macos/Runner/Info.plist" +TARGET_PATH="${1:-}" + +usage() { + cat <<'EOF' +Usage: + scripts/check-apple-export-compliance.sh [artifact-path] + +Without an artifact path, the script validates the source plist values for iOS +and macOS. With an artifact path, it also validates the built bundle/archive: + +Supported artifact paths: + - *.xcarchive + - *.app + - path to an Info.plist file +EOF +} + +if [[ "${TARGET_PATH:-}" == "-h" || "${TARGET_PATH:-}" == "--help" ]]; then + usage + exit 0 +fi + +read_bool() { + local plist_path="$1" + local key="$2" + python3 - "$plist_path" "$key" <<'PY' +import plistlib +import sys + +plist_path = sys.argv[1] +key = sys.argv[2] + +with open(plist_path, "rb") as handle: + data = plistlib.load(handle) + +value = data.get(key, None) +if value is None: + print("__MISSING__") +elif value is True: + print("true") +elif value is False: + print("false") +else: + print(str(value).strip().lower()) +PY +} + +resolve_artifact_plist() { + local input_path="$1" + if [[ -f "$input_path" && "$(basename "$input_path")" == "Info.plist" ]]; then + printf '%s\n' "$input_path" + return 0 + fi + if [[ -d "$input_path" && "$input_path" == *.xcarchive ]]; then + local plist_path="" + plist_path="$(find "$input_path/Products/Applications" -maxdepth 3 -name Info.plist | head -n 1)" + if [[ -n "$plist_path" ]]; then + printf '%s\n' "$plist_path" + return 0 + fi + fi + if [[ -d "$input_path" && "$input_path" == *.app ]]; then + if [[ -f "$input_path/Contents/Info.plist" ]]; then + printf '%s\n' "$input_path/Contents/Info.plist" + return 0 + fi + if [[ -f "$input_path/Info.plist" ]]; then + printf '%s\n' "$input_path/Info.plist" + return 0 + fi + fi + return 1 +} + +report_value() { + local label="$1" + local plist_path="$2" + local value="$3" + echo "$label: $plist_path" + echo " ITSAppUsesNonExemptEncryption = $value" +} + +assert_false() { + local label="$1" + local plist_path="$2" + local value="$3" + if [[ "$value" != "false" ]]; then + echo "Export compliance check failed for $label" >&2 + report_value "$label" "$plist_path" "$value" >&2 + return 1 + fi +} + +source_ios_value="$(read_bool "$IOS_SOURCE_PLIST" ITSAppUsesNonExemptEncryption)" +source_macos_value="$(read_bool "$MACOS_SOURCE_PLIST" ITSAppUsesNonExemptEncryption)" + +assert_false "iOS source" "$IOS_SOURCE_PLIST" "$source_ios_value" +assert_false "macOS source" "$MACOS_SOURCE_PLIST" "$source_macos_value" + +report_value "iOS source" "$IOS_SOURCE_PLIST" "$source_ios_value" +report_value "macOS source" "$MACOS_SOURCE_PLIST" "$source_macos_value" + +if [[ -z "$TARGET_PATH" ]]; then + exit 0 +fi + +ARTIFACT_PLIST="$(resolve_artifact_plist "$TARGET_PATH" || true)" +if [[ -z "$ARTIFACT_PLIST" ]]; then + echo "Unsupported artifact path: $TARGET_PATH" >&2 + usage >&2 + exit 2 +fi + +artifact_value="$(read_bool "$ARTIFACT_PLIST" ITSAppUsesNonExemptEncryption)" +report_value "Artifact" "$ARTIFACT_PLIST" "$artifact_value" +assert_false "Artifact" "$ARTIFACT_PLIST" "$artifact_value" + +echo "Export compliance check passed." diff --git a/scripts/package-flutter-mac-app.sh b/scripts/package-flutter-mac-app.sh index c8221a98..e351ea45 100755 --- a/scripts/package-flutter-mac-app.sh +++ b/scripts/package-flutter-mac-app.sh @@ -69,6 +69,9 @@ if [[ ! -d "$BUILD_APP_PATH" ]]; then exit 1 fi +echo "Validating export compliance metadata..." +bash "$ROOT_DIR/scripts/check-apple-export-compliance.sh" "$BUILD_APP_PATH" + rm -rf "$DIST_APP_PATH" "$DIST_DMG_PATH" ditto "$BUILD_APP_PATH" "$DIST_APP_PATH" mkdir -p "$HELPERS_DIR" diff --git a/scripts/package-ios-ipa.sh b/scripts/package-ios-ipa.sh index b4f939a8..e15ac761 100755 --- a/scripts/package-ios-ipa.sh +++ b/scripts/package-ios-ipa.sh @@ -65,6 +65,11 @@ sed "s|\${EXPORT_METHOD}|$export_method|g" "$root_dir/ios/ExportOptions.plist" > flutter pub get flutter build ipa --release --export-options-plist="$export_options_path" +archive_path="$root_dir/build/ios/archive/Runner.xcarchive" +if [[ -d "$archive_path" ]]; then + bash "$root_dir/scripts/check-apple-export-compliance.sh" "$archive_path" +fi + find "$root_dir/build/ios/ipa" -maxdepth 1 -name '*.ipa' -exec cp {} "$dist_dir/" \; if ! compgen -G "$dist_dir/*.ipa" >/dev/null; then