From d98e459a8ddb30f63c0aa229c46041c1230a9002 Mon Sep 17 00:00:00 2001 From: Pedro Date: Tue, 1 Oct 2024 12:55:42 +0200 Subject: [PATCH 1/3] Fix the parsing of some Xcode 16 projects --- .../project.pbxproj | 13 +- Fixtures/Xcode16/README.md | 3 + .../Xcode16/Test.xcodeproj/project.pbxproj | 340 +++++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 85 ++ .../Test/Assets.xcassets/Contents.json | 6 + Fixtures/Xcode16/Test/ContentView.swift | 24 + .../Preview Assets.xcassets/Contents.json | 6 + Fixtures/Xcode16/Test/Test.entitlements | 10 + Fixtures/Xcode16/Test/TestApp.swift | 17 + .../Xcode16/copy.xcodeproj/project.pbxproj | 335 +++++ .../PBXFileSystemSynchronizedRootGroup.swift | 203 +-- .../Objects/Project/PBXProject.swift | 1105 +++++++++-------- .../Files/PBXContainerItemProxyTests.swift | 2 +- .../Objects/Files/PBXFileElementTests.swift | 2 + .../Objects/Files/PBXGroupTests.swift | 4 + .../Objects/Project/PBXProject+Fixtures.swift | 1 + .../Objects/Project/PBXProjectTests.swift | 7 + .../Targets/PBXAggregateTargetTests.swift | 1 + .../Targets/PBXNativeTargetTests.swift | 1 + .../Project/XcodeProjIntegrationTests.swift | 10 + .../Utils/ReferenceGeneratorTests.swift | 1 + 22 files changed, 1545 insertions(+), 642 deletions(-) create mode 100644 Fixtures/Xcode16/README.md create mode 100644 Fixtures/Xcode16/Test.xcodeproj/project.pbxproj create mode 100644 Fixtures/Xcode16/Test/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 Fixtures/Xcode16/Test/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Fixtures/Xcode16/Test/Assets.xcassets/Contents.json create mode 100644 Fixtures/Xcode16/Test/ContentView.swift create mode 100644 Fixtures/Xcode16/Test/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 Fixtures/Xcode16/Test/Test.entitlements create mode 100644 Fixtures/Xcode16/Test/TestApp.swift create mode 100644 Fixtures/Xcode16/copy.xcodeproj/project.pbxproj diff --git a/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/project.pbxproj b/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/project.pbxproj index 5db92414b..c6a599659 100644 --- a/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/project.pbxproj +++ b/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/project.pbxproj @@ -21,7 +21,18 @@ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ - 6CF05B9D2C53F64800EF267F /* SynchronizedRootGroups */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (6CF05BA32C53F97F00EF267F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SynchronizedRootGroups; sourceTree = ""; }; + 6CF05B9D2C53F64800EF267F /* SynchronizedRootGroups */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 6CF05BA32C53F97F00EF267F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, + ); + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = SynchronizedRootGroups; + sourceTree = ""; + }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ diff --git a/Fixtures/Xcode16/README.md b/Fixtures/Xcode16/README.md new file mode 100644 index 000000000..e1673acd1 --- /dev/null +++ b/Fixtures/Xcode16/README.md @@ -0,0 +1,3 @@ +# Xcode 16 project + +Xcode 16 introduced some changes in Xcode projects, like [this one](https://github.com/tuist/XcodeProj/issues/861), so this fixture tries to capture those changes to run tests against them. diff --git a/Fixtures/Xcode16/Test.xcodeproj/project.pbxproj b/Fixtures/Xcode16/Test.xcodeproj/project.pbxproj new file mode 100644 index 000000000..38d0a9761 --- /dev/null +++ b/Fixtures/Xcode16/Test.xcodeproj/project.pbxproj @@ -0,0 +1,340 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXFileReference section */ + A4C5307E2CAAC8EA00EDC73B /* Test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Test.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + A4C530802CAAC8EA00EDC73B /* Test */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Test; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + A4C5307B2CAAC8EA00EDC73B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A4C530752CAAC8EA00EDC73B = { + isa = PBXGroup; + children = ( + A4C530802CAAC8EA00EDC73B /* Test */, + A4C5307F2CAAC8EA00EDC73B /* Products */, + ); + sourceTree = ""; + }; + A4C5307F2CAAC8EA00EDC73B /* Products */ = { + isa = PBXGroup; + children = ( + A4C5307E2CAAC8EA00EDC73B /* Test.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + A4C5307D2CAAC8EA00EDC73B /* Test */ = { + isa = PBXNativeTarget; + buildConfigurationList = A4C5308D2CAAC8EC00EDC73B /* Build configuration list for PBXNativeTarget "Test" */; + buildPhases = ( + A4C5307A2CAAC8EA00EDC73B /* Sources */, + A4C5307B2CAAC8EA00EDC73B /* Frameworks */, + A4C5307C2CAAC8EA00EDC73B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + A4C530802CAAC8EA00EDC73B /* Test */, + ); + name = Test; + packageProductDependencies = ( + ); + productName = Test; + productReference = A4C5307E2CAAC8EA00EDC73B /* Test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A4C530762CAAC8EA00EDC73B /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1610; + LastUpgradeCheck = 1610; + TargetAttributes = { + A4C5307D2CAAC8EA00EDC73B = { + CreatedOnToolsVersion = 16.1; + }; + }; + }; + buildConfigurationList = A4C530792CAAC8EA00EDC73B /* Build configuration list for PBXProject "Test" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = A4C530752CAAC8EA00EDC73B; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = A4C5307F2CAAC8EA00EDC73B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A4C5307D2CAAC8EA00EDC73B /* Test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A4C5307C2CAAC8EA00EDC73B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A4C5307A2CAAC8EA00EDC73B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A4C5308B2CAAC8EC00EDC73B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = 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; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = 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; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + A4C5308C2CAAC8EC00EDC73B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = 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; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = 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; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + A4C5308E2CAAC8EC00EDC73B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Test/Test.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Test/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.Test; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.1; + }; + name = Debug; + }; + A4C5308F2CAAC8EC00EDC73B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Test/Test.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Test/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.Test; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A4C530792CAAC8EA00EDC73B /* Build configuration list for PBXProject "Test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A4C5308B2CAAC8EC00EDC73B /* Debug */, + A4C5308C2CAAC8EC00EDC73B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A4C5308D2CAAC8EC00EDC73B /* Build configuration list for PBXNativeTarget "Test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A4C5308E2CAAC8EC00EDC73B /* Debug */, + A4C5308F2CAAC8EC00EDC73B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A4C530762CAAC8EA00EDC73B /* Project object */; +} diff --git a/Fixtures/Xcode16/Test/Assets.xcassets/AccentColor.colorset/Contents.json b/Fixtures/Xcode16/Test/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/Fixtures/Xcode16/Test/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fixtures/Xcode16/Test/Assets.xcassets/AppIcon.appiconset/Contents.json b/Fixtures/Xcode16/Test/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..ffdfe150b --- /dev/null +++ b/Fixtures/Xcode16/Test/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,85 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fixtures/Xcode16/Test/Assets.xcassets/Contents.json b/Fixtures/Xcode16/Test/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Fixtures/Xcode16/Test/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fixtures/Xcode16/Test/ContentView.swift b/Fixtures/Xcode16/Test/ContentView.swift new file mode 100644 index 000000000..32435833a --- /dev/null +++ b/Fixtures/Xcode16/Test/ContentView.swift @@ -0,0 +1,24 @@ +// +// ContentView.swift +// Test +// +// Created by F1248 on 30.09.24. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/Fixtures/Xcode16/Test/Preview Content/Preview Assets.xcassets/Contents.json b/Fixtures/Xcode16/Test/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Fixtures/Xcode16/Test/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fixtures/Xcode16/Test/Test.entitlements b/Fixtures/Xcode16/Test/Test.entitlements new file mode 100644 index 000000000..f2ef3ae02 --- /dev/null +++ b/Fixtures/Xcode16/Test/Test.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/Fixtures/Xcode16/Test/TestApp.swift b/Fixtures/Xcode16/Test/TestApp.swift new file mode 100644 index 000000000..bb7480e72 --- /dev/null +++ b/Fixtures/Xcode16/Test/TestApp.swift @@ -0,0 +1,17 @@ +// +// TestApp.swift +// Test +// +// Created by F1248 on 30.09.24. +// + +import SwiftUI + +@main +struct TestApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/Fixtures/Xcode16/copy.xcodeproj/project.pbxproj b/Fixtures/Xcode16/copy.xcodeproj/project.pbxproj new file mode 100644 index 000000000..4a66fedfd --- /dev/null +++ b/Fixtures/Xcode16/copy.xcodeproj/project.pbxproj @@ -0,0 +1,335 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXFileReference section */ + A4C5307E2CAAC8EA00EDC73B /* Test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Test.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + A4C530802CAAC8EA00EDC73B /* Test */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Test; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + A4C5307B2CAAC8EA00EDC73B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A4C530752CAAC8EA00EDC73B = { + isa = PBXGroup; + children = ( + A4C530802CAAC8EA00EDC73B /* Test */, + A4C5307F2CAAC8EA00EDC73B /* Products */, + ); + sourceTree = ""; + }; + A4C5307F2CAAC8EA00EDC73B /* Products */ = { + isa = PBXGroup; + children = ( + A4C5307E2CAAC8EA00EDC73B /* Test.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + A4C5307D2CAAC8EA00EDC73B /* Test */ = { + isa = PBXNativeTarget; + buildConfigurationList = A4C5308D2CAAC8EC00EDC73B /* Build configuration list for PBXNativeTarget "Test" */; + buildPhases = ( + A4C5307A2CAAC8EA00EDC73B /* Sources */, + A4C5307B2CAAC8EA00EDC73B /* Frameworks */, + A4C5307C2CAAC8EA00EDC73B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + A4C530802CAAC8EA00EDC73B /* Test */, + ); + name = Test; + packageProductDependencies = ( + ); + productName = Test; + productReference = A4C5307E2CAAC8EA00EDC73B /* Test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A4C530762CAAC8EA00EDC73B /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1610; + LastUpgradeCheck = 1610; + TargetAttributes = { + A4C5307D2CAAC8EA00EDC73B = { + CreatedOnToolsVersion = 16.1; + }; + }; + }; + buildConfigurationList = A4C530792CAAC8EA00EDC73B /* Build configuration list for PBXProject "copy" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = A4C530752CAAC8EA00EDC73B; + preferredProjectObjectVersion = 77; + productRefGroup = A4C5307F2CAAC8EA00EDC73B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A4C5307D2CAAC8EA00EDC73B /* Test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A4C5307C2CAAC8EA00EDC73B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A4C5307A2CAAC8EA00EDC73B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A4C5308B2CAAC8EC00EDC73B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = 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; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = 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; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + A4C5308C2CAAC8EC00EDC73B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = 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; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = 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; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + A4C5308E2CAAC8EC00EDC73B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Test/Test.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Test/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.Test; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.1; + }; + name = Debug; + }; + A4C5308F2CAAC8EC00EDC73B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Test/Test.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Test/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 15.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.Test; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A4C530792CAAC8EA00EDC73B /* Build configuration list for PBXProject "copy" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A4C5308B2CAAC8EC00EDC73B /* Debug */, + A4C5308C2CAAC8EC00EDC73B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A4C5308D2CAAC8EC00EDC73B /* Build configuration list for PBXNativeTarget "Test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A4C5308E2CAAC8EC00EDC73B /* Debug */, + A4C5308F2CAAC8EC00EDC73B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A4C530762CAAC8EA00EDC73B /* Project object */; +} diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift index 5df7fe342..1c17b6458 100644 --- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift +++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift @@ -2,108 +2,113 @@ import Foundation import PathKit public class PBXFileSystemSynchronizedRootGroup: PBXFileElement { - /// It maps relative paths inside the synchronized root group to a particular file type. - /// If a path doesn't have a particular file type specified, Xcode defaults to the default file type - /// based on the extension of the file. - public var explicitFileTypes: [String: String] - - /// Returns the references of the exceptions. - var exceptionsReferences: [PBXObjectReference]? - - /// It returns a list of exception objects that override the configuration for some children - /// in the synchronized root group. - public var exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet]? { - set { - exceptionsReferences = newValue?.references() - } - get { - exceptionsReferences?.objects() - } - } - - /// A list of relative paths to children folder whose configuration is overriden. - public var explicitFolders: [String] - - /// Initializes the file element with its properties. - /// - /// - Parameters: - /// - sourceTree: file source tree. - /// - path: object relative path from `sourceTree`, if different than `name`. - /// - name: object name. - /// - includeInIndex: should the IDE index the object? - /// - usesTabs: object uses tabs. - /// - indentWidth: the number of positions to indent blocks of code - /// - tabWidth: the visual width of tab characters - /// - wrapsLines: should the IDE wrap lines when editing the object? - /// - explicitFileTypes: It maps relative paths inside the synchronized root group to a particular file type. - /// - exceptions: It returns a list of exception objects that override the configuration for some children in the synchronized root group. - /// - explicitFolders: A list of relative paths to children folder whose configuration is overriden. - public init(sourceTree: PBXSourceTree? = nil, - path: String? = nil, - name: String? = nil, - includeInIndex: Bool? = nil, - usesTabs: Bool? = nil, - indentWidth: UInt? = nil, - tabWidth: UInt? = nil, - wrapsLines: Bool? = nil, - explicitFileTypes: [String: String] = [:], - exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] = [], - explicitFolders: [String] = []) { - self.explicitFileTypes = explicitFileTypes - exceptionsReferences = exceptions.references() - self.explicitFolders = explicitFolders - super.init(sourceTree: sourceTree, - path: path, - name: name, - includeInIndex: includeInIndex, - usesTabs: usesTabs, - indentWidth: indentWidth, - tabWidth: tabWidth, - wrapsLines: wrapsLines) + /// It maps relative paths inside the synchronized root group to a particular file type. + /// If a path doesn't have a particular file type specified, Xcode defaults to the default file type + /// based on the extension of the file. + public var explicitFileTypes: [String: String]? + + /// Returns the references of the exceptions. + var exceptionsReferences: [PBXObjectReference]? + + /// It returns a list of exception objects that override the configuration for some children + /// in the synchronized root group. + public var exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet]? { + set { + exceptionsReferences = newValue?.references() } - - // MARK: - Decodable - - fileprivate enum CodingKeys: String, CodingKey { - case explicitFileTypes - case exceptions - case explicitFolders + get { + exceptionsReferences?.objects() } - - public required init(from decoder: Decoder) throws { - let objects = decoder.context.objects - let objectReferenceRepository = decoder.context.objectReferenceRepository - let container = try decoder.container(keyedBy: CodingKeys.self) - explicitFileTypes = try (container.decodeIfPresent(.explicitFileTypes)) ?? [:] - let exceptionsReferences: [String] = try (container.decodeIfPresent(.exceptions)) ?? [] - self.exceptionsReferences = exceptionsReferences.map { objectReferenceRepository.getOrCreate(reference: $0, objects: objects) } - explicitFolders = try (container.decodeIfPresent(.explicitFolders)) ?? [] - try super.init(from: decoder) + } + + /// A list of relative paths to children folder whose configuration is overriden. + public var explicitFolders: [String]? + + /// Initializes the file element with its properties. + /// + /// - Parameters: + /// - sourceTree: file source tree. + /// - path: object relative path from `sourceTree`, if different than `name`. + /// - name: object name. + /// - includeInIndex: should the IDE index the object? + /// - usesTabs: object uses tabs. + /// - indentWidth: the number of positions to indent blocks of code + /// - tabWidth: the visual width of tab characters + /// - wrapsLines: should the IDE wrap lines when editing the object? + /// - explicitFileTypes: It maps relative paths inside the synchronized root group to a particular file type. + /// - exceptions: It returns a list of exception objects that override the configuration for some children in the synchronized root group. + /// - explicitFolders: A list of relative paths to children folder whose configuration is overriden. + public init(sourceTree: PBXSourceTree? = nil, + path: String? = nil, + name: String? = nil, + includeInIndex: Bool? = nil, + usesTabs: Bool? = nil, + indentWidth: UInt? = nil, + tabWidth: UInt? = nil, + wrapsLines: Bool? = nil, + explicitFileTypes: [String: String] = [:], + exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] = [], + explicitFolders: [String] = []) { + self.explicitFileTypes = explicitFileTypes + exceptionsReferences = exceptions.references() + self.explicitFolders = explicitFolders + super.init(sourceTree: sourceTree, + path: path, + name: name, + includeInIndex: includeInIndex, + usesTabs: usesTabs, + indentWidth: indentWidth, + tabWidth: tabWidth, + wrapsLines: wrapsLines) + } + + // MARK: - Decodable + + fileprivate enum CodingKeys: String, CodingKey { + case explicitFileTypes + case exceptions + case explicitFolders + } + + public required init(from decoder: Decoder) throws { + let objects = decoder.context.objects + let objectReferenceRepository = decoder.context.objectReferenceRepository + let container = try decoder.container(keyedBy: CodingKeys.self) + explicitFileTypes = try container.decodeIfPresent(.explicitFileTypes) + let exceptionsReferences: [String] = try (container.decodeIfPresent(.exceptions)) ?? [] + self.exceptionsReferences = exceptionsReferences.map { objectReferenceRepository.getOrCreate(reference: $0, objects: objects) } + explicitFolders = try container.decodeIfPresent(.explicitFolders) + try super.init(from: decoder) + } + + // MARK: - PlistSerializable + + override var multiline: Bool { true } + + override func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { + var dictionary: [CommentedString: PlistValue] = try super.plistKeyAndValue(proj: proj, reference: reference).value.dictionary ?? [:] + dictionary["isa"] = .string(CommentedString(type(of: self).isa)) + if let exceptionsReferences, !exceptionsReferences.isEmpty { + dictionary["exceptions"] = .array(exceptionsReferences.map { exceptionReference in + .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet")) + }) } - - // MARK: - PlistSerializable - - override var multiline: Bool { false } - - override func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { - var dictionary: [CommentedString: PlistValue] = try super.plistKeyAndValue(proj: proj, reference: reference).value.dictionary ?? [:] - dictionary["isa"] = .string(CommentedString(type(of: self).isa)) - if let exceptionsReferences, !exceptionsReferences.isEmpty { - dictionary["exceptions"] = .array(exceptionsReferences.map { exceptionReference in - .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet")) - }) - } - dictionary["explicitFileTypes"] = .dictionary(Dictionary(uniqueKeysWithValues: explicitFileTypes.map { relativePath, fileType in - (CommentedString(relativePath), .string(CommentedString(fileType))) - })) - dictionary["explicitFolders"] = .array(explicitFolders.map { .string(CommentedString($0)) }) - return (key: CommentedString(reference, - comment: name ?? path), - value: .dictionary(dictionary)) + if let explicitFileTypes { + dictionary["explicitFileTypes"] = .dictionary(Dictionary(uniqueKeysWithValues: explicitFileTypes.map { relativePath, fileType in + (CommentedString(relativePath), .string(CommentedString(fileType))) + })) } - - override func isEqual(to object: Any?) -> Bool { - guard let rhs = object as? PBXFileSystemSynchronizedRootGroup else { return false } - return isEqual(to: rhs) + if let explicitFolders { + dictionary["explicitFolders"] = .array(explicitFolders.map { .string(CommentedString($0)) }) + } + return (key: CommentedString(reference, + comment: name ?? path), + value: .dictionary(dictionary)) + } + + override func isEqual(to object: Any?) -> Bool { + guard let rhs = object as? PBXFileSystemSynchronizedRootGroup else { return false } + return isEqual(to: rhs) + } } diff --git a/Sources/XcodeProj/Objects/Project/PBXProject.swift b/Sources/XcodeProj/Objects/Project/PBXProject.swift index 14e896995..4f37bbaaa 100644 --- a/Sources/XcodeProj/Objects/Project/PBXProject.swift +++ b/Sources/XcodeProj/Objects/Project/PBXProject.swift @@ -2,580 +2,603 @@ import Foundation import PathKit public final class PBXProject: PBXObject { - // MARK: - Attributes - - /// Project name - public var name: String - - /// Build configuration list reference. - var buildConfigurationListReference: PBXObjectReference - - /// Build configuration list. - public var buildConfigurationList: XCConfigurationList! { - set { - buildConfigurationListReference = newValue.reference - } - get { - buildConfigurationListReference.getObject() - } + // MARK: - Attributes + + /// Project name + public var name: String + + /// Build configuration list reference. + var buildConfigurationListReference: PBXObjectReference + + /// Build configuration list. + public var buildConfigurationList: XCConfigurationList! { + set { + buildConfigurationListReference = newValue.reference } - - /// A string representation of the XcodeCompatibilityVersion. - public var compatibilityVersion: String? - - /// An int representation of the PreferredProjectObjectVersion. - public var preferredProjectObjectVersion: Int? - - /// The region of development. - public var developmentRegion: String? - - /// Whether file encodings have been scanned. - public var hasScannedForEncodings: Int - - /// The known regions for localized files. - public var knownRegions: [String] - - /// The object is a reference to a PBXGroup element. - var mainGroupReference: PBXObjectReference - - /// Project main group. - public var mainGroup: PBXGroup! { - set { - mainGroupReference = newValue.reference - } - get { - mainGroupReference.getObject() - } + get { + buildConfigurationListReference.getObject() } - - /// The object is a reference to a PBXGroup element. - var productsGroupReference: PBXObjectReference? - - /// Products group. - public var productsGroup: PBXGroup? { - set { - productsGroupReference = newValue?.reference - } - get { - productsGroupReference?.getObject() - } + } + + /// A string representation of the XcodeCompatibilityVersion. + public var compatibilityVersion: String? + + /// An int representation of the PreferredProjectObjectVersion. + public var preferredProjectObjectVersion: Int? + + /// An int representation of the minimizedProjectReferenceProxies attribute + public var minimizedProjectReferenceProxies: Int? + + /// The region of development. + public var developmentRegion: String? + + /// Whether file encodings have been scanned. + public var hasScannedForEncodings: Int + + /// The known regions for localized files. + public var knownRegions: [String] + + /// The object is a reference to a PBXGroup element. + var mainGroupReference: PBXObjectReference + + /// Project main group. + public var mainGroup: PBXGroup! { + set { + mainGroupReference = newValue.reference } - - /// The relative path of the project. - public var projectDirPath: String - - /// Project references. - var projectReferences: [[String: PBXObjectReference]] - - /// Project projects. - // { - // ProductGroup = B900DB69213936CC004AEC3E /* Products group reference */; - // ProjectRef = B900DB68213936CC004AEC3E /* Project file reference */; - // }, - public var projects: [[String: PBXFileElement]] { - set { - projectReferences = newValue.map { project in - project.mapValues { $0.reference } - } - } - get { - projectReferences.map { project in - project.mapValues { $0.getObject()! } - } - } + get { + mainGroupReference.getObject() } - - private static let targetAttributesKey = "TargetAttributes" - - /// The relative root paths of the project. - public var projectRoots: [String] - - /// The objects are a reference to a PBXTarget element. - var targetReferences: [PBXObjectReference] - - /// Project targets. - public var targets: [PBXTarget] { - set { - targetReferences = newValue.references() - } - get { - targetReferences.objects() - } + } + + /// The object is a reference to a PBXGroup element. + var productsGroupReference: PBXObjectReference? + + /// Products group. + public var productsGroup: PBXGroup? { + set { + productsGroupReference = newValue?.reference } - - /// Project attributes. - /// Target attributes will be merged into this - public var attributes: [String: Any] - - /// Target attribute references. - var targetAttributeReferences: [PBXObjectReference: [String: Any]] - - /// Target attributes. - public var targetAttributes: [PBXTarget: [String: Any]] { - set { - targetAttributeReferences = [:] - for item in newValue { - targetAttributeReferences[item.key.reference] = item.value - } - } get { - var attributes: [PBXTarget: [String: Any]] = [:] - for targetAttributeReference in targetAttributeReferences { - if let object: PBXTarget = targetAttributeReference.key.getObject() { - attributes[object] = targetAttributeReference.value - } - } - return attributes - } + get { + productsGroupReference?.getObject() } - - /// Remote (`XCRemoteSwiftPackageReference`) and Local (`XCLocalSwiftPackageReference`) Package references. - var packageReferences: [PBXObjectReference]? - - /// Remote Swift packages. - @available(*, deprecated, message: "use remotePackages or localPackages.") - public var packages: [XCRemoteSwiftPackageReference] { - remotePackages + } + + /// The relative path of the project. + public var projectDirPath: String + + /// Project references. + var projectReferences: [[String: PBXObjectReference]] + + /// Project projects. + // { + // ProductGroup = B900DB69213936CC004AEC3E /* Products group reference */; + // ProjectRef = B900DB68213936CC004AEC3E /* Project file reference */; + // }, + public var projects: [[String: PBXFileElement]] { + set { + projectReferences = newValue.map { project in + project.mapValues { $0.reference } + } } - - /// Remote Swift packages. - public var remotePackages: [XCRemoteSwiftPackageReference] { - set { - setPackageReferences(newValue) - } - get { - packageReferences?.objects() ?? [] - } + get { + projectReferences.map { project in + project.mapValues { $0.getObject()! } + } } - - /// Local Swift packages. - public var localPackages: [XCLocalSwiftPackageReference] { - set { - setPackageReferences(newValue) - } - get { - packageReferences?.objects() ?? [] - } + } + + private static let targetAttributesKey = "TargetAttributes" + + /// The relative root paths of the project. + public var projectRoots: [String] + + /// The objects are a reference to a PBXTarget element. + var targetReferences: [PBXObjectReference] + + /// Project targets. + public var targets: [PBXTarget] { + set { + targetReferences = newValue.references() } - - private func setPackageReferences(_ packages: [T]) { - let newReferences = packages.references() - var finalReferences: [PBXObjectReference] = packageReferences?.filter { !($0.getObject() is T) } ?? [] - for reference in newReferences { - if !finalReferences.contains(reference) { - finalReferences.append(reference) - } - } - packageReferences = finalReferences + get { + targetReferences.objects() } - - /// Sets the attributes for the given target. - /// - /// - Parameters: - /// - attributes: attributes that will be set. - /// - target: target. - public func setTargetAttributes(_ attributes: [String: Any], target: PBXTarget) { - targetAttributeReferences[target.reference] = attributes + } + + /// Project attributes. + /// Target attributes will be merged into this + public var attributes: [String: Any] + + /// Target attribute references. + var targetAttributeReferences: [PBXObjectReference: [String: Any]] + + /// Target attributes. + public var targetAttributes: [PBXTarget: [String: Any]] { + set { + targetAttributeReferences = [:] + for item in newValue { + targetAttributeReferences[item.key.reference] = item.value + } + } get { + var attributes: [PBXTarget: [String: Any]] = [:] + for targetAttributeReference in targetAttributeReferences { + if let object: PBXTarget = targetAttributeReference.key.getObject() { + attributes[object] = targetAttributeReference.value + } + } + return attributes } - - /// Removes the attributes for the given target. - /// - /// - Parameter target: target whose attributes will be removed. - public func removeTargetAttributes(target: PBXTarget) { - targetAttributeReferences.removeValue(forKey: target.reference) + } + + /// Remote (`XCRemoteSwiftPackageReference`) and Local (`XCLocalSwiftPackageReference`) Package references. + var packageReferences: [PBXObjectReference]? + + /// Remote Swift packages. + @available(*, deprecated, message: "use remotePackages or localPackages.") + public var packages: [XCRemoteSwiftPackageReference] { + remotePackages + } + + /// Remote Swift packages. + public var remotePackages: [XCRemoteSwiftPackageReference] { + set { + setPackageReferences(newValue) } - - /// Removes the all the target attributes - public func clearAllTargetAttributes() { - targetAttributeReferences.removeAll() + get { + packageReferences?.objects() ?? [] } - - /// Returns the attributes of a given target. - /// - /// - Parameter for: target whose attributes will be returned. - /// - Returns: target attributes. - public func attributes(for target: PBXTarget) -> [String: Any]? { - targetAttributeReferences[target.reference] + } + + /// Local Swift packages. + public var localPackages: [XCLocalSwiftPackageReference] { + set { + setPackageReferences(newValue) } - - /// Adds a remote swift package - /// - /// - Parameters: - /// - repositoryURL: URL in String pointing to the location of remote Swift package - /// - productName: The product to depend on without the extension - /// - versionRequirement: Describes the rules of the version to use - /// - targetName: Target's name to link package product to - public func addSwiftPackage(repositoryURL: String, - productName: String, - versionRequirement: XCRemoteSwiftPackageReference.VersionRequirement, - targetName: String) throws -> XCRemoteSwiftPackageReference { - let objects = try objects() - - guard let target = targets.first(where: { $0.name == targetName }) else { throw PBXProjError.targetNotFound(targetName: targetName) } - - // Reference - let reference = try addSwiftPackageReference(repositoryURL: repositoryURL, - productName: productName, - versionRequirement: versionRequirement) - - // Product - let productDependency = try addSwiftPackageProduct(reference: reference, - productName: productName, - target: target) - - // Build file - let buildFile = PBXBuildFile(product: productDependency) - objects.add(object: buildFile) - - // Link the product - guard let frameworksBuildPhase = try target.frameworksBuildPhase() else { throw PBXProjError.frameworksBuildPhaseNotFound(targetName: targetName) } - frameworksBuildPhase.files?.append(buildFile) - - return reference + get { + packageReferences?.objects() ?? [] } - - /// Adds a local swift package - /// - /// - Parameters: - /// - path: Relative path to the swift package (throws an error if the path is absolute) - /// - productName: The product to depend on without the extension - /// - targetName: Target's name to link package product to - /// - addFileReference: Include a file reference to the package (defaults to main group) - public func addLocalSwiftPackage(path: Path, - productName: String, - targetName: String, - addFileReference: Bool = true) throws -> XCSwiftPackageProductDependency { - guard path.isRelative else { throw PBXProjError.pathIsAbsolute(path) } - - let objects = try objects() - - guard let target = targets.first(where: { $0.name == targetName }) else { throw PBXProjError.targetNotFound(targetName: targetName) } - - // Product - let productDependency = try addLocalSwiftPackageProduct(path: path, - productName: productName, - target: target) - - // Build file - let buildFile = PBXBuildFile(product: productDependency) - objects.add(object: buildFile) - - // Link the product - guard let frameworksBuildPhase = try target.frameworksBuildPhase() else { - throw PBXProjError.frameworksBuildPhaseNotFound(targetName: targetName) - } - - frameworksBuildPhase.files?.append(buildFile) - - // File reference - // The user might want to control adding the file's reference (to be exact when the reference is added) - // to achieve desired hierarchy of the group's children - if addFileReference { - let reference = PBXFileReference(sourceTree: .group, - name: productName, - lastKnownFileType: "folder", - path: path.string) - objects.add(object: reference) - mainGroup.children.append(reference) - } - - return productDependency + } + + private func setPackageReferences(_ packages: [T]) { + let newReferences = packages.references() + var finalReferences: [PBXObjectReference] = packageReferences?.filter { !($0.getObject() is T) } ?? [] + for reference in newReferences { + if !finalReferences.contains(reference) { + finalReferences.append(reference) + } } - - // MARK: - Init - - /// Initializes the project with its attributes - /// - /// - Parameters: - /// - name: xcodeproj's name. - /// - buildConfigurationList: project build configuration list. - /// - compatibilityVersion: project compatibility version. - /// - preferredProjectObjectVersion: preferred project object version - /// - mainGroup: project main group. - /// - developmentRegion: project has development region. - /// - hasScannedForEncodings: project has scanned for encodings. - /// - knownRegions: project known regions. - /// - productsGroup: products group. - /// - projectDirPath: project dir path. - /// - projects: projects. - /// - projectRoots: project roots. - /// - targets: project targets. - /// - packages: project's remote packages. - /// - attributes: project's attributes. - /// - targetAttributes: project target's attributes. - public init(name: String, - buildConfigurationList: XCConfigurationList, - compatibilityVersion: String?, - preferredProjectObjectVersion: Int?, - mainGroup: PBXGroup, - developmentRegion: String? = nil, - hasScannedForEncodings: Int = 0, - knownRegions: [String] = [], - productsGroup: PBXGroup? = nil, - projectDirPath: String = "", - projects: [[String: PBXFileElement]] = [], - projectRoots: [String] = [], - targets: [PBXTarget] = [], - packages: [XCRemoteSwiftPackageReference] = [], - attributes: [String: Any] = [:], - targetAttributes: [PBXTarget: [String: Any]] = [:]) { - self.name = name - buildConfigurationListReference = buildConfigurationList.reference - self.compatibilityVersion = compatibilityVersion - self.preferredProjectObjectVersion = preferredProjectObjectVersion - mainGroupReference = mainGroup.reference - self.developmentRegion = developmentRegion - self.hasScannedForEncodings = hasScannedForEncodings - self.knownRegions = knownRegions - productsGroupReference = productsGroup?.reference - self.projectDirPath = projectDirPath - projectReferences = projects.map { project in project.mapValues { $0.reference } } - self.projectRoots = projectRoots - targetReferences = targets.references() - packageReferences = packages.references() - self.attributes = attributes - targetAttributeReferences = [:] - super.init() - self.targetAttributes = targetAttributes + packageReferences = finalReferences + } + + /// Sets the attributes for the given target. + /// + /// - Parameters: + /// - attributes: attributes that will be set. + /// - target: target. + public func setTargetAttributes(_ attributes: [String: Any], target: PBXTarget) { + targetAttributeReferences[target.reference] = attributes + } + + /// Removes the attributes for the given target. + /// + /// - Parameter target: target whose attributes will be removed. + public func removeTargetAttributes(target: PBXTarget) { + targetAttributeReferences.removeValue(forKey: target.reference) + } + + /// Removes the all the target attributes + public func clearAllTargetAttributes() { + targetAttributeReferences.removeAll() + } + + /// Returns the attributes of a given target. + /// + /// - Parameter for: target whose attributes will be returned. + /// - Returns: target attributes. + public func attributes(for target: PBXTarget) -> [String: Any]? { + targetAttributeReferences[target.reference] + } + + /// Adds a remote swift package + /// + /// - Parameters: + /// - repositoryURL: URL in String pointing to the location of remote Swift package + /// - productName: The product to depend on without the extension + /// - versionRequirement: Describes the rules of the version to use + /// - targetName: Target's name to link package product to + public func addSwiftPackage(repositoryURL: String, + productName: String, + versionRequirement: XCRemoteSwiftPackageReference.VersionRequirement, + targetName: String) throws -> XCRemoteSwiftPackageReference { + let objects = try objects() + + guard let target = targets.first(where: { $0.name == targetName }) else { throw PBXProjError.targetNotFound(targetName: targetName) } + + // Reference + let reference = try addSwiftPackageReference(repositoryURL: repositoryURL, + productName: productName, + versionRequirement: versionRequirement) + + // Product + let productDependency = try addSwiftPackageProduct(reference: reference, + productName: productName, + target: target) + + // Build file + let buildFile = PBXBuildFile(product: productDependency) + objects.add(object: buildFile) + + // Link the product + guard let frameworksBuildPhase = try target.frameworksBuildPhase() else { throw PBXProjError.frameworksBuildPhaseNotFound(targetName: targetName) } + frameworksBuildPhase.files?.append(buildFile) + + return reference + } + + /// Adds a local swift package + /// + /// - Parameters: + /// - path: Relative path to the swift package (throws an error if the path is absolute) + /// - productName: The product to depend on without the extension + /// - targetName: Target's name to link package product to + /// - addFileReference: Include a file reference to the package (defaults to main group) + public func addLocalSwiftPackage(path: Path, + productName: String, + targetName: String, + addFileReference: Bool = true) throws -> XCSwiftPackageProductDependency { + guard path.isRelative else { throw PBXProjError.pathIsAbsolute(path) } + + let objects = try objects() + + guard let target = targets.first(where: { $0.name == targetName }) else { throw PBXProjError.targetNotFound(targetName: targetName) } + + // Product + let productDependency = try addLocalSwiftPackageProduct(path: path, + productName: productName, + target: target) + + // Build file + let buildFile = PBXBuildFile(product: productDependency) + objects.add(object: buildFile) + + // Link the product + guard let frameworksBuildPhase = try target.frameworksBuildPhase() else { + throw PBXProjError.frameworksBuildPhaseNotFound(targetName: targetName) } - - // MARK: - Decodable - - fileprivate enum CodingKeys: String, CodingKey { - case name - case buildConfigurationList - case compatibilityVersion - case preferredProjectObjectVersion - case developmentRegion - case hasScannedForEncodings - case knownRegions - case mainGroup - case productRefGroup - case projectDirPath - case projectReferences - case projectRoot - case projectRoots - case targets - case attributes - case packageReferences + + frameworksBuildPhase.files?.append(buildFile) + + // File reference + // The user might want to control adding the file's reference (to be exact when the reference is added) + // to achieve desired hierarchy of the group's children + if addFileReference { + let reference = PBXFileReference(sourceTree: .group, + name: productName, + lastKnownFileType: "folder", + path: path.string) + objects.add(object: reference) + mainGroup.children.append(reference) } - - public required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let referenceRepository = decoder.context.objectReferenceRepository - let objects = decoder.context.objects - name = try (container.decodeIfPresent(.name)) ?? "" - let buildConfigurationListReference: String = try container.decode(.buildConfigurationList) - self.buildConfigurationListReference = referenceRepository.getOrCreate(reference: buildConfigurationListReference, objects: objects) - compatibilityVersion = try container.decodeIfPresent(.compatibilityVersion) - preferredProjectObjectVersion = try container.decodeIfPresent(.preferredProjectObjectVersion) - developmentRegion = try container.decodeIfPresent(.developmentRegion) - let hasScannedForEncodingsString: String? = try container.decodeIfPresent(.hasScannedForEncodings) - hasScannedForEncodings = hasScannedForEncodingsString.flatMap { Int($0) } ?? 0 - knownRegions = try (container.decodeIfPresent(.knownRegions)) ?? [] - let mainGroupReference: String = try container.decode(.mainGroup) - self.mainGroupReference = referenceRepository.getOrCreate(reference: mainGroupReference, objects: objects) - if let productRefGroupReference: String = try container.decodeIfPresent(.productRefGroup) { - productsGroupReference = referenceRepository.getOrCreate(reference: productRefGroupReference, objects: objects) - } else { - productsGroupReference = nil - } - projectDirPath = try container.decodeIfPresent(.projectDirPath) ?? "" - let projectReferences: [[String: String]] = try (container.decodeIfPresent(.projectReferences)) ?? [] - self.projectReferences = projectReferences.map { references in - references.mapValues { referenceRepository.getOrCreate(reference: $0, objects: objects) } - } - if let projectRoots: [String] = try container.decodeIfPresent(.projectRoots) { - self.projectRoots = projectRoots - } else if let projectRoot: String = try container.decodeIfPresent(.projectRoot) { - projectRoots = [projectRoot] - } else { - projectRoots = [] - } - let targetReferences: [String] = try (container.decodeIfPresent(.targets)) ?? [] - self.targetReferences = targetReferences.map { referenceRepository.getOrCreate(reference: $0, objects: objects) } - - let packageRefeferenceStrings: [String] = try container.decodeIfPresent(.packageReferences) ?? [] - packageReferences = packageRefeferenceStrings.map { referenceRepository.getOrCreate(reference: $0, objects: objects) } - - var attributes = try (container.decodeIfPresent([String: Any].self, forKey: .attributes) ?? [:]) - var targetAttributeReferences: [PBXObjectReference: [String: Any]] = [:] - if let targetAttributes = attributes[PBXProject.targetAttributesKey] as? [String: [String: Any]] { - targetAttributes.forEach { targetAttributeReferences[referenceRepository.getOrCreate(reference: $0.key, objects: objects)] = $0.value } - attributes[PBXProject.targetAttributesKey] = nil - } - self.attributes = attributes - self.targetAttributeReferences = targetAttributeReferences - - try super.init(from: decoder) + + return productDependency + } + + // MARK: - Init + + /// Initializes the project with its attributes + /// + /// - Parameters: + /// - name: xcodeproj's name. + /// - buildConfigurationList: project build configuration list. + /// - compatibilityVersion: project compatibility version. + /// - preferredProjectObjectVersion: preferred project object version + /// - minimizedProjectReferenceProxies: minimized project reference proxies + /// - mainGroup: project main group. + /// - developmentRegion: project has development region. + /// - hasScannedForEncodings: project has scanned for encodings. + /// - knownRegions: project known regions. + /// - productsGroup: products group. + /// - projectDirPath: project dir path. + /// - projects: projects. + /// - projectRoots: project roots. + /// - targets: project targets. + /// - packages: project's remote packages. + /// - attributes: project's attributes. + /// - targetAttributes: project target's attributes. + public init(name: String, + buildConfigurationList: XCConfigurationList, + compatibilityVersion: String?, + preferredProjectObjectVersion: Int?, + minimizedProjectReferenceProxies: Int?, + mainGroup: PBXGroup, + developmentRegion: String? = nil, + hasScannedForEncodings: Int = 0, + knownRegions: [String] = [], + productsGroup: PBXGroup? = nil, + projectDirPath: String = "", + projects: [[String: PBXFileElement]] = [], + projectRoots: [String] = [], + targets: [PBXTarget] = [], + packages: [XCRemoteSwiftPackageReference] = [], + attributes: [String: Any] = [:], + targetAttributes: [PBXTarget: [String: Any]] = [:]) { + self.name = name + buildConfigurationListReference = buildConfigurationList.reference + self.compatibilityVersion = compatibilityVersion + self.preferredProjectObjectVersion = preferredProjectObjectVersion + self.minimizedProjectReferenceProxies = minimizedProjectReferenceProxies + mainGroupReference = mainGroup.reference + self.developmentRegion = developmentRegion + self.hasScannedForEncodings = hasScannedForEncodings + self.knownRegions = knownRegions + productsGroupReference = productsGroup?.reference + self.projectDirPath = projectDirPath + projectReferences = projects.map { project in project.mapValues { $0.reference } } + self.projectRoots = projectRoots + targetReferences = targets.references() + packageReferences = packages.references() + self.attributes = attributes + targetAttributeReferences = [:] + super.init() + self.targetAttributes = targetAttributes + } + + // MARK: - Decodable + + fileprivate enum CodingKeys: String, CodingKey { + case name + case buildConfigurationList + case compatibilityVersion + case preferredProjectObjectVersion + case minimizedProjectReferenceProxies + case developmentRegion + case hasScannedForEncodings + case knownRegions + case mainGroup + case productRefGroup + case projectDirPath + case projectReferences + case projectRoot + case projectRoots + case targets + case attributes + case packageReferences + } + + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let referenceRepository = decoder.context.objectReferenceRepository + let objects = decoder.context.objects + name = try (container.decodeIfPresent(.name)) ?? "" + let buildConfigurationListReference: String = try container.decode(.buildConfigurationList) + self.buildConfigurationListReference = referenceRepository.getOrCreate(reference: buildConfigurationListReference, objects: objects) + compatibilityVersion = try container.decodeIfPresent(.compatibilityVersion) + preferredProjectObjectVersion = if let stringValue = try container.decodeIfPresent(String.self, forKey: .preferredProjectObjectVersion) { + Int(stringValue) + } else if let intValue = try container.decodeIfPresent(Int.self, forKey: .preferredProjectObjectVersion) { + intValue + } else { + nil } - - override func isEqual(to object: Any?) -> Bool { - guard let rhs = object as? PBXProject else { return false } - return isEqual(to: rhs) + minimizedProjectReferenceProxies = if let stringValue = try container.decodeIfPresent(String.self, forKey: .minimizedProjectReferenceProxies) { + Int(stringValue) + } else if let intValue = try container.decodeIfPresent(Int.self, forKey: .minimizedProjectReferenceProxies) { + intValue + } else { + nil + } + developmentRegion = try container.decodeIfPresent(.developmentRegion) + let hasScannedForEncodingsString: String? = try container.decodeIfPresent(.hasScannedForEncodings) + hasScannedForEncodings = hasScannedForEncodingsString.flatMap { Int($0) } ?? 0 + knownRegions = try (container.decodeIfPresent(.knownRegions)) ?? [] + let mainGroupReference: String = try container.decode(.mainGroup) + self.mainGroupReference = referenceRepository.getOrCreate(reference: mainGroupReference, objects: objects) + if let productRefGroupReference: String = try container.decodeIfPresent(.productRefGroup) { + productsGroupReference = referenceRepository.getOrCreate(reference: productRefGroupReference, objects: objects) + } else { + productsGroupReference = nil + } + projectDirPath = try container.decodeIfPresent(.projectDirPath) ?? "" + let projectReferences: [[String: String]] = try (container.decodeIfPresent(.projectReferences)) ?? [] + self.projectReferences = projectReferences.map { references in + references.mapValues { referenceRepository.getOrCreate(reference: $0, objects: objects) } } + if let projectRoots: [String] = try container.decodeIfPresent(.projectRoots) { + self.projectRoots = projectRoots + } else if let projectRoot: String = try container.decodeIfPresent(.projectRoot) { + projectRoots = [projectRoot] + } else { + projectRoots = [] + } + let targetReferences: [String] = try (container.decodeIfPresent(.targets)) ?? [] + self.targetReferences = targetReferences.map { referenceRepository.getOrCreate(reference: $0, objects: objects) } + + let packageRefeferenceStrings: [String] = try container.decodeIfPresent(.packageReferences) ?? [] + packageReferences = packageRefeferenceStrings.map { referenceRepository.getOrCreate(reference: $0, objects: objects) } + + var attributes = try (container.decodeIfPresent([String: Any].self, forKey: .attributes) ?? [:]) + var targetAttributeReferences: [PBXObjectReference: [String: Any]] = [:] + if let targetAttributes = attributes[PBXProject.targetAttributesKey] as? [String: [String: Any]] { + targetAttributes.forEach { targetAttributeReferences[referenceRepository.getOrCreate(reference: $0.key, objects: objects)] = $0.value } + attributes[PBXProject.targetAttributesKey] = nil + } + self.attributes = attributes + self.targetAttributeReferences = targetAttributeReferences + + try super.init(from: decoder) + } + + override func isEqual(to object: Any?) -> Bool { + guard let rhs = object as? PBXProject else { return false } + return isEqual(to: rhs) + } } // MARK: - Helpers extension PBXProject { - /// Adds reference for remote Swift package - private func addSwiftPackageReference(repositoryURL: String, - productName: String, - versionRequirement: XCRemoteSwiftPackageReference.VersionRequirement) throws -> XCRemoteSwiftPackageReference { - let reference: XCRemoteSwiftPackageReference - if let package = remotePackages.first(where: { $0.repositoryURL == repositoryURL }) { - guard package.versionRequirement == versionRequirement else { - throw PBXProjError.multipleRemotePackages(productName: productName) - } - reference = package - } else { - reference = XCRemoteSwiftPackageReference(repositoryURL: repositoryURL, versionRequirement: versionRequirement) - try objects().add(object: reference) - remotePackages.append(reference) - } - - return reference - } - - /// Adds package product for remote Swift package - private func addSwiftPackageProduct(reference: XCRemoteSwiftPackageReference, + /// Adds reference for remote Swift package + private func addSwiftPackageReference(repositoryURL: String, productName: String, - target: PBXTarget) throws -> XCSwiftPackageProductDependency { - let objects = try objects() - - let productDependency: XCSwiftPackageProductDependency - // Avoid duplication - if let product = objects.swiftPackageProductDependencies.first(where: { $0.value.package == reference && $0.value.productName == productName })?.value { - productDependency = product - } else { - productDependency = XCSwiftPackageProductDependency(productName: productName, package: reference) - objects.add(object: productDependency) - } - target.packageProductDependencies?.append(productDependency) - - return productDependency + versionRequirement: XCRemoteSwiftPackageReference.VersionRequirement) throws -> XCRemoteSwiftPackageReference { + let reference: XCRemoteSwiftPackageReference + if let package = remotePackages.first(where: { $0.repositoryURL == repositoryURL }) { + guard package.versionRequirement == versionRequirement else { + throw PBXProjError.multipleRemotePackages(productName: productName) + } + reference = package + } else { + reference = XCRemoteSwiftPackageReference(repositoryURL: repositoryURL, versionRequirement: versionRequirement) + try objects().add(object: reference) + remotePackages.append(reference) } - - /// Adds package product for local Swift package - private func addLocalSwiftPackageProduct(path: Path, - productName: String, - target: PBXTarget) throws -> XCSwiftPackageProductDependency { - let objects = try objects() - - let productDependency: XCSwiftPackageProductDependency - // Avoid duplication - if let product = objects.swiftPackageProductDependencies.first(where: { $0.value.productName == productName }) { - guard objects.fileReferences.first(where: { $0.value.name == productName })?.value.path == path.string else { - throw PBXProjError.multipleLocalPackages(productName: productName) - } - productDependency = product.value - } else { - productDependency = XCSwiftPackageProductDependency(productName: productName) - objects.add(object: productDependency) - } - target.packageProductDependencies?.append(productDependency) - - return productDependency + + return reference + } + + /// Adds package product for remote Swift package + private func addSwiftPackageProduct(reference: XCRemoteSwiftPackageReference, + productName: String, + target: PBXTarget) throws -> XCSwiftPackageProductDependency { + let objects = try objects() + + let productDependency: XCSwiftPackageProductDependency + // Avoid duplication + if let product = objects.swiftPackageProductDependencies.first(where: { $0.value.package == reference && $0.value.productName == productName })?.value { + productDependency = product + } else { + productDependency = XCSwiftPackageProductDependency(productName: productName, package: reference) + objects.add(object: productDependency) } + target.packageProductDependencies?.append(productDependency) + + return productDependency + } + + /// Adds package product for local Swift package + private func addLocalSwiftPackageProduct(path: Path, + productName: String, + target: PBXTarget) throws -> XCSwiftPackageProductDependency { + let objects = try objects() + + let productDependency: XCSwiftPackageProductDependency + // Avoid duplication + if let product = objects.swiftPackageProductDependencies.first(where: { $0.value.productName == productName }) { + guard objects.fileReferences.first(where: { $0.value.name == productName })?.value.path == path.string else { + throw PBXProjError.multipleLocalPackages(productName: productName) + } + productDependency = product.value + } else { + productDependency = XCSwiftPackageProductDependency(productName: productName) + objects.add(object: productDependency) + } + target.packageProductDependencies?.append(productDependency) + + return productDependency + } } // MARK: - PlistSerializable extension PBXProject: PlistSerializable { - // swiftlint:disable:next function_body_length - func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { - var dictionary: [CommentedString: PlistValue] = [:] - dictionary["isa"] = .string(CommentedString(PBXProject.isa)) - let buildConfigurationListComment = "Build configuration list for PBXProject \"\(name)\"" - let buildConfigurationListCommentedString = CommentedString(buildConfigurationListReference.value, - comment: buildConfigurationListComment) - dictionary["buildConfigurationList"] = .string(buildConfigurationListCommentedString) - if let compatibilityVersion { - dictionary["compatibilityVersion"] = .string(CommentedString(compatibilityVersion)) - } - if let developmentRegion { - dictionary["developmentRegion"] = .string(CommentedString(developmentRegion)) - } - dictionary["hasScannedForEncodings"] = .string(CommentedString("\(hasScannedForEncodings)")) - - if !knownRegions.isEmpty { - dictionary["knownRegions"] = PlistValue.array(knownRegions - .map { .string(CommentedString("\($0)")) }) - } - let mainGroupObject: PBXGroup? = mainGroupReference.getObject() - dictionary["mainGroup"] = .string(CommentedString(mainGroupReference.value, comment: mainGroupObject?.fileName())) - if let preferredProjectObjectVersion { - dictionary["preferredProjectObjectVersion"] = .string(CommentedString(preferredProjectObjectVersion.description)) - } - if let productsGroupReference { - let productRefGroupObject: PBXGroup? = productsGroupReference.getObject() - dictionary["productRefGroup"] = .string(CommentedString(productsGroupReference.value, - comment: productRefGroupObject?.fileName())) - } - dictionary["projectDirPath"] = .string(CommentedString(projectDirPath)) - if projectRoots.count > 1 { - dictionary["projectRoots"] = projectRoots.plist() - } else { - dictionary["projectRoot"] = .string(CommentedString(projectRoots.first ?? "")) - } - if let projectReferences = try projectReferencesPlistValue(proj: proj) { - dictionary["projectReferences"] = projectReferences - } - dictionary["targets"] = PlistValue.array(targetReferences - .map { targetReference in - let target: PBXTarget? = targetReference.getObject() - return .string(CommentedString(targetReference.value, comment: target?.name)) - }) - - if !remotePackages.isEmpty || !localPackages.isEmpty { - let remotePackageReferences = remotePackages.map { - PlistValue.string(CommentedString($0.reference.value, comment: "XCRemoteSwiftPackageReference \"\($0.name ?? "")\"")) - } - let localPackageReferences = localPackages.map { - PlistValue.string(CommentedString($0.reference.value, comment: "XCLocalSwiftPackageReference \"\($0.name ?? "")\"")) - } - var finalPackageReferences = remotePackageReferences - finalPackageReferences.append(contentsOf: localPackageReferences) - dictionary["packageReferences"] = PlistValue.array(finalPackageReferences) - } - - var plistAttributes: [String: Any] = attributes - - // merge target attributes - var plistTargetAttributes: [String: Any] = [:] - for (reference, value) in targetAttributeReferences { - plistTargetAttributes[reference.value] = value.mapValues { value in - (value as? PBXObject)?.reference.value ?? value - } - } - plistAttributes[PBXProject.targetAttributesKey] = plistTargetAttributes - - dictionary["attributes"] = plistAttributes.plist() - - return (key: CommentedString(reference, - comment: "Project object"), - value: .dictionary(dictionary)) + // swiftlint:disable:next function_body_length + func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { + var dictionary: [CommentedString: PlistValue] = [:] + dictionary["isa"] = .string(CommentedString(PBXProject.isa)) + let buildConfigurationListComment = "Build configuration list for PBXProject \"\(name)\"" + let buildConfigurationListCommentedString = CommentedString(buildConfigurationListReference.value, + comment: buildConfigurationListComment) + dictionary["buildConfigurationList"] = .string(buildConfigurationListCommentedString) + if let compatibilityVersion { + dictionary["compatibilityVersion"] = .string(CommentedString(compatibilityVersion)) } - - private func projectReferencesPlistValue(proj _: PBXProj) throws -> PlistValue? { - guard !projectReferences.isEmpty else { - return nil - } - return .array(projectReferences.compactMap { reference in - guard let productGroupReference = reference[Xcode.ProjectReference.productGroupKey], - let projectRef = reference[Xcode.ProjectReference.projectReferenceKey] - else { - return nil - } - let producGroup: PBXGroup? = productGroupReference.getObject() - let groupName = producGroup?.fileName() - let project: PBXFileElement? = projectRef.getObject() - let fileRefName = project?.fileName() - - return [ - CommentedString(Xcode.ProjectReference.productGroupKey): PlistValue.string(CommentedString(productGroupReference.value, comment: groupName)), - CommentedString(Xcode.ProjectReference.projectReferenceKey): PlistValue.string(CommentedString(projectRef.value, comment: fileRefName)), - ] - }) + if let developmentRegion { + dictionary["developmentRegion"] = .string(CommentedString(developmentRegion)) + } + dictionary["hasScannedForEncodings"] = .string(CommentedString("\(hasScannedForEncodings)")) + + if !knownRegions.isEmpty { + dictionary["knownRegions"] = PlistValue.array(knownRegions + .map { .string(CommentedString("\($0)")) }) + } + let mainGroupObject: PBXGroup? = mainGroupReference.getObject() + dictionary["mainGroup"] = .string(CommentedString(mainGroupReference.value, comment: mainGroupObject?.fileName())) + if let preferredProjectObjectVersion { + dictionary["preferredProjectObjectVersion"] = .string(CommentedString(preferredProjectObjectVersion.description)) + } + if let minimizedProjectReferenceProxies { + dictionary["minimizedProjectReferenceProxies"] = .string(CommentedString(minimizedProjectReferenceProxies.description)) + } + if let productsGroupReference { + let productRefGroupObject: PBXGroup? = productsGroupReference.getObject() + dictionary["productRefGroup"] = .string(CommentedString(productsGroupReference.value, + comment: productRefGroupObject?.fileName())) + } + dictionary["projectDirPath"] = .string(CommentedString(projectDirPath)) + if projectRoots.count > 1 { + dictionary["projectRoots"] = projectRoots.plist() + } else { + dictionary["projectRoot"] = .string(CommentedString(projectRoots.first ?? "")) + } + if let projectReferences = try projectReferencesPlistValue(proj: proj) { + dictionary["projectReferences"] = projectReferences + } + dictionary["targets"] = PlistValue.array(targetReferences + .map { targetReference in + let target: PBXTarget? = targetReference.getObject() + return .string(CommentedString(targetReference.value, comment: target?.name)) + }) + + if !remotePackages.isEmpty || !localPackages.isEmpty { + let remotePackageReferences = remotePackages.map { + PlistValue.string(CommentedString($0.reference.value, comment: "XCRemoteSwiftPackageReference \"\($0.name ?? "")\"")) + } + let localPackageReferences = localPackages.map { + PlistValue.string(CommentedString($0.reference.value, comment: "XCLocalSwiftPackageReference \"\($0.name ?? "")\"")) + } + var finalPackageReferences = remotePackageReferences + finalPackageReferences.append(contentsOf: localPackageReferences) + dictionary["packageReferences"] = PlistValue.array(finalPackageReferences) + } + + var plistAttributes: [String: Any] = attributes + + // merge target attributes + var plistTargetAttributes: [String: Any] = [:] + for (reference, value) in targetAttributeReferences { + plistTargetAttributes[reference.value] = value.mapValues { value in + (value as? PBXObject)?.reference.value ?? value + } + } + plistAttributes[PBXProject.targetAttributesKey] = plistTargetAttributes + + dictionary["attributes"] = plistAttributes.plist() + + return (key: CommentedString(reference, + comment: "Project object"), + value: .dictionary(dictionary)) + } + + private func projectReferencesPlistValue(proj _: PBXProj) throws -> PlistValue? { + guard !projectReferences.isEmpty else { + return nil } + return .array(projectReferences.compactMap { reference in + guard let productGroupReference = reference[Xcode.ProjectReference.productGroupKey], + let projectRef = reference[Xcode.ProjectReference.projectReferenceKey] + else { + return nil + } + let producGroup: PBXGroup? = productGroupReference.getObject() + let groupName = producGroup?.fileName() + let project: PBXFileElement? = projectRef.getObject() + let fileRefName = project?.fileName() + + return [ + CommentedString(Xcode.ProjectReference.productGroupKey): PlistValue.string(CommentedString(productGroupReference.value, comment: groupName)), + CommentedString(Xcode.ProjectReference.projectReferenceKey): PlistValue.string(CommentedString(projectRef.value, comment: fileRefName)), + ] + }) + } } diff --git a/Tests/XcodeProjTests/Objects/Files/PBXContainerItemProxyTests.swift b/Tests/XcodeProjTests/Objects/Files/PBXContainerItemProxyTests.swift index f0c7fa4bd..6fb69914d 100644 --- a/Tests/XcodeProjTests/Objects/Files/PBXContainerItemProxyTests.swift +++ b/Tests/XcodeProjTests/Objects/Files/PBXContainerItemProxyTests.swift @@ -20,7 +20,7 @@ final class PBXContainerItemProxyTests: XCTestCase { func test_maintains_remoteID() { let target = PBXNativeTarget(name: "") - let project = PBXProject(name: "", buildConfigurationList: XCConfigurationList(), compatibilityVersion: "", preferredProjectObjectVersion: nil, mainGroup: PBXGroup()) + let project = PBXProject(name: "", buildConfigurationList: XCConfigurationList(), compatibilityVersion: "", preferredProjectObjectVersion: nil, minimizedProjectReferenceProxies: nil, mainGroup: PBXGroup()) let containerProxy = PBXContainerItemProxy(containerPortal: .project(project), remoteGlobalID: .object(target)) XCTAssertEqual(target.uuid, containerProxy.remoteGlobalID?.uuid) diff --git a/Tests/XcodeProjTests/Objects/Files/PBXFileElementTests.swift b/Tests/XcodeProjTests/Objects/Files/PBXFileElementTests.swift index 3a0859f27..b7b5acd81 100644 --- a/Tests/XcodeProjTests/Objects/Files/PBXFileElementTests.swift +++ b/Tests/XcodeProjTests/Objects/Files/PBXFileElementTests.swift @@ -55,6 +55,7 @@ final class PBXFileElementTests: XCTestCase { buildConfigurationList: XCConfigurationList(), compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: mainGroup) let objects = PBXObjects(objects: [project, mainGroup, fileref, group]) @@ -111,6 +112,7 @@ final class PBXFileElementTests: XCTestCase { buildConfigurationList: XCConfigurationList(), compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: rootGroup) let objects = PBXObjects(objects: [fileref, nestedGroup, rootGroup, project]) diff --git a/Tests/XcodeProjTests/Objects/Files/PBXGroupTests.swift b/Tests/XcodeProjTests/Objects/Files/PBXGroupTests.swift index 41080cf09..fae0e530d 100644 --- a/Tests/XcodeProjTests/Objects/Files/PBXGroupTests.swift +++ b/Tests/XcodeProjTests/Objects/Files/PBXGroupTests.swift @@ -19,6 +19,7 @@ final class PBXGroupTests: XCTestCase { buildConfigurationList: XCConfigurationList(), compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: group) project.add(object: pbxProject) @@ -141,6 +142,7 @@ final class PBXGroupTests: XCTestCase { buildConfigurationList: XCConfigurationList(), compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: group) project.add(object: pbxProject) @@ -166,6 +168,7 @@ final class PBXGroupTests: XCTestCase { buildConfigurationList: XCConfigurationList(), compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: group) project.add(object: pbxProject) @@ -190,6 +193,7 @@ final class PBXGroupTests: XCTestCase { buildConfigurationList: XCConfigurationList(), compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: group) project.add(object: pbxProject) diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProject+Fixtures.swift b/Tests/XcodeProjTests/Objects/Project/PBXProject+Fixtures.swift index d59c3e9c8..dd51d4143 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProject+Fixtures.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProject+Fixtures.swift @@ -11,6 +11,7 @@ extension PBXProject { buildConfigurationList: buildConfigurationList, compatibilityVersion: compatibilityVersion, preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: mainGroup) } } diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift index 8353a8a80..dc79c4698 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift @@ -13,6 +13,7 @@ final class PBXProjectTests: XCTestCase { buildConfigurationList: XCConfigurationList(), compatibilityVersion: "", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: PBXGroup(), attributes: ["LastUpgradeCheck": "0940"], targetAttributes: [target: ["TestTargetID": "123"]]) @@ -56,6 +57,7 @@ final class PBXProjectTests: XCTestCase { buildConfigurationList: configurationList, compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: mainGroup, targets: [target]) @@ -92,6 +94,7 @@ final class PBXProjectTests: XCTestCase { buildConfigurationList: configurationList, compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: mainGroup, targets: [target]) @@ -129,6 +132,7 @@ final class PBXProjectTests: XCTestCase { buildConfigurationList: configurationList, compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: mainGroup, targets: [target]) @@ -170,6 +174,7 @@ final class PBXProjectTests: XCTestCase { buildConfigurationList: configurationList, compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: mainGroup, targets: [target]) @@ -224,6 +229,7 @@ final class PBXProjectTests: XCTestCase { buildConfigurationList: configurationList, compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: mainGroup, targets: [target, secondTarget]) @@ -299,6 +305,7 @@ final class PBXProjectTests: XCTestCase { buildConfigurationList: configurationList, compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: mainGroup, targets: [target, secondTarget]) diff --git a/Tests/XcodeProjTests/Objects/Targets/PBXAggregateTargetTests.swift b/Tests/XcodeProjTests/Objects/Targets/PBXAggregateTargetTests.swift index 3083d623e..09947e76c 100644 --- a/Tests/XcodeProjTests/Objects/Targets/PBXAggregateTargetTests.swift +++ b/Tests/XcodeProjTests/Objects/Targets/PBXAggregateTargetTests.swift @@ -40,6 +40,7 @@ final class PBXAggregateTargetTests: XCTestCase { buildConfigurationList: configurationList, compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: mainGroup) objects.add(object: project) diff --git a/Tests/XcodeProjTests/Objects/Targets/PBXNativeTargetTests.swift b/Tests/XcodeProjTests/Objects/Targets/PBXNativeTargetTests.swift index 3a08b7aa9..11aa27581 100644 --- a/Tests/XcodeProjTests/Objects/Targets/PBXNativeTargetTests.swift +++ b/Tests/XcodeProjTests/Objects/Targets/PBXNativeTargetTests.swift @@ -41,6 +41,7 @@ final class PBXNativeTargetTests: XCTestCase { buildConfigurationList: configurationList, compatibilityVersion: "0", preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: mainGroup) objects.add(object: project) diff --git a/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift b/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift index 8bcb72580..89dfb9c47 100644 --- a/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift +++ b/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift @@ -4,6 +4,12 @@ import XCTest @testable import XcodeProj final class XcodeProjIntegrationTests: XCTestCase { + + func test_write_xcode16Project() throws { + try testReadWriteProducesNoDiff(from: xcode16ProjectPath, + initModel: XcodeProj.init(path:)) + } + func test_read_iosXcodeProj() throws { let subject = try XcodeProj(path: iosProjectPath) assert(project: subject) @@ -95,6 +101,10 @@ final class XcodeProjIntegrationTests: XCTestCase { private var iosProjectPath: Path { fixturesPath() + "iOS/Project.xcodeproj" } + + private var xcode16ProjectPath: Path { + fixturesPath() + "Xcode16/Test.xcodeproj" + } private var synchronizedRootGroupsFixturePath: Path { fixturesPath() + "SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj" diff --git a/Tests/XcodeProjTests/Utils/ReferenceGeneratorTests.swift b/Tests/XcodeProjTests/Utils/ReferenceGeneratorTests.swift index e9d15059c..6df8959c3 100644 --- a/Tests/XcodeProjTests/Utils/ReferenceGeneratorTests.swift +++ b/Tests/XcodeProjTests/Utils/ReferenceGeneratorTests.swift @@ -95,6 +95,7 @@ private extension PBXProj { buildConfigurationList: XCConfigurationList.fixture(), compatibilityVersion: Xcode.Default.compatibilityVersion, preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, mainGroup: mainGroup) add(object: mainGroup) From 037ac54867504e9324c52185d83ff25f88727b71 Mon Sep 17 00:00:00 2001 From: Pedro Date: Tue, 8 Oct 2024 18:26:37 +0200 Subject: [PATCH 2/3] Fix tests --- .github/workflows/xcodeproj.yml | 6 + .../PBXFileSystemSynchronizedRootGroup.swift | 207 ++- .../Objects/Project/PBXProject.swift | 1128 ++++++++--------- .../Files/PBXContainerItemProxyTests.swift | 2 +- .../Objects/Project/PBXProjEncoderTests.swift | 13 +- .../Project/XcodeProjIntegrationTests.swift | 15 +- 6 files changed, 693 insertions(+), 678 deletions(-) diff --git a/.github/workflows/xcodeproj.yml b/.github/workflows/xcodeproj.yml index 904880cdb..abe2b1317 100644 --- a/.github/workflows/xcodeproj.yml +++ b/.github/workflows/xcodeproj.yml @@ -20,6 +20,8 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v3 + - name: Select Xcode 16 + run: sudo xcode-select -switch /Applications/Xcode_16.app/Contents/Developer - uses: jdx/mise-action@v2 - name: Build run: mise run build @@ -36,6 +38,8 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v3 + - name: Select Xcode 16 + run: sudo xcode-select -switch /Applications/Xcode_16.app/Contents/Developer - uses: jdx/mise-action@v2 - name: Run tests run: mise run test @@ -54,5 +58,7 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v3 + - name: Select Xcode 16 + run: sudo xcode-select -switch /Applications/Xcode_16.app/Contents/Developer - uses: jdx/mise-action@v2 - run: mise run lint diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift index 1c17b6458..882d27db5 100644 --- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift +++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift @@ -2,113 +2,112 @@ import Foundation import PathKit public class PBXFileSystemSynchronizedRootGroup: PBXFileElement { - /// It maps relative paths inside the synchronized root group to a particular file type. - /// If a path doesn't have a particular file type specified, Xcode defaults to the default file type - /// based on the extension of the file. - public var explicitFileTypes: [String: String]? - - /// Returns the references of the exceptions. - var exceptionsReferences: [PBXObjectReference]? - - /// It returns a list of exception objects that override the configuration for some children - /// in the synchronized root group. - public var exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet]? { - set { - exceptionsReferences = newValue?.references() + /// It maps relative paths inside the synchronized root group to a particular file type. + /// If a path doesn't have a particular file type specified, Xcode defaults to the default file type + /// based on the extension of the file. + public var explicitFileTypes: [String: String]? + + /// Returns the references of the exceptions. + var exceptionsReferences: [PBXObjectReference]? + + /// It returns a list of exception objects that override the configuration for some children + /// in the synchronized root group. + public var exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet]? { + set { + exceptionsReferences = newValue?.references() + } + get { + exceptionsReferences?.objects() + } + } + + /// A list of relative paths to children folder whose configuration is overriden. + public var explicitFolders: [String]? + + /// Initializes the file element with its properties. + /// + /// - Parameters: + /// - sourceTree: file source tree. + /// - path: object relative path from `sourceTree`, if different than `name`. + /// - name: object name. + /// - includeInIndex: should the IDE index the object? + /// - usesTabs: object uses tabs. + /// - indentWidth: the number of positions to indent blocks of code + /// - tabWidth: the visual width of tab characters + /// - wrapsLines: should the IDE wrap lines when editing the object? + /// - explicitFileTypes: It maps relative paths inside the synchronized root group to a particular file type. + /// - exceptions: It returns a list of exception objects that override the configuration for some children in the synchronized root group. + /// - explicitFolders: A list of relative paths to children folder whose configuration is overriden. + public init(sourceTree: PBXSourceTree? = nil, + path: String? = nil, + name: String? = nil, + includeInIndex: Bool? = nil, + usesTabs: Bool? = nil, + indentWidth: UInt? = nil, + tabWidth: UInt? = nil, + wrapsLines: Bool? = nil, + explicitFileTypes: [String: String] = [:], + exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] = [], + explicitFolders: [String] = []) { + self.explicitFileTypes = explicitFileTypes + exceptionsReferences = exceptions.references() + self.explicitFolders = explicitFolders + super.init(sourceTree: sourceTree, + path: path, + name: name, + includeInIndex: includeInIndex, + usesTabs: usesTabs, + indentWidth: indentWidth, + tabWidth: tabWidth, + wrapsLines: wrapsLines) } - get { - exceptionsReferences?.objects() + + // MARK: - Decodable + + fileprivate enum CodingKeys: String, CodingKey { + case explicitFileTypes + case exceptions + case explicitFolders } - } - - /// A list of relative paths to children folder whose configuration is overriden. - public var explicitFolders: [String]? - - /// Initializes the file element with its properties. - /// - /// - Parameters: - /// - sourceTree: file source tree. - /// - path: object relative path from `sourceTree`, if different than `name`. - /// - name: object name. - /// - includeInIndex: should the IDE index the object? - /// - usesTabs: object uses tabs. - /// - indentWidth: the number of positions to indent blocks of code - /// - tabWidth: the visual width of tab characters - /// - wrapsLines: should the IDE wrap lines when editing the object? - /// - explicitFileTypes: It maps relative paths inside the synchronized root group to a particular file type. - /// - exceptions: It returns a list of exception objects that override the configuration for some children in the synchronized root group. - /// - explicitFolders: A list of relative paths to children folder whose configuration is overriden. - public init(sourceTree: PBXSourceTree? = nil, - path: String? = nil, - name: String? = nil, - includeInIndex: Bool? = nil, - usesTabs: Bool? = nil, - indentWidth: UInt? = nil, - tabWidth: UInt? = nil, - wrapsLines: Bool? = nil, - explicitFileTypes: [String: String] = [:], - exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] = [], - explicitFolders: [String] = []) { - self.explicitFileTypes = explicitFileTypes - exceptionsReferences = exceptions.references() - self.explicitFolders = explicitFolders - super.init(sourceTree: sourceTree, - path: path, - name: name, - includeInIndex: includeInIndex, - usesTabs: usesTabs, - indentWidth: indentWidth, - tabWidth: tabWidth, - wrapsLines: wrapsLines) - } - - // MARK: - Decodable - - fileprivate enum CodingKeys: String, CodingKey { - case explicitFileTypes - case exceptions - case explicitFolders - } - - public required init(from decoder: Decoder) throws { - let objects = decoder.context.objects - let objectReferenceRepository = decoder.context.objectReferenceRepository - let container = try decoder.container(keyedBy: CodingKeys.self) - explicitFileTypes = try container.decodeIfPresent(.explicitFileTypes) - let exceptionsReferences: [String] = try (container.decodeIfPresent(.exceptions)) ?? [] - self.exceptionsReferences = exceptionsReferences.map { objectReferenceRepository.getOrCreate(reference: $0, objects: objects) } - explicitFolders = try container.decodeIfPresent(.explicitFolders) - try super.init(from: decoder) - } - - // MARK: - PlistSerializable - - override var multiline: Bool { true } - - override func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { - var dictionary: [CommentedString: PlistValue] = try super.plistKeyAndValue(proj: proj, reference: reference).value.dictionary ?? [:] - dictionary["isa"] = .string(CommentedString(type(of: self).isa)) - if let exceptionsReferences, !exceptionsReferences.isEmpty { - dictionary["exceptions"] = .array(exceptionsReferences.map { exceptionReference in - .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet")) - }) + + public required init(from decoder: Decoder) throws { + let objects = decoder.context.objects + let objectReferenceRepository = decoder.context.objectReferenceRepository + let container = try decoder.container(keyedBy: CodingKeys.self) + explicitFileTypes = try container.decodeIfPresent(.explicitFileTypes) + let exceptionsReferences: [String] = try (container.decodeIfPresent(.exceptions)) ?? [] + self.exceptionsReferences = exceptionsReferences.map { objectReferenceRepository.getOrCreate(reference: $0, objects: objects) } + explicitFolders = try container.decodeIfPresent(.explicitFolders) + try super.init(from: decoder) } - if let explicitFileTypes { - dictionary["explicitFileTypes"] = .dictionary(Dictionary(uniqueKeysWithValues: explicitFileTypes.map { relativePath, fileType in - (CommentedString(relativePath), .string(CommentedString(fileType))) - })) + + // MARK: - PlistSerializable + + override var multiline: Bool { true } + + override func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { + var dictionary: [CommentedString: PlistValue] = try super.plistKeyAndValue(proj: proj, reference: reference).value.dictionary ?? [:] + dictionary["isa"] = .string(CommentedString(type(of: self).isa)) + if let exceptionsReferences, !exceptionsReferences.isEmpty { + dictionary["exceptions"] = .array(exceptionsReferences.map { exceptionReference in + .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet")) + }) + } + if let explicitFileTypes { + dictionary["explicitFileTypes"] = .dictionary(Dictionary(uniqueKeysWithValues: explicitFileTypes.map { relativePath, fileType in + (CommentedString(relativePath), .string(CommentedString(fileType))) + })) + } + if let explicitFolders { + dictionary["explicitFolders"] = .array(explicitFolders.map { .string(CommentedString($0)) }) + } + return (key: CommentedString(reference, + comment: name ?? path), + value: .dictionary(dictionary)) } - if let explicitFolders { - dictionary["explicitFolders"] = .array(explicitFolders.map { .string(CommentedString($0)) }) - + + override func isEqual(to object: Any?) -> Bool { + guard let rhs = object as? PBXFileSystemSynchronizedRootGroup else { return false } + return isEqual(to: rhs) } - return (key: CommentedString(reference, - comment: name ?? path), - value: .dictionary(dictionary)) - } - - override func isEqual(to object: Any?) -> Bool { - guard let rhs = object as? PBXFileSystemSynchronizedRootGroup else { return false } - return isEqual(to: rhs) - } } diff --git a/Sources/XcodeProj/Objects/Project/PBXProject.swift b/Sources/XcodeProj/Objects/Project/PBXProject.swift index 4f37bbaaa..50dd148d0 100644 --- a/Sources/XcodeProj/Objects/Project/PBXProject.swift +++ b/Sources/XcodeProj/Objects/Project/PBXProject.swift @@ -2,603 +2,603 @@ import Foundation import PathKit public final class PBXProject: PBXObject { - // MARK: - Attributes - - /// Project name - public var name: String - - /// Build configuration list reference. - var buildConfigurationListReference: PBXObjectReference - - /// Build configuration list. - public var buildConfigurationList: XCConfigurationList! { - set { - buildConfigurationListReference = newValue.reference - } - get { - buildConfigurationListReference.getObject() - } - } - - /// A string representation of the XcodeCompatibilityVersion. - public var compatibilityVersion: String? - - /// An int representation of the PreferredProjectObjectVersion. - public var preferredProjectObjectVersion: Int? - - /// An int representation of the minimizedProjectReferenceProxies attribute - public var minimizedProjectReferenceProxies: Int? - - /// The region of development. - public var developmentRegion: String? - - /// Whether file encodings have been scanned. - public var hasScannedForEncodings: Int - - /// The known regions for localized files. - public var knownRegions: [String] - - /// The object is a reference to a PBXGroup element. - var mainGroupReference: PBXObjectReference - - /// Project main group. - public var mainGroup: PBXGroup! { - set { - mainGroupReference = newValue.reference - } - get { - mainGroupReference.getObject() - } - } - - /// The object is a reference to a PBXGroup element. - var productsGroupReference: PBXObjectReference? - - /// Products group. - public var productsGroup: PBXGroup? { - set { - productsGroupReference = newValue?.reference + // MARK: - Attributes + + /// Project name + public var name: String + + /// Build configuration list reference. + var buildConfigurationListReference: PBXObjectReference + + /// Build configuration list. + public var buildConfigurationList: XCConfigurationList! { + set { + buildConfigurationListReference = newValue.reference + } + get { + buildConfigurationListReference.getObject() + } } - get { - productsGroupReference?.getObject() + + /// A string representation of the XcodeCompatibilityVersion. + public var compatibilityVersion: String? + + /// An int representation of the PreferredProjectObjectVersion. + public var preferredProjectObjectVersion: Int? + + /// An int representation of the minimizedProjectReferenceProxies attribute + public var minimizedProjectReferenceProxies: Int? + + /// The region of development. + public var developmentRegion: String? + + /// Whether file encodings have been scanned. + public var hasScannedForEncodings: Int + + /// The known regions for localized files. + public var knownRegions: [String] + + /// The object is a reference to a PBXGroup element. + var mainGroupReference: PBXObjectReference + + /// Project main group. + public var mainGroup: PBXGroup! { + set { + mainGroupReference = newValue.reference + } + get { + mainGroupReference.getObject() + } } - } - - /// The relative path of the project. - public var projectDirPath: String - - /// Project references. - var projectReferences: [[String: PBXObjectReference]] - - /// Project projects. - // { - // ProductGroup = B900DB69213936CC004AEC3E /* Products group reference */; - // ProjectRef = B900DB68213936CC004AEC3E /* Project file reference */; - // }, - public var projects: [[String: PBXFileElement]] { - set { - projectReferences = newValue.map { project in - project.mapValues { $0.reference } - } + + /// The object is a reference to a PBXGroup element. + var productsGroupReference: PBXObjectReference? + + /// Products group. + public var productsGroup: PBXGroup? { + set { + productsGroupReference = newValue?.reference + } + get { + productsGroupReference?.getObject() + } } - get { - projectReferences.map { project in - project.mapValues { $0.getObject()! } - } + + /// The relative path of the project. + public var projectDirPath: String + + /// Project references. + var projectReferences: [[String: PBXObjectReference]] + + /// Project projects. + // { + // ProductGroup = B900DB69213936CC004AEC3E /* Products group reference */; + // ProjectRef = B900DB68213936CC004AEC3E /* Project file reference */; + // }, + public var projects: [[String: PBXFileElement]] { + set { + projectReferences = newValue.map { project in + project.mapValues { $0.reference } + } + } + get { + projectReferences.map { project in + project.mapValues { $0.getObject()! } + } + } } - } - - private static let targetAttributesKey = "TargetAttributes" - - /// The relative root paths of the project. - public var projectRoots: [String] - - /// The objects are a reference to a PBXTarget element. - var targetReferences: [PBXObjectReference] - - /// Project targets. - public var targets: [PBXTarget] { - set { - targetReferences = newValue.references() + + private static let targetAttributesKey = "TargetAttributes" + + /// The relative root paths of the project. + public var projectRoots: [String] + + /// The objects are a reference to a PBXTarget element. + var targetReferences: [PBXObjectReference] + + /// Project targets. + public var targets: [PBXTarget] { + set { + targetReferences = newValue.references() + } + get { + targetReferences.objects() + } } - get { - targetReferences.objects() + + /// Project attributes. + /// Target attributes will be merged into this + public var attributes: [String: Any] + + /// Target attribute references. + var targetAttributeReferences: [PBXObjectReference: [String: Any]] + + /// Target attributes. + public var targetAttributes: [PBXTarget: [String: Any]] { + set { + targetAttributeReferences = [:] + for item in newValue { + targetAttributeReferences[item.key.reference] = item.value + } + } get { + var attributes: [PBXTarget: [String: Any]] = [:] + for targetAttributeReference in targetAttributeReferences { + if let object: PBXTarget = targetAttributeReference.key.getObject() { + attributes[object] = targetAttributeReference.value + } + } + return attributes + } } - } - - /// Project attributes. - /// Target attributes will be merged into this - public var attributes: [String: Any] - - /// Target attribute references. - var targetAttributeReferences: [PBXObjectReference: [String: Any]] - - /// Target attributes. - public var targetAttributes: [PBXTarget: [String: Any]] { - set { - targetAttributeReferences = [:] - for item in newValue { - targetAttributeReferences[item.key.reference] = item.value - } - } get { - var attributes: [PBXTarget: [String: Any]] = [:] - for targetAttributeReference in targetAttributeReferences { - if let object: PBXTarget = targetAttributeReference.key.getObject() { - attributes[object] = targetAttributeReference.value - } - } - return attributes + + /// Remote (`XCRemoteSwiftPackageReference`) and Local (`XCLocalSwiftPackageReference`) Package references. + var packageReferences: [PBXObjectReference]? + + /// Remote Swift packages. + @available(*, deprecated, message: "use remotePackages or localPackages.") + public var packages: [XCRemoteSwiftPackageReference] { + remotePackages } - } - - /// Remote (`XCRemoteSwiftPackageReference`) and Local (`XCLocalSwiftPackageReference`) Package references. - var packageReferences: [PBXObjectReference]? - - /// Remote Swift packages. - @available(*, deprecated, message: "use remotePackages or localPackages.") - public var packages: [XCRemoteSwiftPackageReference] { - remotePackages - } - - /// Remote Swift packages. - public var remotePackages: [XCRemoteSwiftPackageReference] { - set { - setPackageReferences(newValue) + + /// Remote Swift packages. + public var remotePackages: [XCRemoteSwiftPackageReference] { + set { + setPackageReferences(newValue) + } + get { + packageReferences?.objects() ?? [] + } } - get { - packageReferences?.objects() ?? [] + + /// Local Swift packages. + public var localPackages: [XCLocalSwiftPackageReference] { + set { + setPackageReferences(newValue) + } + get { + packageReferences?.objects() ?? [] + } } - } - - /// Local Swift packages. - public var localPackages: [XCLocalSwiftPackageReference] { - set { - setPackageReferences(newValue) + + private func setPackageReferences(_ packages: [T]) { + let newReferences = packages.references() + var finalReferences: [PBXObjectReference] = packageReferences?.filter { !($0.getObject() is T) } ?? [] + for reference in newReferences { + if !finalReferences.contains(reference) { + finalReferences.append(reference) + } + } + packageReferences = finalReferences } - get { - packageReferences?.objects() ?? [] + + /// Sets the attributes for the given target. + /// + /// - Parameters: + /// - attributes: attributes that will be set. + /// - target: target. + public func setTargetAttributes(_ attributes: [String: Any], target: PBXTarget) { + targetAttributeReferences[target.reference] = attributes } - } - - private func setPackageReferences(_ packages: [T]) { - let newReferences = packages.references() - var finalReferences: [PBXObjectReference] = packageReferences?.filter { !($0.getObject() is T) } ?? [] - for reference in newReferences { - if !finalReferences.contains(reference) { - finalReferences.append(reference) - } + + /// Removes the attributes for the given target. + /// + /// - Parameter target: target whose attributes will be removed. + public func removeTargetAttributes(target: PBXTarget) { + targetAttributeReferences.removeValue(forKey: target.reference) } - packageReferences = finalReferences - } - - /// Sets the attributes for the given target. - /// - /// - Parameters: - /// - attributes: attributes that will be set. - /// - target: target. - public func setTargetAttributes(_ attributes: [String: Any], target: PBXTarget) { - targetAttributeReferences[target.reference] = attributes - } - - /// Removes the attributes for the given target. - /// - /// - Parameter target: target whose attributes will be removed. - public func removeTargetAttributes(target: PBXTarget) { - targetAttributeReferences.removeValue(forKey: target.reference) - } - - /// Removes the all the target attributes - public func clearAllTargetAttributes() { - targetAttributeReferences.removeAll() - } - - /// Returns the attributes of a given target. - /// - /// - Parameter for: target whose attributes will be returned. - /// - Returns: target attributes. - public func attributes(for target: PBXTarget) -> [String: Any]? { - targetAttributeReferences[target.reference] - } - - /// Adds a remote swift package - /// - /// - Parameters: - /// - repositoryURL: URL in String pointing to the location of remote Swift package - /// - productName: The product to depend on without the extension - /// - versionRequirement: Describes the rules of the version to use - /// - targetName: Target's name to link package product to - public func addSwiftPackage(repositoryURL: String, - productName: String, - versionRequirement: XCRemoteSwiftPackageReference.VersionRequirement, - targetName: String) throws -> XCRemoteSwiftPackageReference { - let objects = try objects() - - guard let target = targets.first(where: { $0.name == targetName }) else { throw PBXProjError.targetNotFound(targetName: targetName) } - - // Reference - let reference = try addSwiftPackageReference(repositoryURL: repositoryURL, - productName: productName, - versionRequirement: versionRequirement) - - // Product - let productDependency = try addSwiftPackageProduct(reference: reference, - productName: productName, - target: target) - - // Build file - let buildFile = PBXBuildFile(product: productDependency) - objects.add(object: buildFile) - - // Link the product - guard let frameworksBuildPhase = try target.frameworksBuildPhase() else { throw PBXProjError.frameworksBuildPhaseNotFound(targetName: targetName) } - frameworksBuildPhase.files?.append(buildFile) - - return reference - } - - /// Adds a local swift package - /// - /// - Parameters: - /// - path: Relative path to the swift package (throws an error if the path is absolute) - /// - productName: The product to depend on without the extension - /// - targetName: Target's name to link package product to - /// - addFileReference: Include a file reference to the package (defaults to main group) - public func addLocalSwiftPackage(path: Path, - productName: String, - targetName: String, - addFileReference: Bool = true) throws -> XCSwiftPackageProductDependency { - guard path.isRelative else { throw PBXProjError.pathIsAbsolute(path) } - - let objects = try objects() - - guard let target = targets.first(where: { $0.name == targetName }) else { throw PBXProjError.targetNotFound(targetName: targetName) } - - // Product - let productDependency = try addLocalSwiftPackageProduct(path: path, - productName: productName, - target: target) - - // Build file - let buildFile = PBXBuildFile(product: productDependency) - objects.add(object: buildFile) - - // Link the product - guard let frameworksBuildPhase = try target.frameworksBuildPhase() else { - throw PBXProjError.frameworksBuildPhaseNotFound(targetName: targetName) + + /// Removes the all the target attributes + public func clearAllTargetAttributes() { + targetAttributeReferences.removeAll() } - - frameworksBuildPhase.files?.append(buildFile) - - // File reference - // The user might want to control adding the file's reference (to be exact when the reference is added) - // to achieve desired hierarchy of the group's children - if addFileReference { - let reference = PBXFileReference(sourceTree: .group, - name: productName, - lastKnownFileType: "folder", - path: path.string) - objects.add(object: reference) - mainGroup.children.append(reference) + + /// Returns the attributes of a given target. + /// + /// - Parameter for: target whose attributes will be returned. + /// - Returns: target attributes. + public func attributes(for target: PBXTarget) -> [String: Any]? { + targetAttributeReferences[target.reference] } - - return productDependency - } - - // MARK: - Init - - /// Initializes the project with its attributes - /// - /// - Parameters: - /// - name: xcodeproj's name. - /// - buildConfigurationList: project build configuration list. - /// - compatibilityVersion: project compatibility version. - /// - preferredProjectObjectVersion: preferred project object version - /// - minimizedProjectReferenceProxies: minimized project reference proxies - /// - mainGroup: project main group. - /// - developmentRegion: project has development region. - /// - hasScannedForEncodings: project has scanned for encodings. - /// - knownRegions: project known regions. - /// - productsGroup: products group. - /// - projectDirPath: project dir path. - /// - projects: projects. - /// - projectRoots: project roots. - /// - targets: project targets. - /// - packages: project's remote packages. - /// - attributes: project's attributes. - /// - targetAttributes: project target's attributes. - public init(name: String, - buildConfigurationList: XCConfigurationList, - compatibilityVersion: String?, - preferredProjectObjectVersion: Int?, - minimizedProjectReferenceProxies: Int?, - mainGroup: PBXGroup, - developmentRegion: String? = nil, - hasScannedForEncodings: Int = 0, - knownRegions: [String] = [], - productsGroup: PBXGroup? = nil, - projectDirPath: String = "", - projects: [[String: PBXFileElement]] = [], - projectRoots: [String] = [], - targets: [PBXTarget] = [], - packages: [XCRemoteSwiftPackageReference] = [], - attributes: [String: Any] = [:], - targetAttributes: [PBXTarget: [String: Any]] = [:]) { - self.name = name - buildConfigurationListReference = buildConfigurationList.reference - self.compatibilityVersion = compatibilityVersion - self.preferredProjectObjectVersion = preferredProjectObjectVersion - self.minimizedProjectReferenceProxies = minimizedProjectReferenceProxies - mainGroupReference = mainGroup.reference - self.developmentRegion = developmentRegion - self.hasScannedForEncodings = hasScannedForEncodings - self.knownRegions = knownRegions - productsGroupReference = productsGroup?.reference - self.projectDirPath = projectDirPath - projectReferences = projects.map { project in project.mapValues { $0.reference } } - self.projectRoots = projectRoots - targetReferences = targets.references() - packageReferences = packages.references() - self.attributes = attributes - targetAttributeReferences = [:] - super.init() - self.targetAttributes = targetAttributes - } - - // MARK: - Decodable - - fileprivate enum CodingKeys: String, CodingKey { - case name - case buildConfigurationList - case compatibilityVersion - case preferredProjectObjectVersion - case minimizedProjectReferenceProxies - case developmentRegion - case hasScannedForEncodings - case knownRegions - case mainGroup - case productRefGroup - case projectDirPath - case projectReferences - case projectRoot - case projectRoots - case targets - case attributes - case packageReferences - } - - public required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let referenceRepository = decoder.context.objectReferenceRepository - let objects = decoder.context.objects - name = try (container.decodeIfPresent(.name)) ?? "" - let buildConfigurationListReference: String = try container.decode(.buildConfigurationList) - self.buildConfigurationListReference = referenceRepository.getOrCreate(reference: buildConfigurationListReference, objects: objects) - compatibilityVersion = try container.decodeIfPresent(.compatibilityVersion) - preferredProjectObjectVersion = if let stringValue = try container.decodeIfPresent(String.self, forKey: .preferredProjectObjectVersion) { - Int(stringValue) - } else if let intValue = try container.decodeIfPresent(Int.self, forKey: .preferredProjectObjectVersion) { - intValue - } else { - nil + + /// Adds a remote swift package + /// + /// - Parameters: + /// - repositoryURL: URL in String pointing to the location of remote Swift package + /// - productName: The product to depend on without the extension + /// - versionRequirement: Describes the rules of the version to use + /// - targetName: Target's name to link package product to + public func addSwiftPackage(repositoryURL: String, + productName: String, + versionRequirement: XCRemoteSwiftPackageReference.VersionRequirement, + targetName: String) throws -> XCRemoteSwiftPackageReference { + let objects = try objects() + + guard let target = targets.first(where: { $0.name == targetName }) else { throw PBXProjError.targetNotFound(targetName: targetName) } + + // Reference + let reference = try addSwiftPackageReference(repositoryURL: repositoryURL, + productName: productName, + versionRequirement: versionRequirement) + + // Product + let productDependency = try addSwiftPackageProduct(reference: reference, + productName: productName, + target: target) + + // Build file + let buildFile = PBXBuildFile(product: productDependency) + objects.add(object: buildFile) + + // Link the product + guard let frameworksBuildPhase = try target.frameworksBuildPhase() else { throw PBXProjError.frameworksBuildPhaseNotFound(targetName: targetName) } + frameworksBuildPhase.files?.append(buildFile) + + return reference } - minimizedProjectReferenceProxies = if let stringValue = try container.decodeIfPresent(String.self, forKey: .minimizedProjectReferenceProxies) { - Int(stringValue) - } else if let intValue = try container.decodeIfPresent(Int.self, forKey: .minimizedProjectReferenceProxies) { - intValue - } else { - nil + + /// Adds a local swift package + /// + /// - Parameters: + /// - path: Relative path to the swift package (throws an error if the path is absolute) + /// - productName: The product to depend on without the extension + /// - targetName: Target's name to link package product to + /// - addFileReference: Include a file reference to the package (defaults to main group) + public func addLocalSwiftPackage(path: Path, + productName: String, + targetName: String, + addFileReference: Bool = true) throws -> XCSwiftPackageProductDependency { + guard path.isRelative else { throw PBXProjError.pathIsAbsolute(path) } + + let objects = try objects() + + guard let target = targets.first(where: { $0.name == targetName }) else { throw PBXProjError.targetNotFound(targetName: targetName) } + + // Product + let productDependency = try addLocalSwiftPackageProduct(path: path, + productName: productName, + target: target) + + // Build file + let buildFile = PBXBuildFile(product: productDependency) + objects.add(object: buildFile) + + // Link the product + guard let frameworksBuildPhase = try target.frameworksBuildPhase() else { + throw PBXProjError.frameworksBuildPhaseNotFound(targetName: targetName) + } + + frameworksBuildPhase.files?.append(buildFile) + + // File reference + // The user might want to control adding the file's reference (to be exact when the reference is added) + // to achieve desired hierarchy of the group's children + if addFileReference { + let reference = PBXFileReference(sourceTree: .group, + name: productName, + lastKnownFileType: "folder", + path: path.string) + objects.add(object: reference) + mainGroup.children.append(reference) + } + + return productDependency } - developmentRegion = try container.decodeIfPresent(.developmentRegion) - let hasScannedForEncodingsString: String? = try container.decodeIfPresent(.hasScannedForEncodings) - hasScannedForEncodings = hasScannedForEncodingsString.flatMap { Int($0) } ?? 0 - knownRegions = try (container.decodeIfPresent(.knownRegions)) ?? [] - let mainGroupReference: String = try container.decode(.mainGroup) - self.mainGroupReference = referenceRepository.getOrCreate(reference: mainGroupReference, objects: objects) - if let productRefGroupReference: String = try container.decodeIfPresent(.productRefGroup) { - productsGroupReference = referenceRepository.getOrCreate(reference: productRefGroupReference, objects: objects) - } else { - productsGroupReference = nil + + // MARK: - Init + + /// Initializes the project with its attributes + /// + /// - Parameters: + /// - name: xcodeproj's name. + /// - buildConfigurationList: project build configuration list. + /// - compatibilityVersion: project compatibility version. + /// - preferredProjectObjectVersion: preferred project object version + /// - minimizedProjectReferenceProxies: minimized project reference proxies + /// - mainGroup: project main group. + /// - developmentRegion: project has development region. + /// - hasScannedForEncodings: project has scanned for encodings. + /// - knownRegions: project known regions. + /// - productsGroup: products group. + /// - projectDirPath: project dir path. + /// - projects: projects. + /// - projectRoots: project roots. + /// - targets: project targets. + /// - packages: project's remote packages. + /// - attributes: project's attributes. + /// - targetAttributes: project target's attributes. + public init(name: String, + buildConfigurationList: XCConfigurationList, + compatibilityVersion: String?, + preferredProjectObjectVersion: Int?, + minimizedProjectReferenceProxies: Int?, + mainGroup: PBXGroup, + developmentRegion: String? = nil, + hasScannedForEncodings: Int = 0, + knownRegions: [String] = [], + productsGroup: PBXGroup? = nil, + projectDirPath: String = "", + projects: [[String: PBXFileElement]] = [], + projectRoots: [String] = [], + targets: [PBXTarget] = [], + packages: [XCRemoteSwiftPackageReference] = [], + attributes: [String: Any] = [:], + targetAttributes: [PBXTarget: [String: Any]] = [:]) { + self.name = name + buildConfigurationListReference = buildConfigurationList.reference + self.compatibilityVersion = compatibilityVersion + self.preferredProjectObjectVersion = preferredProjectObjectVersion + self.minimizedProjectReferenceProxies = minimizedProjectReferenceProxies + mainGroupReference = mainGroup.reference + self.developmentRegion = developmentRegion + self.hasScannedForEncodings = hasScannedForEncodings + self.knownRegions = knownRegions + productsGroupReference = productsGroup?.reference + self.projectDirPath = projectDirPath + projectReferences = projects.map { project in project.mapValues { $0.reference } } + self.projectRoots = projectRoots + targetReferences = targets.references() + packageReferences = packages.references() + self.attributes = attributes + targetAttributeReferences = [:] + super.init() + self.targetAttributes = targetAttributes } - projectDirPath = try container.decodeIfPresent(.projectDirPath) ?? "" - let projectReferences: [[String: String]] = try (container.decodeIfPresent(.projectReferences)) ?? [] - self.projectReferences = projectReferences.map { references in - references.mapValues { referenceRepository.getOrCreate(reference: $0, objects: objects) } + + // MARK: - Decodable + + fileprivate enum CodingKeys: String, CodingKey { + case name + case buildConfigurationList + case compatibilityVersion + case preferredProjectObjectVersion + case minimizedProjectReferenceProxies + case developmentRegion + case hasScannedForEncodings + case knownRegions + case mainGroup + case productRefGroup + case projectDirPath + case projectReferences + case projectRoot + case projectRoots + case targets + case attributes + case packageReferences } - if let projectRoots: [String] = try container.decodeIfPresent(.projectRoots) { - self.projectRoots = projectRoots - } else if let projectRoot: String = try container.decodeIfPresent(.projectRoot) { - projectRoots = [projectRoot] - } else { - projectRoots = [] + + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let referenceRepository = decoder.context.objectReferenceRepository + let objects = decoder.context.objects + name = try (container.decodeIfPresent(.name)) ?? "" + let buildConfigurationListReference: String = try container.decode(.buildConfigurationList) + self.buildConfigurationListReference = referenceRepository.getOrCreate(reference: buildConfigurationListReference, objects: objects) + compatibilityVersion = try container.decodeIfPresent(.compatibilityVersion) + preferredProjectObjectVersion = if let stringValue = try container.decodeIfPresent(String.self, forKey: .preferredProjectObjectVersion) { + Int(stringValue) + } else if let intValue = try container.decodeIfPresent(Int.self, forKey: .preferredProjectObjectVersion) { + intValue + } else { + nil + } + minimizedProjectReferenceProxies = if let stringValue = try container.decodeIfPresent(String.self, forKey: .minimizedProjectReferenceProxies) { + Int(stringValue) + } else if let intValue = try container.decodeIfPresent(Int.self, forKey: .minimizedProjectReferenceProxies) { + intValue + } else { + nil + } + developmentRegion = try container.decodeIfPresent(.developmentRegion) + let hasScannedForEncodingsString: String? = try container.decodeIfPresent(.hasScannedForEncodings) + hasScannedForEncodings = hasScannedForEncodingsString.flatMap { Int($0) } ?? 0 + knownRegions = try (container.decodeIfPresent(.knownRegions)) ?? [] + let mainGroupReference: String = try container.decode(.mainGroup) + self.mainGroupReference = referenceRepository.getOrCreate(reference: mainGroupReference, objects: objects) + if let productRefGroupReference: String = try container.decodeIfPresent(.productRefGroup) { + productsGroupReference = referenceRepository.getOrCreate(reference: productRefGroupReference, objects: objects) + } else { + productsGroupReference = nil + } + projectDirPath = try container.decodeIfPresent(.projectDirPath) ?? "" + let projectReferences: [[String: String]] = try (container.decodeIfPresent(.projectReferences)) ?? [] + self.projectReferences = projectReferences.map { references in + references.mapValues { referenceRepository.getOrCreate(reference: $0, objects: objects) } + } + if let projectRoots: [String] = try container.decodeIfPresent(.projectRoots) { + self.projectRoots = projectRoots + } else if let projectRoot: String = try container.decodeIfPresent(.projectRoot) { + projectRoots = [projectRoot] + } else { + projectRoots = [] + } + let targetReferences: [String] = try (container.decodeIfPresent(.targets)) ?? [] + self.targetReferences = targetReferences.map { referenceRepository.getOrCreate(reference: $0, objects: objects) } + + let packageRefeferenceStrings: [String] = try container.decodeIfPresent(.packageReferences) ?? [] + packageReferences = packageRefeferenceStrings.map { referenceRepository.getOrCreate(reference: $0, objects: objects) } + + var attributes = try (container.decodeIfPresent([String: Any].self, forKey: .attributes) ?? [:]) + var targetAttributeReferences: [PBXObjectReference: [String: Any]] = [:] + if let targetAttributes = attributes[PBXProject.targetAttributesKey] as? [String: [String: Any]] { + targetAttributes.forEach { targetAttributeReferences[referenceRepository.getOrCreate(reference: $0.key, objects: objects)] = $0.value } + attributes[PBXProject.targetAttributesKey] = nil + } + self.attributes = attributes + self.targetAttributeReferences = targetAttributeReferences + + try super.init(from: decoder) } - let targetReferences: [String] = try (container.decodeIfPresent(.targets)) ?? [] - self.targetReferences = targetReferences.map { referenceRepository.getOrCreate(reference: $0, objects: objects) } - - let packageRefeferenceStrings: [String] = try container.decodeIfPresent(.packageReferences) ?? [] - packageReferences = packageRefeferenceStrings.map { referenceRepository.getOrCreate(reference: $0, objects: objects) } - - var attributes = try (container.decodeIfPresent([String: Any].self, forKey: .attributes) ?? [:]) - var targetAttributeReferences: [PBXObjectReference: [String: Any]] = [:] - if let targetAttributes = attributes[PBXProject.targetAttributesKey] as? [String: [String: Any]] { - targetAttributes.forEach { targetAttributeReferences[referenceRepository.getOrCreate(reference: $0.key, objects: objects)] = $0.value } - attributes[PBXProject.targetAttributesKey] = nil + + override func isEqual(to object: Any?) -> Bool { + guard let rhs = object as? PBXProject else { return false } + return isEqual(to: rhs) } - self.attributes = attributes - self.targetAttributeReferences = targetAttributeReferences - - try super.init(from: decoder) - } - - override func isEqual(to object: Any?) -> Bool { - guard let rhs = object as? PBXProject else { return false } - return isEqual(to: rhs) - } } // MARK: - Helpers extension PBXProject { - /// Adds reference for remote Swift package - private func addSwiftPackageReference(repositoryURL: String, - productName: String, - versionRequirement: XCRemoteSwiftPackageReference.VersionRequirement) throws -> XCRemoteSwiftPackageReference { - let reference: XCRemoteSwiftPackageReference - if let package = remotePackages.first(where: { $0.repositoryURL == repositoryURL }) { - guard package.versionRequirement == versionRequirement else { - throw PBXProjError.multipleRemotePackages(productName: productName) - } - reference = package - } else { - reference = XCRemoteSwiftPackageReference(repositoryURL: repositoryURL, versionRequirement: versionRequirement) - try objects().add(object: reference) - remotePackages.append(reference) + /// Adds reference for remote Swift package + private func addSwiftPackageReference(repositoryURL: String, + productName: String, + versionRequirement: XCRemoteSwiftPackageReference.VersionRequirement) throws -> XCRemoteSwiftPackageReference { + let reference: XCRemoteSwiftPackageReference + if let package = remotePackages.first(where: { $0.repositoryURL == repositoryURL }) { + guard package.versionRequirement == versionRequirement else { + throw PBXProjError.multipleRemotePackages(productName: productName) + } + reference = package + } else { + reference = XCRemoteSwiftPackageReference(repositoryURL: repositoryURL, versionRequirement: versionRequirement) + try objects().add(object: reference) + remotePackages.append(reference) + } + + return reference } - - return reference - } - - /// Adds package product for remote Swift package - private func addSwiftPackageProduct(reference: XCRemoteSwiftPackageReference, - productName: String, - target: PBXTarget) throws -> XCSwiftPackageProductDependency { - let objects = try objects() - - let productDependency: XCSwiftPackageProductDependency - // Avoid duplication - if let product = objects.swiftPackageProductDependencies.first(where: { $0.value.package == reference && $0.value.productName == productName })?.value { - productDependency = product - } else { - productDependency = XCSwiftPackageProductDependency(productName: productName, package: reference) - objects.add(object: productDependency) + + /// Adds package product for remote Swift package + private func addSwiftPackageProduct(reference: XCRemoteSwiftPackageReference, + productName: String, + target: PBXTarget) throws -> XCSwiftPackageProductDependency { + let objects = try objects() + + let productDependency: XCSwiftPackageProductDependency + // Avoid duplication + if let product = objects.swiftPackageProductDependencies.first(where: { $0.value.package == reference && $0.value.productName == productName })?.value { + productDependency = product + } else { + productDependency = XCSwiftPackageProductDependency(productName: productName, package: reference) + objects.add(object: productDependency) + } + target.packageProductDependencies?.append(productDependency) + + return productDependency } - target.packageProductDependencies?.append(productDependency) - - return productDependency - } - - /// Adds package product for local Swift package - private func addLocalSwiftPackageProduct(path: Path, - productName: String, - target: PBXTarget) throws -> XCSwiftPackageProductDependency { - let objects = try objects() - - let productDependency: XCSwiftPackageProductDependency - // Avoid duplication - if let product = objects.swiftPackageProductDependencies.first(where: { $0.value.productName == productName }) { - guard objects.fileReferences.first(where: { $0.value.name == productName })?.value.path == path.string else { - throw PBXProjError.multipleLocalPackages(productName: productName) - } - productDependency = product.value - } else { - productDependency = XCSwiftPackageProductDependency(productName: productName) - objects.add(object: productDependency) + + /// Adds package product for local Swift package + private func addLocalSwiftPackageProduct(path: Path, + productName: String, + target: PBXTarget) throws -> XCSwiftPackageProductDependency { + let objects = try objects() + + let productDependency: XCSwiftPackageProductDependency + // Avoid duplication + if let product = objects.swiftPackageProductDependencies.first(where: { $0.value.productName == productName }) { + guard objects.fileReferences.first(where: { $0.value.name == productName })?.value.path == path.string else { + throw PBXProjError.multipleLocalPackages(productName: productName) + } + productDependency = product.value + } else { + productDependency = XCSwiftPackageProductDependency(productName: productName) + objects.add(object: productDependency) + } + target.packageProductDependencies?.append(productDependency) + + return productDependency } - target.packageProductDependencies?.append(productDependency) - - return productDependency - } } // MARK: - PlistSerializable extension PBXProject: PlistSerializable { - // swiftlint:disable:next function_body_length - func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { - var dictionary: [CommentedString: PlistValue] = [:] - dictionary["isa"] = .string(CommentedString(PBXProject.isa)) - let buildConfigurationListComment = "Build configuration list for PBXProject \"\(name)\"" - let buildConfigurationListCommentedString = CommentedString(buildConfigurationListReference.value, - comment: buildConfigurationListComment) - dictionary["buildConfigurationList"] = .string(buildConfigurationListCommentedString) - if let compatibilityVersion { - dictionary["compatibilityVersion"] = .string(CommentedString(compatibilityVersion)) - } - if let developmentRegion { - dictionary["developmentRegion"] = .string(CommentedString(developmentRegion)) - } - dictionary["hasScannedForEncodings"] = .string(CommentedString("\(hasScannedForEncodings)")) - - if !knownRegions.isEmpty { - dictionary["knownRegions"] = PlistValue.array(knownRegions - .map { .string(CommentedString("\($0)")) }) - } - let mainGroupObject: PBXGroup? = mainGroupReference.getObject() - dictionary["mainGroup"] = .string(CommentedString(mainGroupReference.value, comment: mainGroupObject?.fileName())) - if let preferredProjectObjectVersion { - dictionary["preferredProjectObjectVersion"] = .string(CommentedString(preferredProjectObjectVersion.description)) - } - if let minimizedProjectReferenceProxies { - dictionary["minimizedProjectReferenceProxies"] = .string(CommentedString(minimizedProjectReferenceProxies.description)) - } - if let productsGroupReference { - let productRefGroupObject: PBXGroup? = productsGroupReference.getObject() - dictionary["productRefGroup"] = .string(CommentedString(productsGroupReference.value, - comment: productRefGroupObject?.fileName())) - } - dictionary["projectDirPath"] = .string(CommentedString(projectDirPath)) - if projectRoots.count > 1 { - dictionary["projectRoots"] = projectRoots.plist() - } else { - dictionary["projectRoot"] = .string(CommentedString(projectRoots.first ?? "")) - } - if let projectReferences = try projectReferencesPlistValue(proj: proj) { - dictionary["projectReferences"] = projectReferences - } - dictionary["targets"] = PlistValue.array(targetReferences - .map { targetReference in - let target: PBXTarget? = targetReference.getObject() - return .string(CommentedString(targetReference.value, comment: target?.name)) - }) - - if !remotePackages.isEmpty || !localPackages.isEmpty { - let remotePackageReferences = remotePackages.map { - PlistValue.string(CommentedString($0.reference.value, comment: "XCRemoteSwiftPackageReference \"\($0.name ?? "")\"")) - } - let localPackageReferences = localPackages.map { - PlistValue.string(CommentedString($0.reference.value, comment: "XCLocalSwiftPackageReference \"\($0.name ?? "")\"")) - } - var finalPackageReferences = remotePackageReferences - finalPackageReferences.append(contentsOf: localPackageReferences) - dictionary["packageReferences"] = PlistValue.array(finalPackageReferences) - } - - var plistAttributes: [String: Any] = attributes - - // merge target attributes - var plistTargetAttributes: [String: Any] = [:] - for (reference, value) in targetAttributeReferences { - plistTargetAttributes[reference.value] = value.mapValues { value in - (value as? PBXObject)?.reference.value ?? value - } + // swiftlint:disable:next function_body_length + func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) { + var dictionary: [CommentedString: PlistValue] = [:] + dictionary["isa"] = .string(CommentedString(PBXProject.isa)) + let buildConfigurationListComment = "Build configuration list for PBXProject \"\(name)\"" + let buildConfigurationListCommentedString = CommentedString(buildConfigurationListReference.value, + comment: buildConfigurationListComment) + dictionary["buildConfigurationList"] = .string(buildConfigurationListCommentedString) + if let compatibilityVersion { + dictionary["compatibilityVersion"] = .string(CommentedString(compatibilityVersion)) + } + if let developmentRegion { + dictionary["developmentRegion"] = .string(CommentedString(developmentRegion)) + } + dictionary["hasScannedForEncodings"] = .string(CommentedString("\(hasScannedForEncodings)")) + + if !knownRegions.isEmpty { + dictionary["knownRegions"] = PlistValue.array(knownRegions + .map { .string(CommentedString("\($0)")) }) + } + let mainGroupObject: PBXGroup? = mainGroupReference.getObject() + dictionary["mainGroup"] = .string(CommentedString(mainGroupReference.value, comment: mainGroupObject?.fileName())) + if let preferredProjectObjectVersion { + dictionary["preferredProjectObjectVersion"] = .string(CommentedString(preferredProjectObjectVersion.description)) + } + if let minimizedProjectReferenceProxies { + dictionary["minimizedProjectReferenceProxies"] = .string(CommentedString(minimizedProjectReferenceProxies.description)) + } + if let productsGroupReference { + let productRefGroupObject: PBXGroup? = productsGroupReference.getObject() + dictionary["productRefGroup"] = .string(CommentedString(productsGroupReference.value, + comment: productRefGroupObject?.fileName())) + } + dictionary["projectDirPath"] = .string(CommentedString(projectDirPath)) + if projectRoots.count > 1 { + dictionary["projectRoots"] = projectRoots.plist() + } else { + dictionary["projectRoot"] = .string(CommentedString(projectRoots.first ?? "")) + } + if let projectReferences = try projectReferencesPlistValue(proj: proj) { + dictionary["projectReferences"] = projectReferences + } + dictionary["targets"] = PlistValue.array(targetReferences + .map { targetReference in + let target: PBXTarget? = targetReference.getObject() + return .string(CommentedString(targetReference.value, comment: target?.name)) + }) + + if !remotePackages.isEmpty || !localPackages.isEmpty { + let remotePackageReferences = remotePackages.map { + PlistValue.string(CommentedString($0.reference.value, comment: "XCRemoteSwiftPackageReference \"\($0.name ?? "")\"")) + } + let localPackageReferences = localPackages.map { + PlistValue.string(CommentedString($0.reference.value, comment: "XCLocalSwiftPackageReference \"\($0.name ?? "")\"")) + } + var finalPackageReferences = remotePackageReferences + finalPackageReferences.append(contentsOf: localPackageReferences) + dictionary["packageReferences"] = PlistValue.array(finalPackageReferences) + } + + var plistAttributes: [String: Any] = attributes + + // merge target attributes + var plistTargetAttributes: [String: Any] = [:] + for (reference, value) in targetAttributeReferences { + plistTargetAttributes[reference.value] = value.mapValues { value in + (value as? PBXObject)?.reference.value ?? value + } + } + plistAttributes[PBXProject.targetAttributesKey] = plistTargetAttributes + + dictionary["attributes"] = plistAttributes.plist() + + return (key: CommentedString(reference, + comment: "Project object"), + value: .dictionary(dictionary)) } - plistAttributes[PBXProject.targetAttributesKey] = plistTargetAttributes - - dictionary["attributes"] = plistAttributes.plist() - - return (key: CommentedString(reference, - comment: "Project object"), - value: .dictionary(dictionary)) - } - - private func projectReferencesPlistValue(proj _: PBXProj) throws -> PlistValue? { - guard !projectReferences.isEmpty else { - return nil + + private func projectReferencesPlistValue(proj _: PBXProj) throws -> PlistValue? { + guard !projectReferences.isEmpty else { + return nil + } + return .array(projectReferences.compactMap { reference in + guard let productGroupReference = reference[Xcode.ProjectReference.productGroupKey], + let projectRef = reference[Xcode.ProjectReference.projectReferenceKey] + else { + return nil + } + let producGroup: PBXGroup? = productGroupReference.getObject() + let groupName = producGroup?.fileName() + let project: PBXFileElement? = projectRef.getObject() + let fileRefName = project?.fileName() + + return [ + CommentedString(Xcode.ProjectReference.productGroupKey): PlistValue.string(CommentedString(productGroupReference.value, comment: groupName)), + CommentedString(Xcode.ProjectReference.projectReferenceKey): PlistValue.string(CommentedString(projectRef.value, comment: fileRefName)), + ] + }) } - return .array(projectReferences.compactMap { reference in - guard let productGroupReference = reference[Xcode.ProjectReference.productGroupKey], - let projectRef = reference[Xcode.ProjectReference.projectReferenceKey] - else { - return nil - } - let producGroup: PBXGroup? = productGroupReference.getObject() - let groupName = producGroup?.fileName() - let project: PBXFileElement? = projectRef.getObject() - let fileRefName = project?.fileName() - - return [ - CommentedString(Xcode.ProjectReference.productGroupKey): PlistValue.string(CommentedString(productGroupReference.value, comment: groupName)), - CommentedString(Xcode.ProjectReference.projectReferenceKey): PlistValue.string(CommentedString(projectRef.value, comment: fileRefName)), - ] - }) - } } diff --git a/Tests/XcodeProjTests/Objects/Files/PBXContainerItemProxyTests.swift b/Tests/XcodeProjTests/Objects/Files/PBXContainerItemProxyTests.swift index 6fb69914d..e5f660a78 100644 --- a/Tests/XcodeProjTests/Objects/Files/PBXContainerItemProxyTests.swift +++ b/Tests/XcodeProjTests/Objects/Files/PBXContainerItemProxyTests.swift @@ -20,7 +20,7 @@ final class PBXContainerItemProxyTests: XCTestCase { func test_maintains_remoteID() { let target = PBXNativeTarget(name: "") - let project = PBXProject(name: "", buildConfigurationList: XCConfigurationList(), compatibilityVersion: "", preferredProjectObjectVersion: nil, minimizedProjectReferenceProxies: nil, mainGroup: PBXGroup()) + let project = PBXProject(name: "", buildConfigurationList: XCConfigurationList(), compatibilityVersion: "", preferredProjectObjectVersion: nil, minimizedProjectReferenceProxies: nil, mainGroup: PBXGroup()) let containerProxy = PBXContainerItemProxy(containerPortal: .project(project), remoteGlobalID: .object(target)) XCTAssertEqual(target.uuid, containerProxy.remoteGlobalID?.uuid) diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift index 9ff5e3cbc..482bf8cce 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift @@ -294,7 +294,18 @@ class PBXProjEncoderTests: XCTestCase { let lines = lines(fromFile: encodeProject(settings: settings)) let beginGroup = lines.findLine("/* Begin PBXFileSystemSynchronizedRootGroup section */") - var line = lines.validate(line: "6CF05B9D2C53F64800EF267F /* SynchronizedRootGroups */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (6CF05BA32C53F97F00EF267F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SynchronizedRootGroups; sourceTree = \"\"; };", after: beginGroup) + var line = lines.validate(line: "6CF05B9D2C53F64800EF267F /* SynchronizedRootGroups */ = {", after: beginGroup) + line = lines.validate(line: "isa = PBXFileSystemSynchronizedRootGroup;", after: line) + line = lines.validate(line: "exceptions = (", after: line) + line = lines.validate(line: "6CF05BA32C53F97F00EF267F /* PBXFileSystemSynchronizedBuildFileExceptionSet */,", after: line) + line = lines.validate(line: ");", after: line) + line = lines.validate(line: "explicitFileTypes = {", after: line) + line = lines.validate(line: "};", after: line) + line = lines.validate(line: "explicitFolders = (", after: line) + line = lines.validate(line: ");", after: line) + line = lines.validate(line: "path = SynchronizedRootGroups;", after: line) + line = lines.validate(line: "sourceTree = \"\";", after: line) + line = lines.validate(line: "};", after: line) line = lines.validate(line: "/* End PBXFileSystemSynchronizedRootGroup section */", after: line) } diff --git a/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift b/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift index 89dfb9c47..374515c8b 100644 --- a/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift +++ b/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift @@ -4,12 +4,11 @@ import XCTest @testable import XcodeProj final class XcodeProjIntegrationTests: XCTestCase { - func test_write_xcode16Project() throws { - try testReadWriteProducesNoDiff(from: xcode16ProjectPath, - initModel: XcodeProj.init(path:)) + try testReadWriteProducesNoDiff(from: xcode16ProjectPath, + initModel: XcodeProj.init(path:)) } - + func test_read_iosXcodeProj() throws { let subject = try XcodeProj(path: iosProjectPath) assert(project: subject) @@ -101,10 +100,10 @@ final class XcodeProjIntegrationTests: XCTestCase { private var iosProjectPath: Path { fixturesPath() + "iOS/Project.xcodeproj" } - - private var xcode16ProjectPath: Path { - fixturesPath() + "Xcode16/Test.xcodeproj" - } + + private var xcode16ProjectPath: Path { + fixturesPath() + "Xcode16/Test.xcodeproj" + } private var synchronizedRootGroupsFixturePath: Path { fixturesPath() + "SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj" From 31bdab9768fa80c658e98280356fab28a6feb75f Mon Sep 17 00:00:00 2001 From: Pedro Date: Tue, 8 Oct 2024 18:31:23 +0200 Subject: [PATCH 3/3] Fix release notes that all include an [unreleased] version at the top --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a4f0bf4c8..0fd2db638 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: inputs: version: - description: 'The version to release' + description: "The version to release" type: string permissions: @@ -19,7 +19,7 @@ permissions: jobs: release: name: Release - runs-on: 'ubuntu-latest' + runs-on: "ubuntu-latest" timeout-minutes: 15 if: "!startsWith(github.event.head_commit.message, '[Release]')" steps: @@ -62,7 +62,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | echo "RELEASE_NOTES<> "$GITHUB_OUTPUT" - git cliff 8.22.0.. --unreleased >> "$GITHUB_OUTPUT" + git cliff 8.22.0.. --bump --unreleased >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" - name: Update CHANGELOG.md if: env.should-release == 'true' @@ -74,7 +74,7 @@ jobs: uses: stefanzweifel/git-auto-commit-action@v5 if: env.should-release == 'true' with: - commit_options: '--allow-empty' + commit_options: "--allow-empty" tagging_message: ${{ steps.next-version.outputs.NEXT_VERSION }} skip_dirty_check: true commit_message: "[Release] XcodeProj ${{ steps.next-version.outputs.NEXT_VERSION }}"