From f820fec2c0718d11fb97035b4fe27211be0f51ec Mon Sep 17 00:00:00 2001 From: itsjoshpark Date: Sat, 31 Oct 2026 14:14:01 -0400 Subject: [PATCH] build(project): convert to the JSON project format Xcode 27.2 is now required to build, on macOS Tahoe 26.6 or later. --- .github/workflows/ci.yml | 10 +- .github/workflows/release.yml | 22 +- AGENTS.md | 1 + Front Row.xcodeproj/project.pbxproj | 639 ---------------------------- Front Row.xcodeproj/project.xcproj | 248 +++++++++++ README.md | 2 +- scripts/project-version.sh | 76 ++-- scripts/project-version.test.sh | 126 +++--- 8 files changed, 375 insertions(+), 749 deletions(-) delete mode 100644 Front Row.xcodeproj/project.pbxproj create mode 100644 Front Row.xcodeproj/project.xcproj diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 528ba74..fa948d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,15 +7,15 @@ on: jobs: build: - runs-on: macos-26 + runs-on: xcode-27 steps: - name: Checkout uses: actions/checkout@v7 - - name: Setup Xcode Version - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: latest-stable + # The project uses Xcode's JSON project format (project.xcproj), which + # needs Xcode 27.2. The xcode-27 image defaults to an older Xcode 27. + - name: Select Xcode 27.2 + run: sudo xcode-select -s /Applications/Xcode_27.2.app - name: Install swift-format and ffmpeg run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b779a96..1d9be20 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,9 +7,9 @@ # The notes go to GitHub as Markdown and to the appcast as HTML, rendered by # cmark-gfm so both read the same. # -# The marketing version is read from project.pbxproj and bumped by the chosen +# The marketing version is read from project.xcproj and bumped by the chosen # amount; the build number is the commit count and is never read back as input. -# Both are passed to xcodebuild, then written back to project.pbxproj in the same +# Both are passed to xcodebuild, then written back to project.xcproj in the same # commit as the appcast — so a local build always reports the last release. # # Nothing outside the runner is touched until notarization succeeds, so a failed @@ -60,7 +60,7 @@ concurrency: jobs: release: name: Release - runs-on: macos-26 + runs-on: xcode-27 permissions: contents: write env: @@ -74,10 +74,10 @@ jobs: # the shipped build number and would strand every existing user. fetch-depth: 0 - - name: Setup Xcode Version - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1 - with: - xcode-version: latest-stable + # The project uses Xcode's JSON project format (project.xcproj), which + # needs Xcode 27.2. The xcode-27 image defaults to an older Xcode 27. + - name: Select Xcode 27.2 + run: sudo xcode-select -s /Applications/Xcode_27.2.app # Build output must stay outside the workspace: the app target's # swift-format lint phase runs `-r ./` from $SRCROOT and would lint SPM's @@ -117,10 +117,10 @@ jobs: exit 1 fi - pbxproj="Front Row.xcodeproj/project.pbxproj" + xcproj="Front Row.xcodeproj/project.xcproj" # The marketing version is authoritative in the project file; the build # number is mechanical and never read back as input. - current="$(./scripts/project-version.sh read-marketing "$pbxproj")" + current="$(./scripts/project-version.sh read-marketing "$xcproj")" version="$(./scripts/bump-version.sh "$current" "${{ inputs.release-type }}")" build="$(git rev-list --count HEAD)" @@ -379,7 +379,7 @@ jobs: # Record what was just released, so local builds report the shipped # version and the next run has a marketing version to bump from. - ./scripts/project-version.sh write "Front Row.xcodeproj/project.pbxproj" \ + ./scripts/project-version.sh write "Front Row.xcodeproj/project.xcproj" \ "$version" "${{ steps.version.outputs.build }}" # Archive the notes under the version they shipped as, and leave a @@ -388,7 +388,7 @@ jobs: git mv "${{ steps.version.outputs.notes }}" "$notes" cp .sparkle/notes/TEMPLATE.md .sparkle/notes/next.md - git add .sparkle/appcast.xml "Front Row.xcodeproj/project.pbxproj" .sparkle/notes + git add .sparkle/appcast.xml "Front Row.xcodeproj/project.xcproj" .sparkle/notes git commit -m "chore: release $version (${{ steps.version.outputs.build }}) [skip ci]" { diff --git a/AGENTS.md b/AGENTS.md index 9c6ce24..ce927b5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,6 +24,7 @@ swift-format format -p -r -i ./ ## Architecture - Target macOS Sequoia 15.6 or later. +- Building needs Xcode 27.2 or later (on macOS Tahoe 26.6 or later). The project uses Xcode's JSON project format, `Front Row.xcodeproj/project.xcproj`. `MARKETING_VERSION` and `CURRENT_PROJECT_VERSION` are project-level settings there, read and written by `scripts/project-version.sh`. - Do not introduce third-party frameworks without asking first. - Group source files by feature, not by layer. A feature folder holds its models and its views together. - A feature that outgrows one folder splits by kind into `Models/`, `Views/`, and any domain subfolder it needs (`Conversion/FFmpeg/`). Small features stay flat. diff --git a/Front Row.xcodeproj/project.pbxproj b/Front Row.xcodeproj/project.pbxproj deleted file mode 100644 index c503666..0000000 --- a/Front Row.xcodeproj/project.pbxproj +++ /dev/null @@ -1,639 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXBuildFile section */ - 03B712272B96C40C00C1F753 /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03B712262B96C40C00C1F753 /* AVKit.framework */; }; - 03C112BF2DB56BFF00AE6799 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 03C112BE2DB56BFF00AE6799 /* Sparkle */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - FADEFEED000000000000000E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 03EA68452B9630CF003348BE /* Project object */; - proxyType = 1; - remoteGlobalIDString = 03EA684C2B9630CF003348BE; - remoteInfo = "Front Row"; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 031457A330329C99001B3B01 /* Front Row UI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Front Row UI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 03B712262B96C40C00C1F753 /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; }; - 03BACB2D2B96C8A800D24F07 /* FrontRowInfo.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = FrontRowInfo.plist; sourceTree = SOURCE_ROOT; }; - 03EA684D2B9630CF003348BE /* Front Row.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Front Row.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - FADEFEED0000000000000001 /* Front Row Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Front Row Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFileSystemSynchronizedRootGroup section */ - 031457A430329C99001B3B01 /* Front Row UI Tests */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = "Front Row UI Tests"; - sourceTree = ""; - }; - FADEFEED0000000000000030 /* Front Row */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = "Front Row"; - sourceTree = ""; - }; - FADEFEED0000000000000031 /* Front Row Tests */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = "Front Row Tests"; - sourceTree = ""; - }; -/* End PBXFileSystemSynchronizedRootGroup section */ - -/* Begin PBXFrameworksBuildPhase section */ - 031457A030329C99001B3B01 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 03EA684A2B9630CF003348BE /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 03B712272B96C40C00C1F753 /* AVKit.framework in Frameworks */, - 03C112BF2DB56BFF00AE6799 /* Sparkle in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - FADEFEED000000000000000B /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 03B712252B96C40C00C1F753 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 03B712262B96C40C00C1F753 /* AVKit.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 03EA68442B9630CF003348BE = { - isa = PBXGroup; - children = ( - 03BACB2D2B96C8A800D24F07 /* FrontRowInfo.plist */, - 031457A430329C99001B3B01 /* Front Row UI Tests */, - 03B712252B96C40C00C1F753 /* Frameworks */, - FADEFEED0000000000000030 /* Front Row */, - FADEFEED0000000000000031 /* Front Row Tests */, - 03EA684E2B9630CF003348BE /* Products */, - ); - sourceTree = ""; - }; - 03EA684E2B9630CF003348BE /* Products */ = { - isa = PBXGroup; - children = ( - 03EA684D2B9630CF003348BE /* Front Row.app */, - FADEFEED0000000000000001 /* Front Row Tests.xctest */, - 031457A330329C99001B3B01 /* Front Row UI Tests.xctest */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 031457A230329C99001B3B01 /* Front Row UI Tests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 031457A730329C99001B3B01 /* Build configuration list for PBXNativeTarget "Front Row UI Tests" */; - buildPhases = ( - 0314579F30329C99001B3B01 /* Sources */, - 031457A030329C99001B3B01 /* Frameworks */, - 031457A130329C99001B3B01 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - fileSystemSynchronizedGroups = ( - 031457A430329C99001B3B01 /* Front Row UI Tests */, - ); - name = "Front Row UI Tests"; - packageProductDependencies = ( - ); - productName = "Front Row UI Tests"; - productReference = 031457A330329C99001B3B01 /* Front Row UI Tests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; - 03EA684C2B9630CF003348BE /* Front Row */ = { - isa = PBXNativeTarget; - buildConfigurationList = 03EA685C2B9630D0003348BE /* Build configuration list for PBXNativeTarget "Front Row" */; - buildPhases = ( - 0328BCEA2BA12090004B5AE0 /* ShellScript */, - 03EA68492B9630CF003348BE /* Sources */, - 03EA684A2B9630CF003348BE /* Frameworks */, - 03EA684B2B9630CF003348BE /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - fileSystemSynchronizedGroups = ( - FADEFEED0000000000000030 /* Front Row */, - ); - name = "Front Row"; - packageProductDependencies = ( - 03C112BE2DB56BFF00AE6799 /* Sparkle */, - ); - productName = "Front Row"; - productReference = 03EA684D2B9630CF003348BE /* Front Row.app */; - productType = "com.apple.product-type.application"; - }; - FADEFEED0000000000000009 /* Front Row Tests */ = { - isa = PBXNativeTarget; - buildConfigurationList = FADEFEED0000000000000011 /* Build configuration list for PBXNativeTarget "Front Row Tests" */; - buildPhases = ( - FADEFEED000000000000000A /* Sources */, - FADEFEED000000000000000B /* Frameworks */, - FADEFEED000000000000000C /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - FADEFEED000000000000000D /* PBXTargetDependency */, - ); - fileSystemSynchronizedGroups = ( - FADEFEED0000000000000031 /* Front Row Tests */, - ); - name = "Front Row Tests"; - productName = "Front Row Tests"; - productReference = FADEFEED0000000000000001 /* Front Row Tests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 03EA68452B9630CF003348BE /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 2700; - LastUpgradeCheck = 2630; - TargetAttributes = { - 031457A230329C99001B3B01 = { - CreatedOnToolsVersion = 27.0; - }; - 03EA684C2B9630CF003348BE = { - CreatedOnToolsVersion = 15.2; - }; - FADEFEED0000000000000009 = { - CreatedOnToolsVersion = 26.3; - TestTargetID = 03EA684C2B9630CF003348BE; - }; - }; - }; - buildConfigurationList = 03EA68482B9630CF003348BE /* Build configuration list for PBXProject "Front Row" */; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ar, - "zh-Hans", - "zh-Hant", - cs, - da, - nl, - fi, - fr, - de, - el, - he, - hu, - it, - ja, - ko, - pl, - ro, - ru, - es, - sv, - tr, - uk, - vi, - ); - mainGroup = 03EA68442B9630CF003348BE; - minimizedProjectReferenceProxies = 1; - packageReferences = ( - 03C112BD2DB56BFF00AE6799 /* XCRemoteSwiftPackageReference "Sparkle" */, - ); - preferredProjectObjectVersion = 77; - productRefGroup = 03EA684E2B9630CF003348BE /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 03EA684C2B9630CF003348BE /* Front Row */, - FADEFEED0000000000000009 /* Front Row Tests */, - 031457A230329C99001B3B01 /* Front Row UI Tests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 031457A130329C99001B3B01 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 03EA684B2B9630CF003348BE /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - FADEFEED000000000000000C /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 0328BCEA2BA12090004B5AE0 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nif which swift-format > /dev/null; then\n swift-format lint -s -p -r ./\nelse\n echo \"warning: swift-format not installed, download from https://github.com/apple/swift-format\"\nfi\n"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 0314579F30329C99001B3B01 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 03EA68492B9630CF003348BE /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - FADEFEED000000000000000A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - FADEFEED000000000000000D /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 03EA684C2B9630CF003348BE /* Front Row */; - targetProxy = FADEFEED000000000000000E /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 031457A830329C99001B3B01 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CODE_SIGN_STYLE = Automatic; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 15.6; - PRODUCT_BUNDLE_IDENTIFIER = dev.joshuapark.FrontRowUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - STRING_CATALOG_GENERATE_SYMBOLS = NO; - SUPPORTED_PLATFORMS = macosx; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 6.0; - }; - name = Debug; - }; - 031457A930329C99001B3B01 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CODE_SIGN_STYLE = Automatic; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 15.6; - PRODUCT_BUNDLE_IDENTIFIER = dev.joshuapark.FrontRowUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - STRING_CATALOG_GENERATE_SYMBOLS = NO; - SUPPORTED_PLATFORMS = macosx; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 6.0; - }; - name = Release; - }; - 03EA685A2B9630D0003348BE /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = arm64; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 275; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = TCQ6328PP6; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 15.6; - MARKETING_VERSION = 3.1.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 6.0; - }; - name = Debug; - }; - 03EA685B2B9630D0003348BE /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = arm64; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 275; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = TCQ6328PP6; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 15.6; - MARKETING_VERSION = 3.1.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = macosx; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_VERSION = 6.0; - }; - name = Release; - }; - 03EA685D2B9630D0003348BE /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = "Front Row/FrontRow.entitlements"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - ENABLE_APP_SANDBOX = NO; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; - ENABLE_PREVIEWS = YES; - ENABLE_USER_SELECTED_FILES = readonly; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = FrontRowInfo.plist; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.video"; - INFOPLIST_KEY_NSHumanReadableCopyright = "By Joshua Park"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = dev.joshuapark.FrontRow; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - }; - name = Debug; - }; - 03EA685E2B9630D0003348BE /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = "Front Row/FrontRow.entitlements"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - ENABLE_APP_SANDBOX = NO; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; - ENABLE_PREVIEWS = YES; - ENABLE_USER_SELECTED_FILES = readonly; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = FrontRowInfo.plist; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.video"; - INFOPLIST_KEY_NSHumanReadableCopyright = "By Joshua Park"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = dev.joshuapark.FrontRow; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - }; - name = Release; - }; - FADEFEED000000000000000F /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = TCQ6328PP6; - GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 15.6; - PRODUCT_BUNDLE_IDENTIFIER = dev.joshuapark.FrontRowTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 6.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Front Row.app/Contents/MacOS/Front Row"; - }; - name = Debug; - }; - FADEFEED0000000000000010 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = TCQ6328PP6; - GENERATE_INFOPLIST_FILE = YES; - MACOSX_DEPLOYMENT_TARGET = 15.6; - PRODUCT_BUNDLE_IDENTIFIER = dev.joshuapark.FrontRowTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 6.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Front Row.app/Contents/MacOS/Front Row"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 031457A730329C99001B3B01 /* Build configuration list for PBXNativeTarget "Front Row UI Tests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 031457A830329C99001B3B01 /* Debug */, - 031457A930329C99001B3B01 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 03EA68482B9630CF003348BE /* Build configuration list for PBXProject "Front Row" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 03EA685A2B9630D0003348BE /* Debug */, - 03EA685B2B9630D0003348BE /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 03EA685C2B9630D0003348BE /* Build configuration list for PBXNativeTarget "Front Row" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 03EA685D2B9630D0003348BE /* Debug */, - 03EA685E2B9630D0003348BE /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - FADEFEED0000000000000011 /* Build configuration list for PBXNativeTarget "Front Row Tests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - FADEFEED000000000000000F /* Debug */, - FADEFEED0000000000000010 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - 03C112BD2DB56BFF00AE6799 /* XCRemoteSwiftPackageReference "Sparkle" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/sparkle-project/Sparkle"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.9.5; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 03C112BE2DB56BFF00AE6799 /* Sparkle */ = { - isa = XCSwiftPackageProductDependency; - package = 03C112BD2DB56BFF00AE6799 /* XCRemoteSwiftPackageReference "Sparkle" */; - productName = Sparkle; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = 03EA68452B9630CF003348BE /* Project object */; -} diff --git a/Front Row.xcodeproj/project.xcproj b/Front Row.xcodeproj/project.xcproj new file mode 100644 index 0000000..9915631 --- /dev/null +++ b/Front Row.xcodeproj/project.xcproj @@ -0,0 +1,248 @@ +{ + "default-configuration": "Release", + "configurations": [ + "Debug", + "Release", + ], + "localizations": { + "development": "en", + "supported": [ + "Base", + "ar", + "cs", + "da", + "de", + "el", + "es", + "fi", + "fr", + "he", + "hu", + "it", + "ja", + "ko", + "nl", + "pl", + "ro", + "ru", + "sv", + "tr", + "uk", + "vi", + "zh-Hans", + "zh-Hant", + ], + }, + "packages": [ + { + "kind": "remote", + "repository": "https://github.com/sparkle-project/Sparkle", + "version": { + "up-to-next-major-version": "2.9.5", + }, + }, + ], + "files": [ + { "path": "/FrontRowInfo.plist" }, + { "kind": "folder", "path": "Front Row UI Tests", "target-membership": [ "Front Row UI Tests" ] }, + { + "kind": "group", + "name": "Frameworks", + "children": [ + { "path": "/System/Library/Frameworks/AVKit.framework", "target-membership": [ "Front Row/frameworks" ] }, + ], + }, + { "kind": "folder", "path": "Front Row", "target-membership": [ "Front Row" ] }, + { "kind": "folder", "path": "Front Row Tests", "target-membership": [ "Front Row Tests" ] }, + { + "kind": "group", + "name": "Products", + "children": [ + { "path": "/Front Row.app", "id": "03EA684D2B9630CF003348BE", "type": "wrapper.application", "index": false }, + { "path": "/Front Row Tests.xctest", "id": "FADEFEED0000000000000001", "type": "wrapper.cfbundle", "index": false }, + { "path": "/Front Row UI Tests.xctest", "id": "031457A330329C99001B3B01", "type": "wrapper.cfbundle", "index": false }, + ], + }, + ], + "targets": [ + { + "name": "Front Row", + "id": "03EA684C2B9630CF003348BE", + "product": "Products/Front Row.app", + "product-type": "application", + "build-phases": [ + { + "kind": "script", + "name": "Run Script", + "log-environment-variables": true, + "run-on-every-build": true, + "shell": "/bin/sh", + "script": [ + "export PATH=\"$PATH:/opt/homebrew/bin\"", + "if which swift-format > /dev/null; then", + " swift-format lint -s -p -r ./", + "else", + " echo \"warning: swift-format not installed, download from https://github.com/apple/swift-format\"", + "fi", + "", + ], + }, + "compile-sources", + "frameworks", + "resources", + ], + "package-product-members": [ + { + "package": "Sparkle", + "product-name": "Sparkle", + "build-phase": { "build-phase": "frameworks" }, + }, + ], + "build-settings": { + "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", + "CODE_SIGN_ENTITLEMENTS": "Front Row/FrontRow.entitlements", + "CODE_SIGN_STYLE": "Automatic", + "COMBINE_HIDPI_IMAGES": "YES", + "ENABLE_APP_SANDBOX": "NO", + "ENABLE_HARDENED_RUNTIME": "YES", + "ENABLE_OUTGOING_NETWORK_CONNECTIONS": "YES", + "ENABLE_PREVIEWS": "YES", + "ENABLE_USER_SELECTED_FILES": "readonly", + "GENERATE_INFOPLIST_FILE": "YES", + "INFOPLIST_FILE": "FrontRowInfo.plist", + "INFOPLIST_KEY_LSApplicationCategoryType": "public.app-category.video", + "INFOPLIST_KEY_NSHumanReadableCopyright": "By Joshua Park", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/../Frameworks", + ], + "PRODUCT_BUNDLE_IDENTIFIER": "dev.joshuapark.FrontRow", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SWIFT_EMIT_LOC_STRINGS": "YES", + }, + }, { + "name": "Front Row Tests", + "id": "FADEFEED0000000000000009", + "product": "Products/Front Row Tests.xctest", + "product-type": "bundle.unit-test", + "test-host-target": "Front Row", + "dependencies": [ + "Front Row", + ], + "build-phases": [ + "compile-sources", + "frameworks", + "resources", + ], + "build-settings": { + "BUNDLE_LOADER": "$(TEST_HOST)", + "CODE_SIGN_STYLE": "Automatic", + "DEVELOPMENT_TEAM": "TCQ6328PP6", + "GENERATE_INFOPLIST_FILE": "YES", + "MACOSX_DEPLOYMENT_TARGET": "15.6", + "PRODUCT_BUNDLE_IDENTIFIER": "dev.joshuapark.FrontRowTests", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SWIFT_EMIT_LOC_STRINGS": "NO", + "SWIFT_VERSION": "6.0", + "TEST_HOST": "$(BUILT_PRODUCTS_DIR)/Front Row.app/Contents/MacOS/Front Row", + }, + }, { + "name": "Front Row UI Tests", + "id": "031457A230329C99001B3B01", + "product": "Products/Front Row UI Tests.xctest", + "product-type": "bundle.ui-testing", + "build-phases": [ + "compile-sources", + "frameworks", + "resources", + ], + "build-settings": { + "ALWAYS_SEARCH_USER_PATHS": "NO", + "CODE_SIGN_STYLE": "Automatic", + "ENABLE_USER_SCRIPT_SANDBOXING": "YES", + "GENERATE_INFOPLIST_FILE": "YES", + "MACOSX_DEPLOYMENT_TARGET": "15.6", + "PRODUCT_BUNDLE_IDENTIFIER": "dev.joshuapark.FrontRowUITests", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SDKROOT": "macosx", + "STRING_CATALOG_GENERATE_SYMBOLS": "NO", + "SUPPORTED_PLATFORMS": "macosx", + "SWIFT_APPROACHABLE_CONCURRENCY": "YES", + "SWIFT_EMIT_LOC_STRINGS": "NO", + "SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY": "YES", + "SWIFT_VERSION": "6.0", + }, + }, + ], + "build-settings": { + "ARCHS": "arm64", + "ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS": "YES", + "CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED": "YES", + "CLANG_ANALYZER_NONNULL": "YES", + "CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION": "YES_AGGRESSIVE", + "CLANG_CXX_LANGUAGE_STANDARD": "gnu++20", + "CLANG_ENABLE_MODULES": "YES", + "CLANG_ENABLE_OBJC_ARC": "YES", + "CLANG_ENABLE_OBJC_WEAK": "YES", + "CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING": "YES", + "CLANG_WARN_BOOL_CONVERSION": "YES", + "CLANG_WARN_COMMA": "YES", + "CLANG_WARN_CONSTANT_CONVERSION": "YES", + "CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS": "YES", + "CLANG_WARN_DIRECT_OBJC_ISA_USAGE": "YES_ERROR", + "CLANG_WARN_DOCUMENTATION_COMMENTS": "YES", + "CLANG_WARN_EMPTY_BODY": "YES", + "CLANG_WARN_ENUM_CONVERSION": "YES", + "CLANG_WARN_INFINITE_RECURSION": "YES", + "CLANG_WARN_INT_CONVERSION": "YES", + "CLANG_WARN_NON_LITERAL_NULL_CONVERSION": "YES", + "CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF": "YES", + "CLANG_WARN_OBJC_LITERAL_CONVERSION": "YES", + "CLANG_WARN_OBJC_ROOT_CLASS": "YES_ERROR", + "CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER": "YES", + "CLANG_WARN_RANGE_LOOP_ANALYSIS": "YES", + "CLANG_WARN_STRICT_PROTOTYPES": "YES", + "CLANG_WARN_SUSPICIOUS_MOVE": "YES", + "CLANG_WARN_UNGUARDED_AVAILABILITY": "YES_AGGRESSIVE", + "CLANG_WARN_UNREACHABLE_CODE": "YES", + "CLANG_WARN__DUPLICATE_METHOD_MATCH": "YES", + "COPY_PHASE_STRIP": "NO", + "CURRENT_PROJECT_VERSION": "275", + "DEAD_CODE_STRIPPING": "YES", + "DEBUG_INFORMATION_FORMAT[config=Debug]": "dwarf", + "DEBUG_INFORMATION_FORMAT[config=Release]": "dwarf-with-dsym", + "DEVELOPMENT_TEAM": "TCQ6328PP6", + "ENABLE_NS_ASSERTIONS[config=Release]": "NO", + "ENABLE_STRICT_OBJC_MSGSEND": "YES", + "ENABLE_TESTABILITY[config=Debug]": "YES", + "GCC_C_LANGUAGE_STANDARD": "gnu17", + "GCC_DYNAMIC_NO_PIC[config=Debug]": "NO", + "GCC_NO_COMMON_BLOCKS": "YES", + "GCC_OPTIMIZATION_LEVEL[config=Debug]": "0", + "GCC_PREPROCESSOR_DEFINITIONS[config=Debug]": [ + "DEBUG=1", + "$(inherited)", + ], + "GCC_WARN_64_TO_32_BIT_CONVERSION": "YES", + "GCC_WARN_ABOUT_RETURN_TYPE": "YES_ERROR", + "GCC_WARN_UNDECLARED_SELECTOR": "YES", + "GCC_WARN_UNINITIALIZED_AUTOS": "YES_AGGRESSIVE", + "GCC_WARN_UNUSED_FUNCTION": "YES", + "GCC_WARN_UNUSED_VARIABLE": "YES", + "LOCALIZATION_PREFERS_STRING_CATALOGS": "YES", + "MACOSX_DEPLOYMENT_TARGET": "15.6", + "MARKETING_VERSION": "3.1.0", + "MTL_ENABLE_DEBUG_INFO[config=Debug]": "INCLUDE_SOURCE", + "MTL_ENABLE_DEBUG_INFO[config=Release]": "NO", + "MTL_FAST_MATH": "YES", + "ONLY_ACTIVE_ARCH[config=Debug]": "YES", + "SDKROOT": "macosx", + "STRING_CATALOG_GENERATE_SYMBOLS": "YES", + "SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Debug]": "DEBUG $(inherited)", + "SWIFT_COMPILATION_MODE[config=Release]": "wholemodule", + "SWIFT_OPTIMIZATION_LEVEL[config=Debug]": "-Onone", + "SWIFT_VERSION": "6.0", + }, + "last-upgrade": "26.3", + "last-swift-update": "27.0", +} diff --git a/README.md b/README.md index 6081123..a89a930 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Experience color accurate HDR videos with full surround sound using spatial audi - [HDR video compatible Macs](https://support.apple.com/en-us/102205) and/or [spatial audio compatible devices](https://support.apple.com/en-us/102469) - Apple Silicon (M1 and later) - macOS Sequoia 15.6 and later -- Xcode 26.3 (to build) +- Xcode 27.2 or later, on macOS Tahoe 26.6 or later (to build) ## Frequently Asked Questions diff --git a/scripts/project-version.sh b/scripts/project-version.sh index 2948fc9..084d96b 100755 --- a/scripts/project-version.sh +++ b/scripts/project-version.sh @@ -1,76 +1,90 @@ #!/bin/bash -# Reads and writes the version settings in project.pbxproj. +# Reads and writes the version settings in project.xcproj. # -# project-version.sh read-marketing "Front Row.xcodeproj/project.pbxproj" -# project-version.sh read-build "Front Row.xcodeproj/project.pbxproj" -# project-version.sh write "Front Row.xcodeproj/project.pbxproj" 2.11.0 206 +# project-version.sh read-marketing "Front Row.xcodeproj/project.xcproj" +# project-version.sh read-build "Front Row.xcodeproj/project.xcproj" +# project-version.sh write "Front Row.xcodeproj/project.xcproj" 2.11.0 206 # -# MARKETING_VERSION and CURRENT_PROJECT_VERSION both live at the project level, -# so each appears exactly twice — once per configuration. Anything else means -# the project no longer matches what this assumes, and writing would be a guess. +# project.xcproj is Xcode's JSON project format, in its canonical layout +# (xcprojformatter): project-level settings sit in the top-level +# "build-settings" object, one per line at four spaces of indentation. +# MARKETING_VERSION and CURRENT_PROJECT_VERSION both live there, shared by every +# configuration, so each appears exactly once, with no per-configuration +# ("KEY[config=...]") or per-target override. Anything else means the project +# no longer matches what this assumes, and writing would be a guess. set -euo pipefail -readonly EXPECTED_OCCURRENCES=2 - die() { echo "project-version: $1" >&2 exit 1 } +# Every mention of the setting as a key, at any level or with any condition. +count_keys() { + grep -c "\"$2[\"[]" "$1" || true +} + +# The setting as a plain project-level key: four spaces, then "KEY": "value", +project_line() { + printf '^ "%s": "\\(.*\\)",$' "$1" +} + read_setting() { - local pbxproj="$1" setting="$2" - [[ -f "$pbxproj" ]] || die "project file not found: $pbxproj" + local project="$1" setting="$2" + [[ -f "$project" ]] || die "project file not found: $project" + + local count + count="$(count_keys "$project" "$setting")" + (( count > 0 )) || die "$setting not found in $project" + (( count == 1 )) || + die "$setting is set $count times in $project; expected one project-level value" - local values - values="$(sed -n "s/^[[:space:]]*$setting = \(.*\);$/\1/p" "$pbxproj" | sort -u)" - [[ -n "$values" ]] || die "$setting not found in $pbxproj" - [[ "$(wc -l <<<"$values")" -eq 1 ]] || - die "$setting has conflicting values in $pbxproj: $(tr '\n' ' ' <<<"$values")" + local value + value="$(sed -n "s/$(project_line "$setting")/\1/p" "$project")" + [[ -n "$value" ]] || die "$setting in $project is not a project-level setting" - echo "$values" + echo "$value" } command="${1-}" -pbxproj="${2-}" +project="${2-}" case "$command" in read-marketing) - read_setting "$pbxproj" MARKETING_VERSION + read_setting "$project" MARKETING_VERSION ;; read-build) - read_setting "$pbxproj" CURRENT_PROJECT_VERSION + read_setting "$project" CURRENT_PROJECT_VERSION ;; write) marketing="${3-}" build="${4-}" - [[ -f "$pbxproj" ]] || die "project file not found: $pbxproj" + [[ -f "$project" ]] || die "project file not found: $project" [[ "$marketing" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || die "marketing version must be X.Y.Z, got '$marketing'" [[ "$build" =~ ^[0-9]+$ ]] || die "build number must be a whole number, got '$build'" - for setting in MARKETING_VERSION CURRENT_PROJECT_VERSION; do - count="$(grep -c "^[[:space:]]*$setting = .*;$" "$pbxproj" || true)" - (( count == EXPECTED_OCCURRENCES )) || - die "expected $EXPECTED_OCCURRENCES occurrences of $setting, found $count — the project layout changed" - done + # Reading validates the layout: one project-level value for each setting. + read_setting "$project" MARKETING_VERSION >/dev/null + read_setting "$project" CURRENT_PROJECT_VERSION >/dev/null # Write to a temp file and move into place so a failure cannot leave a # half-rewritten project. tmp="$(mktemp)" trap 'rm -f "$tmp"' EXIT - sed -e "s/^\([[:space:]]*\)MARKETING_VERSION = .*;$/\1MARKETING_VERSION = $marketing;/" \ - -e "s/^\([[:space:]]*\)CURRENT_PROJECT_VERSION = .*;$/\1CURRENT_PROJECT_VERSION = $build;/" \ - "$pbxproj" >"$tmp" + sed -e "s/$(project_line MARKETING_VERSION)/ \"MARKETING_VERSION\": \"$marketing\",/" \ + -e "s/$(project_line CURRENT_PROJECT_VERSION)/ \"CURRENT_PROJECT_VERSION\": \"$build\",/" \ + "$project" >"$tmp" - mv "$tmp" "$pbxproj" + mv "$tmp" "$project" trap - EXIT - echo "Set $pbxproj to $marketing ($build)" + echo "Set $project to $marketing ($build)" ;; "") diff --git a/scripts/project-version.test.sh b/scripts/project-version.test.sh index af580f2..562b395 100755 --- a/scripts/project-version.test.sh +++ b/scripts/project-version.test.sh @@ -6,9 +6,6 @@ set -uo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" tool="$script_dir/project-version.sh" -# What project-version.sh itself expects: one value per build configuration. -readonly EXPECTED_OCCURRENCES=2 - failures=0 workdir="$(mktemp -d)" trap 'rm -rf "$workdir"' EXIT @@ -31,59 +28,55 @@ expect_equal() { fi } -# Mirrors the real layout: both settings at project level, in two configurations. -make_pbxproj() { - cat >"$1" <<'PBX' -// !$*UTF8*$! +# Mirrors the real layout: both settings once, at project level, between +# per-configuration and per-target settings that must be left alone. +make_project() { + cat >"$1" <<'XCPROJ' { - objects = { - AAAA /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 24; - MARKETING_VERSION = 2.10; - DEAD_CODE_STRIPPING = YES; - }; - name = Debug; - }; - BBBB /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 24; - MARKETING_VERSION = 2.10; - DEAD_CODE_STRIPPING = YES; - }; - name = Release; - }; - }; + "default-configuration": "Release", + "targets": [ + { + "name": "App", + "build-settings": { + "PRODUCT_NAME": "$(TARGET_NAME)", + }, + }, + ], + "build-settings": { + "COPY_PHASE_STRIP": "NO", + "CURRENT_PROJECT_VERSION": "24", + "DEAD_CODE_STRIPPING": "YES", + "DEBUG_INFORMATION_FORMAT[config=Debug]": "dwarf", + "MARKETING_VERSION": "2.10", + "SDKROOT": "macosx", + }, } -PBX +XCPROJ } -pbx="$workdir/project.pbxproj" -make_pbxproj "$pbx" +project="$workdir/project.xcproj" +make_project "$project" # --- read ------------------------------------------------------------------- -expect_equal "$("$tool" read-marketing "$pbx")" "2.10" "reads the marketing version" -expect_equal "$("$tool" read-build "$pbx")" "24" "reads the build number" +expect_equal "$("$tool" read-marketing "$project")" "2.10" "reads the marketing version" +expect_equal "$("$tool" read-build "$project")" "24" "reads the build number" # --- write ------------------------------------------------------------------ -if "$tool" write "$pbx" 2.11.0 206 >/dev/null 2>&1; then +if "$tool" write "$project" 2.11.0 206 >/dev/null 2>&1; then check "exits zero on write" pass else check "exits zero on write" fail fi -expect_equal "$("$tool" read-marketing "$pbx")" "2.11.0" "writes the marketing version" -expect_equal "$("$tool" read-build "$pbx")" "206" "writes the build number" -expect_equal "$(grep -c 'MARKETING_VERSION = 2.11.0;' "$pbx")" "2" "updates both configurations' marketing version" -expect_equal "$(grep -c 'CURRENT_PROJECT_VERSION = 206;' "$pbx")" "2" "updates both configurations' build number" -expect_equal "$(grep -c 'COPY_PHASE_STRIP' "$pbx")" "2" "leaves neighbouring settings alone" +expect_equal "$("$tool" read-marketing "$project")" "2.11.0" "writes the marketing version" +expect_equal "$("$tool" read-build "$project")" "206" "writes the build number" +expect_equal "$(grep -c '^ "MARKETING_VERSION": "2.11.0",$' "$project")" "1" "writes the marketing version in place" +expect_equal "$(grep -c '^ "CURRENT_PROJECT_VERSION": "206",$' "$project")" "1" "writes the build number in place" +make_project "$workdir/expected.xcproj" +expect_equal "$(diff "$workdir/expected.xcproj" "$project" | grep -c '^[<>]')" "4" "changes only the two version lines" # --- guards ----------------------------------------------------------------- -missing="$workdir/nope.pbxproj" +missing="$workdir/nope.xcproj" if "$tool" read-marketing "$missing" >/dev/null 2>&1; then check "rejects a missing project file" fail else @@ -92,7 +85,7 @@ fi # A project whose settings are not where we expect must fail loudly rather than # silently write nothing. -empty="$workdir/empty.pbxproj" +empty="$workdir/empty.xcproj" printf '{\n}\n' >"$empty" if "$tool" read-marketing "$empty" >/dev/null 2>&1; then check "rejects a project with no marketing version" fail @@ -105,31 +98,44 @@ else check "refuses to write when settings are absent" pass fi -# One occurrence means the project no longer matches the assumed layout. -lopsided="$workdir/lopsided.pbxproj" -make_pbxproj "$lopsided" -# Delete only the first MARKETING_VERSION line. BSD sed has no 0,/re/ range. -awk '!done && /MARKETING_VERSION/ { done = 1; next } { print }' "$lopsided" >"$lopsided.tmp" -mv "$lopsided.tmp" "$lopsided" -[[ "$(grep -c 'MARKETING_VERSION' "$lopsided")" == "1" ]] || - { echo "FAIL test fixture is wrong: lopsided project should have 1 MARKETING_VERSION"; failures=$((failures + 1)); } -if "$tool" write "$lopsided" 2.11.0 206 >/dev/null 2>&1; then - check "refuses to write when occurrence count is unexpected" fail +# An override anywhere else means the project no longer matches the assumed +# layout: a per-configuration value, or a per-target one. +overridden="$workdir/overridden.xcproj" +for override in '"MARKETING_VERSION[config=Debug]": "2.9",' '"MARKETING_VERSION": "2.9",'; do + make_project "$overridden" + awk -v line=" $override" '{ print } /"PRODUCT_NAME"/ { print line }' "$overridden" >"$overridden.tmp" + mv "$overridden.tmp" "$overridden" + [[ "$(grep -c 'MARKETING_VERSION' "$overridden")" == "2" ]] || + { echo "FAIL test fixture is wrong: overridden project should have 2 MARKETING_VERSION"; failures=$((failures + 1)); } + if "$tool" write "$overridden" 2.11.0 206 >/dev/null 2>&1; then + check "refuses to write with override $override" fail + else + check "refuses to write with override $override" pass + fi +done + +# A setting that is only set per target is not a project-level setting. +targetonly="$workdir/targetonly.xcproj" +make_project "$targetonly" +awk '!/^ "MARKETING_VERSION"/ { print } /"PRODUCT_NAME"/ { print " \"MARKETING_VERSION\": \"2.9\"," }' "$targetonly" >"$targetonly.tmp" +mv "$targetonly.tmp" "$targetonly" +if "$tool" read-marketing "$targetonly" >/dev/null 2>&1; then + check "rejects a marketing version set only on a target" fail else - check "refuses to write when occurrence count is unexpected" pass + check "rejects a marketing version set only on a target" pass fi # Reject values that would corrupt the project file or produce a bad version. -make_pbxproj "$pbx" +make_project "$project" for bad in "" "2.11.0;" "2.11.0 extra" "not a version"; do - if "$tool" write "$pbx" "$bad" 206 >/dev/null 2>&1; then + if "$tool" write "$project" "$bad" 206 >/dev/null 2>&1; then check "rejects marketing version '$bad'" fail else check "rejects marketing version '$bad'" pass fi done for bad in "" "abc" "20.6" "-1"; do - if "$tool" write "$pbx" 2.11.0 "$bad" >/dev/null 2>&1; then + if "$tool" write "$project" 2.11.0 "$bad" >/dev/null 2>&1; then check "rejects build number '$bad'" fail else check "rejects build number '$bad'" pass @@ -137,11 +143,11 @@ for bad in "" "abc" "20.6" "-1"; do done # Nothing above should have modified the file. -expect_equal "$("$tool" read-marketing "$pbx")" "2.10" "leaves the project untouched when validation fails" +expect_equal "$("$tool" read-marketing "$project")" "2.10" "leaves the project untouched when validation fails" # The real project, which is what a release reads. Both settings must stay # unambiguous across every target for the release to work out the next version. -real="$script_dir/../Front Row.xcodeproj/project.pbxproj" +real="$script_dir/../Front Row.xcodeproj/project.xcproj" for command in read-marketing read-build; do if "$tool" "$command" "$real" >/dev/null 2>&1; then check "$command answers for the real project" pass @@ -149,10 +155,6 @@ for command in read-marketing read-build; do check "$command answers for the real project" fail fi done -for setting in MARKETING_VERSION CURRENT_PROJECT_VERSION; do - expect_equal "$(grep -c "^[[:space:]]*$setting = .*;$" "$real")" "$EXPECTED_OCCURRENCES" \ - "the real project carries $setting once per configuration" -done echo if (( failures > 0 )); then