diff --git a/CHANGELOG.md b/CHANGELOG.md index 872eadf5..cc11e794 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,11 @@ #### Added +- Added support for Swift Package dependencies [#624](https://github.com/yonaskolb/XcodeGen/pull/624) @yonaskolb - Added `includes` to `sources` for a Target. This follows the same glob-style as `excludes` but functions as a way to only include files that match a specified pattern. Useful if you only want a certain file type, for example specifying `**/*.swift`. [#637](https://github.com/yonaskolb/XcodeGen/pull/637) @bclymer -- Support `dylib` SDK. [#650](https://github.com/yonaskolb/XcodeGen/pull/650) -- Added `language` and `region` options for `run` and `test` scheme [#654](https://github.com/yonaskolb/XcodeGen/pull/654) -- Added `debugEnabled` option for `run` and `test` scheme [#657](https://github.com/yonaskolb/XcodeGen/pull/657) +- Support `dylib` SDK. [#650](https://github.com/yonaskolb/XcodeGen/pull/650) @kateinoigakukun +- Added `language` and `region` options for `run` and `test` scheme [#654](https://github.com/yonaskolb/XcodeGen/pull/654) @kateinoigakukun +- Added `debugEnabled` option for `run` and `test` scheme [#657](https://github.com/yonaskolb/XcodeGen/pull/657) @kateinoigakukun #### Fixed @@ -16,6 +17,7 @@ #### Internal - Removed needless `Array` initialization. [#661](https://github.com/yonaskolb/XcodeGen/pull/661) @RomanPodymov +- Updated to XcodeProj 7.1.0 [#624](https://github.com/yonaskolb/XcodeGen/pull/624) @yonaskolb ## 2.7.0 diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index d9749612..0321f222 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -23,6 +23,7 @@ - [Aggregate Target](#aggregate-target) - [Target Template](#target-template) - [Scheme](#scheme) +- [Swift Package](#swift-package) ## General @@ -46,6 +47,8 @@ You can also use environment variables in your configuration file, by using `${S - [ ] **fileGroups**: **[String]** - A list of paths to add to the root of the project. These aren't files that will be included in your targets, but that you'd like to include in the project hierachy anyway. For example a folder of xcconfig files that aren't already added by any target sources, or a Readme file. - [ ] **schemes**: **[Scheme](#scheme)** - A list of schemes by name. This allows more control over what is found in [Target Scheme](#target-scheme) - [ ] **targetTemplates**: **[String: [Target Template](#target-template)]** - a list of targets that can be used as templates for actual targets which reference them via a `template` property. They can be used to extract common target settings. Works great in combination with `include`. +- [ ] **packages**: **[String: [Swift Package](#swift-package)]** - a map of Swift packages by name +- [ ] **localPackages**: **[String]** - A list of paths to local Swift Packages. The paths must be directories with a `Package.swift` file in them. This is used to override `packages` with a local version for development purposes. ### Include @@ -117,6 +120,7 @@ Note that target names can also be changed by adding a `name` property to a targ - [ ] **transitivelyLinkDependencies**: **Bool** - If this is `true` then targets will link to the dependencies of their target dependencies. If a target should embed its dependencies, such as application and test bundles, it will embed these transitive dependencies as well. Some complex setups might want to set this to `false` and explicitly specify dependencies at every level. Targets can override this with [Target](#target).transitivelyLinkDependencies. Defaults to `false`. - [ ] **generateEmptyDirectories**: **Bool** - If this is `true` then empty directories will be added to project too else will be missed. Defaults to `false`. - [ ] **findCarthageFrameworks**: **Bool** - When this is set to `true`, all the invididual frameworks for Carthage dependencies will automatically be found. This property can be overriden individually for each carthage dependency - for more details see See **findFrameworks** in the [Dependency](#dependency) section. Defaults to `false`. +- [ ] **localPackagesGroup**: **String** - The group name that local packages are put into. This defaults to `Packages` ```yaml options: @@ -373,6 +377,7 @@ A dependency can be one of a 3 types: - `framework: path` - links to a framework - `carthage: name` - helper for linking to a Carthage framework - `sdk: name` - links to a dependency with the SDK. This can either be a relative path within the sdk root or a single filename that references a framework (.framework) or lib (.tbd) +- `package: name` - links to a Swift Package. The name must match the name of a package defined in the top level `packages` **Linking options**: @@ -396,7 +401,7 @@ Carthage frameworks are expected to be in `CARTHAGE_BUILD_PATH/PLATFORM/FRAMEWOR - `PLATFORM` = the target's platform - `FRAMEWORK` = the specified name. -All the invididual frameworks of a Carthage dependency can be automatically found via `findFrameworks: true`. This overrides the value of [Options](#options).findCarthageFrameworks. Otherwise each one will have to be listed invididually. +All the individual frameworks of a Carthage dependency can be automatically found via `findFrameworks: true`. This overrides the value of [Options](#options).findCarthageFrameworks. Otherwise each one will have to be listed individually. Xcodegen uses `.version` files generated by Carthage in order for this framework lookup to work, so the Carthage dependencies will need to have already been built at the time XcodeGen is run. If any applications contain carthage dependencies within itself or any dependent targets, a carthage copy files script is automatically added to the application containing all the relevant frameworks. A `FRAMEWORK_SEARCH_PATHS` setting is also automatically added @@ -433,6 +438,25 @@ targets: type: framework ``` +**Package dependency** +- [ ] **product**: **String** - The product to use from the package. This defaults to the package name, so is only required if a Package has multiple libraries or a library with a differing name + +```yaml +packages: + Yams: + url: https://github.com/jpsim/Yams + majorVersion: 2.0.0 + SwiftPM: + url: https://github.com/apple/swift-package-manager + branch: swift-5.0-branch +targets: + App: + dependencies: + - package: Yams + - package: SwiftPM + product: SPMUtility +``` + ### Config Files Specifies `.xcconfig` files for each configuration. @@ -756,3 +780,28 @@ schemes: customArchiveName: MyTarget revealArchiveInOrganizer: false ``` + +## Swift Package +Swift packages are defined at a project level, and then linked to individual targets via a [Dependency](#dependency). + +> Note that Swift Packages don't work in projects with configurations other than `Debug` and `Release`. That limitation is tracked here bugs.swift.org/browse/SR-10927 + +- [x] **url**: **URL** - the url to the package +- [x] **version**: **String** - the version of the package to use. It can take a few forms: + - `majorVersion: 1.2.0` or `from: 1.2.0` + - `minorVersion: 1.2.1` + - `exactVersion: 1.2.1` or `version: 1.2.1` + - `minVersion: 1.0.0, maxVersion: 1.2.9` + - `branch: master` + - `revision: xxxxxx` + +```yml +packages: + Yams: + url: https://github.com/jpsim/Yams + from: 2.0.0 +targets: + App: + dependencies: + - package: Yams +``` \ No newline at end of file diff --git a/Docs/Usage.md b/Docs/Usage.md index ce810a0d..546c12aa 100644 --- a/Docs/Usage.md +++ b/Docs/Usage.md @@ -4,9 +4,10 @@ - [Settings](#settings) - [Setting Groups](#setting-groups) - [xcconfig files](#xcconfig-files) -- [Use dependencies](#use-dependencies) +- [Dependencies](#dependencies) - [CocoaPods](#cocoapods) - [Carthage](#carthage) + - [Swift Package](#swift-package) - [SDK](#sdk) - [Framework](#framework) @@ -92,7 +93,7 @@ You can also always overide any build settings on CI when building by passing sp DEVELOPMENT_TEAM=XXXXXXXXX xcodebuild ... ``` -# Use dependencies +# Dependencies Each target can declare one or more dependencies. See [Dependency](ProjectSpec.md#dependency) in the ProjectSpec for more info about all the properties @@ -153,6 +154,41 @@ options: carthageBuildPath: ../../Carthage/Build ``` +### Swift Package +Swift Packages can be integrated by defining them at the project level and then referencing them in targets + +```yaml +packages: + Yams: + url: https://github.com/jpsim/Yams + from: 2.0.0 + SwiftPM: + url: https://github.com/apple/swift-package-manager + branch: swift-5.0-branch +targets: + App: + dependencies: + # by default the package product that is linked to is the same as the package name + - package: Yams + - package: SwiftPM + - package: SwiftPM + product: SPMUtility # specify a specific product +``` +If you want to check in the `Package.resolved` file so that everyone is on the same versions, you need to check in `ProjectName.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved` + +> Note that Swift Packages don't work in projects with configurations other than `Debug` and `Release`. That limitation is tracked here bugs.swift.org/browse/SR-10927 + +You can also include local Swift Packages by referencing them by paths in `localPackages`. When these have the same name as `packages` they will be used instead of the remote repos. This is useful for local development. + +```yml +localPackages: + - ../../Yams + - ~/Developer/MyPackage +``` +These local packages get put into a `Packages` group in the root of the project by default. This can be changed with `options.localPackagesGroup` + +> For now local packages that don't mirror remote packages aren't able to be linked to + ### SDK System frameworks and libs can be linked by using the `sdk` dependency type. You can either specify frameworks or libs by using a `.framework`, `.tbd` or `dylib` filename, respectively diff --git a/Package.resolved b/Package.resolved index e30bf923..fa39b355 100644 --- a/Package.resolved +++ b/Package.resolved @@ -69,8 +69,8 @@ "repositoryURL": "https://github.com/tuist/xcodeproj.git", "state": { "branch": null, - "revision": "b951777f42e9acbfb8f19da623b43aaa604422f9", - "version": "7.0.0" + "revision": "0f563e2d7d604499e7b57a28c78ff23d5c545acd", + "version": "7.1.0" } }, { diff --git a/Package.swift b/Package.swift index 4d621eeb..ebbba726 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,7 @@ let package = Package( .package(url: "https://github.com/yonaskolb/JSONUtilities.git", from: "4.2.0"), .package(url: "https://github.com/kylef/Spectre.git", from: "0.9.0"), .package(url: "https://github.com/onevcat/Rainbow.git", from: "3.0.0"), - .package(url: "https://github.com/tuist/xcodeproj.git", .exact("7.0.0")), + .package(url: "https://github.com/tuist/xcodeproj.git", .exact("7.1.0")), .package(url: "https://github.com/jakeheis/SwiftCLI.git", .exact("5.2.2")), ], targets: [ diff --git a/Sources/ProjectSpec/Dependency.swift b/Sources/ProjectSpec/Dependency.swift index 022bcbcd..a31c0bd3 100644 --- a/Sources/ProjectSpec/Dependency.swift +++ b/Sources/ProjectSpec/Dependency.swift @@ -38,6 +38,7 @@ public struct Dependency: Equatable { case framework case carthage(findFrameworks: Bool?) case sdk(root: String?) + case package(product: String?) } } @@ -64,6 +65,10 @@ extension Dependency: JSONObjectConvertible { let sdkRoot: String? = jsonDictionary.json(atKeyPath: "root") type = .sdk(root: sdkRoot) reference = sdk + } else if let package: String = jsonDictionary.json(atKeyPath: "package") { + let product: String? = jsonDictionary.json(atKeyPath: "product") + type = .package(product: product) + reference = package } else { throw SpecParsingError.invalidDependency(jsonDictionary) } @@ -114,6 +119,8 @@ extension Dependency: JSONEncodable { } case .sdk: dict["sdk"] = reference + case .package: + dict["package"] = reference } return dict diff --git a/Sources/ProjectSpec/Project.swift b/Sources/ProjectSpec/Project.swift index adfdf029..3e2f1e0d 100644 --- a/Sources/ProjectSpec/Project.swift +++ b/Sources/ProjectSpec/Project.swift @@ -19,6 +19,9 @@ public struct Project: BuildSettingsContainer { } } + public var packages: [String: SwiftPackage] + public var localPackages: [String] + public var settings: Settings public var settingGroups: [String: Settings] public var configs: [Config] @@ -41,6 +44,8 @@ public struct Project: BuildSettingsContainer { settings: Settings = .empty, settingGroups: [String: Settings] = [:], schemes: [Scheme] = [], + packages: [String: SwiftPackage] = [:], + localPackages: [String] = [], options: SpecOptions = SpecOptions(), fileGroups: [String] = [], configFiles: [String: String] = [:], @@ -56,6 +61,8 @@ public struct Project: BuildSettingsContainer { self.settings = settings self.settingGroups = settingGroups self.schemes = schemes + self.packages = packages + self.localPackages = localPackages self.options = options self.fileGroups = fileGroups self.configFiles = configFiles @@ -126,6 +133,8 @@ extension Project: Equatable { lhs.fileGroups == rhs.fileGroups && lhs.configFiles == rhs.configFiles && lhs.options == rhs.options && + lhs.packages == rhs.packages && + lhs.localPackages == rhs.localPackages && NSDictionary(dictionary: lhs.attributes).isEqual(to: rhs.attributes) } } @@ -160,6 +169,12 @@ extension Project { configFiles = jsonDictionary.json(atKeyPath: "configFiles") ?? [:] attributes = jsonDictionary.json(atKeyPath: "attributes") ?? [:] include = jsonDictionary.json(atKeyPath: "include") ?? [] + if jsonDictionary["packages"] != nil { + packages = try jsonDictionary.json(atKeyPath: "packages", invalidItemBehaviour: .fail) + } else { + packages = [:] + } + localPackages = jsonDictionary.json(atKeyPath: "localPackages") ?? [] if jsonDictionary["options"] != nil { options = try jsonDictionary.json(atKeyPath: "options") } else { @@ -187,6 +202,7 @@ extension Project: PathContainer { static var pathProperties: [PathProperty] { return [ .string("configFiles"), + .string("localPackages"), .object("options", SpecOptions.pathProperties), .object("targets", Target.pathProperties), .object("targetTemplates", Target.pathProperties), @@ -242,19 +258,22 @@ extension Project: JSONEncodable { let aggregateTargetsPairs = aggregateTargets.map { ($0.name, $0.toJSONValue()) } let schemesPairs = schemes.map { ($0.name, $0.toJSONValue()) } - return [ - "name": name, - "options": options.toJSONValue(), - "settings": settings.toJSONValue(), - "fileGroups": fileGroups, - "configFiles": configFiles, - "include": include, - "attributes": attributes, - "targets": Dictionary(uniqueKeysWithValues: targetPairs), - "configs": Dictionary(uniqueKeysWithValues: configsPairs), - "aggregateTargets": Dictionary(uniqueKeysWithValues: aggregateTargetsPairs), - "schemes": Dictionary(uniqueKeysWithValues: schemesPairs), - "settingGroups": settingGroups.mapValues { $0.toJSONValue() }, - ] + var dictionary: JSONDictionary = [:] + dictionary["name"] = name + dictionary["options"] = options.toJSONValue() + dictionary["settings"] = settings.toJSONValue() + dictionary["fileGroups"] = fileGroups + dictionary["configFiles"] = configFiles + dictionary["include"] = include + dictionary["attributes"] = attributes + dictionary["packages"] = packages.mapValues { $0.toJSONValue() } + dictionary["localPackages"] = localPackages + dictionary["targets"] = Dictionary(uniqueKeysWithValues: targetPairs) + dictionary["configs"] = Dictionary(uniqueKeysWithValues: configsPairs) + dictionary["aggregateTargets"] = Dictionary(uniqueKeysWithValues: aggregateTargetsPairs) + dictionary["schemes"] = Dictionary(uniqueKeysWithValues: schemesPairs) + dictionary["settingGroups"] = settingGroups.mapValues { $0.toJSONValue() } + + return dictionary } } diff --git a/Sources/ProjectSpec/SpecOptions.swift b/Sources/ProjectSpec/SpecOptions.swift index fc55641e..1b2081b9 100644 --- a/Sources/ProjectSpec/SpecOptions.swift +++ b/Sources/ProjectSpec/SpecOptions.swift @@ -27,6 +27,7 @@ public struct SpecOptions: Equatable { public var groupSortPosition: GroupSortPosition public var generateEmptyDirectories: Bool public var findCarthageFrameworks: Bool + public var localPackagesGroup: String? public enum ValidationType: String { case missingConfigs @@ -82,7 +83,8 @@ public struct SpecOptions: Equatable { transitivelyLinkDependencies: Bool = transitivelyLinkDependenciesDefault, groupSortPosition: GroupSortPosition = groupSortPositionDefault, generateEmptyDirectories: Bool = generateEmptyDirectoriesDefault, - findCarthageFrameworks: Bool = findCarthageFrameworksDefault + findCarthageFrameworks: Bool = findCarthageFrameworksDefault, + localPackagesGroup: String? = nil ) { self.minimumXcodeGenVersion = minimumXcodeGenVersion self.carthageBuildPath = carthageBuildPath @@ -102,6 +104,7 @@ public struct SpecOptions: Equatable { self.groupSortPosition = groupSortPosition self.generateEmptyDirectories = generateEmptyDirectories self.findCarthageFrameworks = findCarthageFrameworks + self.localPackagesGroup = localPackagesGroup } } @@ -129,6 +132,7 @@ extension SpecOptions: JSONObjectConvertible { groupSortPosition = jsonDictionary.json(atKeyPath: "groupSortPosition") ?? SpecOptions.groupSortPositionDefault generateEmptyDirectories = jsonDictionary.json(atKeyPath: "generateEmptyDirectories") ?? SpecOptions.generateEmptyDirectoriesDefault findCarthageFrameworks = jsonDictionary.json(atKeyPath: "findCarthageFrameworks") ?? SpecOptions.findCarthageFrameworksDefault + localPackagesGroup = jsonDictionary.json(atKeyPath: "localPackagesGroup") } } @@ -149,6 +153,7 @@ extension SpecOptions: JSONEncodable { "indentWidth": indentWidth.flatMap { Int($0) }, "tabWidth": tabWidth.flatMap { Int($0) }, "defaultConfig": defaultConfig, + "localPackagesGroup": localPackagesGroup, ] if settingPresets != SpecOptions.settingPresetsDefault { diff --git a/Sources/ProjectSpec/SpecParsingError.swift b/Sources/ProjectSpec/SpecParsingError.swift index 5d48d4d0..6afa5bf2 100644 --- a/Sources/ProjectSpec/SpecParsingError.swift +++ b/Sources/ProjectSpec/SpecParsingError.swift @@ -4,6 +4,7 @@ public enum SpecParsingError: Error, CustomStringConvertible { case unknownTargetType(String) case unknownTargetPlatform(String) case invalidDependency([String: Any]) + case unknownPackageRequirement([String: Any]) case invalidSourceBuildPhase(String) case invalidVersion(String) @@ -19,6 +20,8 @@ public enum SpecParsingError: Error, CustomStringConvertible { return "Invalid Source Build Phase: \(error)" case let .invalidVersion(version): return "Invalid version: \(version)" + case let .unknownPackageRequirement(package): + return "Unknown package requirement: \(package)" } } } diff --git a/Sources/ProjectSpec/SpecValidation.swift b/Sources/ProjectSpec/SpecValidation.swift index fa7d2add..ce61b4d8 100644 --- a/Sources/ProjectSpec/SpecValidation.swift +++ b/Sources/ProjectSpec/SpecValidation.swift @@ -52,6 +52,12 @@ extension Project { } } + for package in localPackages { + if !(basePath + Path(package).normalize()).exists { + errors.append(.invalidLocalPackage(package)) + } + } + for (config, configFile) in configFiles { if !options.disabledValidations.contains(.missingConfigFiles) && !(basePath + configFile).exists { errors.append(.invalidConfigFile(configFile: configFile, config: config)) @@ -156,6 +162,10 @@ extension Project { errors.append(.invalidSDKDependency(target: target.name, dependency: dependency.reference)) } } + case .package: + if packages[dependency.reference] == nil { + errors.append(.invalidSwiftPackage(name: dependency.reference, target: target.name)) + } default: break } } diff --git a/Sources/ProjectSpec/SpecValidationError.swift b/Sources/ProjectSpec/SpecValidationError.swift index d1a7a0b0..98e78d2d 100644 --- a/Sources/ProjectSpec/SpecValidationError.swift +++ b/Sources/ProjectSpec/SpecValidationError.swift @@ -18,6 +18,8 @@ public struct SpecValidationError: Error, CustomStringConvertible { case invalidTargetSchemeTest(target: String, testTarget: String) case invalidSchemeTarget(scheme: String, target: String) case invalidSchemeConfig(scheme: String, config: String) + case invalidSwiftPackage(name: String, target: String) + case invalidLocalPackage(String) case invalidConfigFile(configFile: String, config: String) case invalidBuildSettingConfig(String) case invalidSettingsGroup(String) @@ -61,6 +63,10 @@ public struct SpecValidationError: Error, CustomStringConvertible { return "Invalid file group \(group.quoted)" case let .invalidConfigFileConfig(config): return "Config file has invalid config \(config.quoted)" + case let .invalidSwiftPackage(name, target): + return "Target \(target.quoted) has an invalid package dependency \(name.quoted)" + case let .invalidLocalPackage(path): + return "Invalid local package \(path.quoted)" case let .missingConfigForTargetScheme(target, configType): return "Target \(target.quoted) is missing a config of type \(configType.rawValue) to generate its scheme" case let .missingDefaultConfig(name): diff --git a/Sources/ProjectSpec/SwiftPackage.swift b/Sources/ProjectSpec/SwiftPackage.swift new file mode 100644 index 00000000..6f1be5c1 --- /dev/null +++ b/Sources/ProjectSpec/SwiftPackage.swift @@ -0,0 +1,77 @@ +import Foundation +import XcodeProj +import JSONUtilities + +public struct SwiftPackage: Equatable { + + public typealias VersionRequirement = XCRemoteSwiftPackageReference.VersionRequirement + + public let url: String + public let versionRequirement: VersionRequirement + + public init(url: String, versionRequirement: VersionRequirement) { + self.url = url + self.versionRequirement = versionRequirement + } +} + +extension SwiftPackage: JSONObjectConvertible { + + public init(jsonDictionary: JSONDictionary) throws { + url = try jsonDictionary.json(atKeyPath: "url") + versionRequirement = try VersionRequirement(jsonDictionary: jsonDictionary) + } +} + +extension SwiftPackage: JSONEncodable { + + public func toJSONValue() -> Any { + var dictionary: JSONDictionary = [:] + dictionary["url"] = url + + switch versionRequirement { + + case .upToNextMajorVersion(let version): + dictionary["majorVersion"] = version + case .upToNextMinorVersion(let version): + dictionary["minorVersion"] = version + case .range(let from, let to): + dictionary["minVersion"] = from + dictionary["maxVersion"] = to + case .exact(let version): + dictionary["exactVersion"] = version + case .branch(let branch): + dictionary["branch"] = branch + case .revision(let revision): + dictionary["revision"] = revision + } + return dictionary + } +} + +extension SwiftPackage.VersionRequirement: JSONObjectConvertible { + + public init(jsonDictionary: JSONDictionary) throws { + if jsonDictionary["exactVersion"] != nil { + self = try .exact(jsonDictionary.json(atKeyPath: "exactVersion")) + } else if jsonDictionary["version"] != nil { + self = try .exact(jsonDictionary.json(atKeyPath: "version")) + } else if jsonDictionary["revision"] != nil { + self = try .revision(jsonDictionary.json(atKeyPath: "revision")) + } else if jsonDictionary["branch"] != nil { + self = try .branch(jsonDictionary.json(atKeyPath: "branch")) + } else if jsonDictionary["minVersion"] != nil && jsonDictionary["maxVersion"] != nil { + let minimum: String = try jsonDictionary.json(atKeyPath: "minVersion") + let maximum: String = try jsonDictionary.json(atKeyPath: "maxVersion") + self = .range(from: minimum, to: maximum) + } else if jsonDictionary["minorVersion"] != nil { + self = try .upToNextMinorVersion(jsonDictionary.json(atKeyPath: "minorVersion")) + } else if jsonDictionary["majorVersion"] != nil { + self = try .upToNextMajorVersion(jsonDictionary.json(atKeyPath: "majorVersion")) + } else if jsonDictionary["from"] != nil { + self = try .upToNextMajorVersion(jsonDictionary.json(atKeyPath: "from")) + } else { + throw SpecParsingError.unknownPackageRequirement(jsonDictionary) + } + } +} diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index f556e3b9..c0c5231c 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -17,6 +17,7 @@ public class PBXProjGenerator { var targetAggregateObjects: [String: PBXAggregateTarget] = [:] var targetFileReferences: [String: PBXFileReference] = [:] var sdkFileReferences: [String: PBXFileReference] = [:] + var packageReferences: [String: XCRemoteSwiftPackageReference] = [:] var carthageFrameworksByPlatform: [String: Set] = [:] var frameworkFiles: [PBXFileElement] = [] @@ -30,6 +31,7 @@ public class PBXProjGenerator { sourceGenerator = SourceGenerator(project: project, pbxProj: pbxProj) } + @discardableResult func addObject(_ object: T, context: String? = nil) -> T { pbxProj.add(object: object) object.context = context @@ -46,6 +48,12 @@ public class PBXProjGenerator { try sourceGenerator.getFileGroups(path: group) } + let localPackages = Set(project.localPackages) + for package in localPackages { + let path = project.basePath + Path(package).normalize() + try sourceGenerator.createLocalPackage(path: path) + } + let buildConfigs: [XCBuildConfiguration] = project.configs.map { config in let buildSettings = project.getProjectBuildSettings(config: config) var baseConfiguration: PBXFileReference? @@ -149,6 +157,12 @@ public class PBXProjGenerator { targetAggregateObjects[target.name] = aggregateTarget } + for (name, package) in project.packages { + let packageReference = XCRemoteSwiftPackageReference(repositoryURL: package.url, versionRequirement: package.versionRequirement) + packageReferences[name] = packageReference + addObject(packageReference) + } + try project.targets.forEach(generateTarget) try project.aggregateTargets.forEach(generateAggregateTarget) @@ -208,6 +222,7 @@ public class PBXProjGenerator { let knownRegions = sourceGenerator.knownRegions.sorted() pbxProject.knownRegions = knownRegions.isEmpty ? ["en"] : knownRegions + pbxProject.packages = packageReferences.sorted { $0.key < $1.key }.map { $1 } let allTargets: [PBXTarget] = targetObjects.valueArray + targetAggregateObjects.valueArray pbxProject.targets = allTargets @@ -415,6 +430,7 @@ public class PBXProjGenerator { var copyFrameworksReferences: [PBXBuildFile] = [] var copyResourcesReferences: [PBXBuildFile] = [] var copyWatchReferences: [PBXBuildFile] = [] + var packageDependencies: [XCSwiftPackageProductDependency] = [] var extensions: [PBXBuildFile] = [] var carthageFrameworksToEmbed: [String] = [] @@ -457,10 +473,10 @@ public class PBXProjGenerator { guard let dependencyTarget = project.getTarget(dependencyTargetName) else { continue } let dependecyLinkage = dependencyTarget.defaultLinkage - let link = dependency.link ?? ( - (dependecyLinkage == .dynamic && target.type != .staticLibrary) - || (dependecyLinkage == .static && target.type.isExecutable) - ) + let link = dependency.link ?? + (dependecyLinkage == .dynamic && target.type != .staticLibrary) || + (dependecyLinkage == .static && target.type.isExecutable) + if link { let dependencyFile = targetFileReferences[dependencyTarget.name]! let buildFile = addObject( @@ -604,6 +620,29 @@ public class PBXProjGenerator { } } // Embedding handled by iterating over `carthageDependencies` below + case .package(let product): + guard let packageReference = packageReferences[dependency.reference] else { + return + } + + let productName = product ?? dependency.reference + let packageDependency = addObject( + XCSwiftPackageProductDependency(productName: productName, package: packageReference) + ) + packageDependencies.append(packageDependency) + + let link = dependency.link ?? (target.type != .staticLibrary) + if link { + let buildFile = addObject( + PBXBuildFile(product: packageDependency) + ) + targetFrameworkBuildFiles.append(buildFile) + } + + let targetDependency = addObject( + PBXTargetDependency(product: packageDependency) + ) + dependencies.append(targetDependency) } } @@ -925,6 +964,7 @@ public class PBXProjGenerator { targetObject.dependencies = dependencies targetObject.productName = target.name targetObject.buildRules = buildRules + targetObject.packageProductDependencies = packageDependencies targetObject.product = targetFileReference if !target.isLegacy { targetObject.productType = target.type @@ -969,7 +1009,7 @@ public class PBXProjGenerator { switch dependency.type { case .sdk: dependencies[dependency.reference] = dependency - case .framework, .carthage: + case .framework, .carthage, .package: if isTopLevel || dependency.embed == nil { dependencies[dependency.reference] = dependency } diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index 35e1ab3b..e00230e5 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -16,6 +16,7 @@ class SourceGenerator { private var fileReferencesByPath: [String: PBXFileElement] = [:] private var groupsByPath: [Path: PBXGroup] = [:] private var variantGroupsByPath: [Path: PBXVariantGroup] = [:] + private var localPackageGroup: PBXGroup? private let project: Project let pbxProj: PBXProj @@ -34,12 +35,32 @@ class SourceGenerator { self.pbxProj = pbxProj } + @discardableResult func addObject(_ object: T, context: String? = nil) -> T { pbxProj.add(object: object) object.context = context return object } + func createLocalPackage(path: Path) throws { + + + if localPackageGroup == nil { + let groupName = project.options.localPackagesGroup ?? "Packages" + localPackageGroup = addObject(PBXGroup(sourceTree: .sourceRoot, name: groupName)) + rootGroups.insert(localPackageGroup!) + } + let fileReference = addObject( + PBXFileReference( + sourceTree: .sourceRoot, + name: path.lastComponent, + lastKnownFileType: "folder", + path: try path.relativePath(from: project.basePath).string + ) + ) + localPackageGroup!.children.append(fileReference) + } + func getAllSourceFiles(targetType: PBXProductType, sources: [TargetSource]) throws -> [SourceFile] { return try sources.flatMap { try getSourceFiles(targetType: targetType, targetSource: $0, path: project.basePath + $0.path) } } diff --git a/Tests/Fixtures/SPM/SPM.xcodeproj/project.pbxproj b/Tests/Fixtures/SPM/SPM.xcodeproj/project.pbxproj new file mode 100644 index 00000000..dd33053a --- /dev/null +++ b/Tests/Fixtures/SPM/SPM.xcodeproj/project.pbxproj @@ -0,0 +1,457 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 2DA7998902987953B119E4CE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F7EFEE613987D1E1258A60 /* AppDelegate.swift */; }; + 578E78BC3627CF48FB2CE129 /* App.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = A9601593D0AD02931266A4E5 /* App.xctestplan */; }; + 78E2E1F9F271C0C4CDA04BD9 /* StaticLibrary.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F7AFEF8ECCC678519EA643C /* StaticLibrary.a */; }; + 9C4AD0711D706FD3ED0E436D /* StaticLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C17B77601A9D1B7895AB42 /* StaticLibrary.swift */; }; + CE46CBA5671B951B546C8673 /* Codability in Frameworks */ = {isa = PBXBuildFile; productRef = 16E6FE01D5BD99F78D4A17E2 /* Codability */; }; + E368431019ABC696E4FFC0CF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4E22B8BCC18A29EFE1DE3BE4 /* Assets.xcassets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 29147E1DDAEB1AAC20CB0CF9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F7B09D77DB7447B17DCDA3C4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3F8D94C4EFC431F646AAFB28; + remoteInfo = StaticLibrary; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 097F2DB5622B591E21BC3C73 /* App.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 26F7EFEE613987D1E1258A60 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 3F7AFEF8ECCC678519EA643C /* StaticLibrary.a */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = archive.ar; path = StaticLibrary.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 464ACF8D8F2D9F219BCFD3E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4E22B8BCC18A29EFE1DE3BE4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 61C17B77601A9D1B7895AB42 /* StaticLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLibrary.swift; sourceTree = ""; }; + A9601593D0AD02931266A4E5 /* App.xctestplan */ = {isa = PBXFileReference; path = App.xctestplan; sourceTree = ""; }; + C1DE9A872F470EAA65B9B0B0 /* XcodeGen */ = {isa = PBXFileReference; lastKnownFileType = folder; name = XcodeGen; path = ../../..; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 796E7DE873F16699BD82FFEA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CE46CBA5671B951B546C8673 /* Codability in Frameworks */, + 78E2E1F9F271C0C4CDA04BD9 /* StaticLibrary.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 17DD374CC81D710476AFF41C /* SPM */ = { + isa = PBXGroup; + children = ( + A9601593D0AD02931266A4E5 /* App.xctestplan */, + 26F7EFEE613987D1E1258A60 /* AppDelegate.swift */, + 4E22B8BCC18A29EFE1DE3BE4 /* Assets.xcassets */, + 464ACF8D8F2D9F219BCFD3E7 /* Info.plist */, + ); + path = SPM; + sourceTree = ""; + }; + 1FA59BFD192FB5A68D5F587C /* StaticLibrary */ = { + isa = PBXGroup; + children = ( + 61C17B77601A9D1B7895AB42 /* StaticLibrary.swift */, + ); + path = StaticLibrary; + sourceTree = ""; + }; + 218F6C96DF9E182F526258CF = { + isa = PBXGroup; + children = ( + AD0F3623091EEA8D1EA3DFF8 /* Packages */, + 17DD374CC81D710476AFF41C /* SPM */, + 1FA59BFD192FB5A68D5F587C /* StaticLibrary */, + 5D68FDDE55EE935627A1B376 /* Products */, + ); + sourceTree = ""; + }; + 5D68FDDE55EE935627A1B376 /* Products */ = { + isa = PBXGroup; + children = ( + 097F2DB5622B591E21BC3C73 /* App.app */, + 3F7AFEF8ECCC678519EA643C /* StaticLibrary.a */, + ); + name = Products; + sourceTree = ""; + }; + AD0F3623091EEA8D1EA3DFF8 /* Packages */ = { + isa = PBXGroup; + children = ( + C1DE9A872F470EAA65B9B0B0 /* XcodeGen */, + ); + name = Packages; + sourceTree = SOURCE_ROOT; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 3F8D94C4EFC431F646AAFB28 /* StaticLibrary */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3B861439E878E4B7EE6EE131 /* Build configuration list for PBXNativeTarget "StaticLibrary" */; + buildPhases = ( + B070E114B1D62BD5A07B61DF /* Sources */, + 723C19B61A1AD980BD7C9DF0 /* Copy Swift Objective-C Interface Header */, + ); + buildRules = ( + ); + dependencies = ( + D85FFB99444DD260A72DDDA7 /* PBXTargetDependency */, + ); + name = StaticLibrary; + packageProductDependencies = ( + AF233B61592982A7F6431FC6 /* Codability */, + ); + productName = StaticLibrary; + productReference = 3F7AFEF8ECCC678519EA643C /* StaticLibrary.a */; + productType = "com.apple.product-type.library.static"; + }; + C99E3C420D63D5219CE57E33 /* App */ = { + isa = PBXNativeTarget; + buildConfigurationList = 091BBC493EA2F0A446682C48 /* Build configuration list for PBXNativeTarget "App" */; + buildPhases = ( + 460F52476B5219D2CDA494C2 /* Sources */, + F77D37B94534F63D9B461F30 /* Resources */, + 796E7DE873F16699BD82FFEA /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + E157C6348B8AD6A28C706801 /* PBXTargetDependency */, + 078202CF7B08B66ACF7FEC23 /* PBXTargetDependency */, + ); + name = App; + packageProductDependencies = ( + 16E6FE01D5BD99F78D4A17E2 /* Codability */, + ); + productName = App; + productReference = 097F2DB5622B591E21BC3C73 /* App.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F7B09D77DB7447B17DCDA3C4 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + }; + buildConfigurationList = 425866ADA259DB93FC4AF1E3 /* Build configuration list for PBXProject "SPM" */; + compatibilityVersion = "Xcode 10.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 218F6C96DF9E182F526258CF; + packageReferences = ( + 5BA91390AE78D2EE15C60091 /* XCRemoteSwiftPackageReference "Codability" */, + ); + projectDirPath = ""; + projectRoot = ""; + targets = ( + C99E3C420D63D5219CE57E33 /* App */, + 3F8D94C4EFC431F646AAFB28 /* StaticLibrary */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + F77D37B94534F63D9B461F30 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 578E78BC3627CF48FB2CE129 /* App.xctestplan in Resources */, + E368431019ABC696E4FFC0CF /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 723C19B61A1AD980BD7C9DF0 /* Copy Swift Objective-C Interface Header */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)", + ); + name = "Copy Swift Objective-C Interface Header"; + outputPaths = ( + "$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "ditto \"${SCRIPT_INPUT_FILE_0}\" \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 460F52476B5219D2CDA494C2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2DA7998902987953B119E4CE /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B070E114B1D62BD5A07B61DF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9C4AD0711D706FD3ED0E436D /* StaticLibrary.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 078202CF7B08B66ACF7FEC23 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3F8D94C4EFC431F646AAFB28 /* StaticLibrary */; + targetProxy = 29147E1DDAEB1AAC20CB0CF9 /* PBXContainerItemProxy */; + }; + D85FFB99444DD260A72DDDA7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = AF233B61592982A7F6431FC6 /* Codability */; + }; + E157C6348B8AD6A28C706801 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = 16E6FE01D5BD99F78D4A17E2 /* Codability */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 0CCC06807E5CD8361D899B7F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1640ABF22E84A6AB9FFFB0D9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 41B31B6C4A1D9194EC6FFF6B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = SPM/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7A384B9B9CF42FCF9EF02057 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = SPM/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + B4F2839AD4756B475B2005F2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = 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_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; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + 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; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + BC33B43DF18620E6CCC43E96 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = 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_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; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 091BBC493EA2F0A446682C48 /* Build configuration list for PBXNativeTarget "App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 41B31B6C4A1D9194EC6FFF6B /* Debug */, + 7A384B9B9CF42FCF9EF02057 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ""; + }; + 3B861439E878E4B7EE6EE131 /* Build configuration list for PBXNativeTarget "StaticLibrary" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0CCC06807E5CD8361D899B7F /* Debug */, + 1640ABF22E84A6AB9FFFB0D9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ""; + }; + 425866ADA259DB93FC4AF1E3 /* Build configuration list for PBXProject "SPM" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B4F2839AD4756B475B2005F2 /* Debug */, + BC33B43DF18620E6CCC43E96 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 5BA91390AE78D2EE15C60091 /* XCRemoteSwiftPackageReference "Codability" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/yonaskolb/Codability"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.2.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 16E6FE01D5BD99F78D4A17E2 /* Codability */ = { + isa = XCSwiftPackageProductDependency; + package = 5BA91390AE78D2EE15C60091 /* XCRemoteSwiftPackageReference "Codability" */; + productName = Codability; + }; + AF233B61592982A7F6431FC6 /* Codability */ = { + isa = XCSwiftPackageProductDependency; + package = 5BA91390AE78D2EE15C60091 /* XCRemoteSwiftPackageReference "Codability" */; + productName = Codability; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = F7B09D77DB7447B17DCDA3C4 /* Project object */; +} diff --git a/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..1bb3b087 --- /dev/null +++ b/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,97 @@ +{ + "object": { + "pins": [ + { + "package": "AEXML", + "repositoryURL": "https://github.com/tadija/AEXML", + "state": { + "branch": null, + "revision": "e4d517844dd03dac557e35d77a8e9ab438de91a6", + "version": "4.4.0" + } + }, + { + "package": "Codability", + "repositoryURL": "https://github.com/yonaskolb/Codability", + "state": { + "branch": null, + "revision": "eb5bac78e0679f521c3f058c1eb9be0dd657dadd", + "version": "0.2.1" + } + }, + { + "package": "JSONUtilities", + "repositoryURL": "https://github.com/yonaskolb/JSONUtilities.git", + "state": { + "branch": null, + "revision": "128d2ffc22467f69569ef8ff971683e2393191a0", + "version": "4.2.0" + } + }, + { + "package": "PathKit", + "repositoryURL": "https://github.com/kylef/PathKit.git", + "state": { + "branch": null, + "revision": "73f8e9dca9b7a3078cb79128217dc8f2e585a511", + "version": "1.0.0" + } + }, + { + "package": "Rainbow", + "repositoryURL": "https://github.com/onevcat/Rainbow.git", + "state": { + "branch": null, + "revision": "9c52c1952e9b2305d4507cf473392ac2d7c9b155", + "version": "3.1.5" + } + }, + { + "package": "Shell", + "repositoryURL": "https://github.com/tuist/Shell", + "state": { + "branch": null, + "revision": "d38121f89401db902b0d0bfc30b987e2c84c254e", + "version": "2.0.3" + } + }, + { + "package": "Spectre", + "repositoryURL": "https://github.com/kylef/Spectre.git", + "state": { + "branch": null, + "revision": "f14ff47f45642aa5703900980b014c2e9394b6e5", + "version": "0.9.0" + } + }, + { + "package": "SwiftCLI", + "repositoryURL": "https://github.com/jakeheis/SwiftCLI.git", + "state": { + "branch": null, + "revision": "5318c37d3cacc8780f50b87a8840a6774320ebdf", + "version": "5.2.2" + } + }, + { + "package": "XcodeProj", + "repositoryURL": "https://github.com/tuist/xcodeproj.git", + "state": { + "branch": null, + "revision": "aefcbf59cea5b0831837ed2f385e6dfd80d82cc9", + "version": "7.1.0" + } + }, + { + "package": "Yams", + "repositoryURL": "https://github.com/jpsim/Yams.git", + "state": { + "branch": null, + "revision": "c947a306d2e80ecb2c0859047b35c73b8e1ca27f", + "version": "2.0.0" + } + } + ] + }, + "version": 1 +} diff --git a/Tests/Fixtures/SPM/SPM.xcodeproj/xcshareddata/xcschemes/App.xcscheme b/Tests/Fixtures/SPM/SPM.xcodeproj/xcshareddata/xcschemes/App.xcscheme new file mode 100644 index 00000000..4a4cc8cc --- /dev/null +++ b/Tests/Fixtures/SPM/SPM.xcodeproj/xcshareddata/xcschemes/App.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/Fixtures/SPM/SPM/App.xctestplan b/Tests/Fixtures/SPM/SPM/App.xctestplan new file mode 100644 index 00000000..0a76fad8 --- /dev/null +++ b/Tests/Fixtures/SPM/SPM/App.xctestplan @@ -0,0 +1,23 @@ +{ + "configurations" : [ + { + "id" : "521B6958-2D62-4961-B353-91EF8F252F4B", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : false, + "targetForVariableExpansion" : { + "containerPath" : "container:SPM.xcodeproj", + "identifier" : "C99E3C420D63D5219CE57E33", + "name" : "App" + } + }, + "testTargets" : [ + + ], + "version" : 1 +} diff --git a/Tests/Fixtures/SPM/SPM/AppDelegate.swift b/Tests/Fixtures/SPM/SPM/AppDelegate.swift new file mode 100644 index 00000000..d26e3925 --- /dev/null +++ b/Tests/Fixtures/SPM/SPM/AppDelegate.swift @@ -0,0 +1,22 @@ +// +// AppDelegate.swift +// SPM +// +// Created by Yonas Kolb on 13/8/19. +// Copyright © 2019 BeemIt. All rights reserved. +// + +import UIKit +import Codability + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + +} + diff --git a/Tests/Fixtures/SPM/SPM/Assets.xcassets/AppIcon.appiconset/Contents.json b/Tests/Fixtures/SPM/SPM/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d8db8d65 --- /dev/null +++ b/Tests/Fixtures/SPM/SPM/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tests/Fixtures/SPM/SPM/Assets.xcassets/Contents.json b/Tests/Fixtures/SPM/SPM/Assets.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Tests/Fixtures/SPM/SPM/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tests/Fixtures/SPM/SPM/Info.plist b/Tests/Fixtures/SPM/SPM/Info.plist new file mode 100644 index 00000000..2a3483c0 --- /dev/null +++ b/Tests/Fixtures/SPM/SPM/Info.plist @@ -0,0 +1,64 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Tests/Fixtures/SPM/StaticLibrary/StaticLibrary.swift b/Tests/Fixtures/SPM/StaticLibrary/StaticLibrary.swift new file mode 100644 index 00000000..be8fedcd --- /dev/null +++ b/Tests/Fixtures/SPM/StaticLibrary/StaticLibrary.swift @@ -0,0 +1,5 @@ +import Codability + +func doThing() { + _ = AnyCodable.self +} diff --git a/Tests/Fixtures/SPM/project.yml b/Tests/Fixtures/SPM/project.yml new file mode 100644 index 00000000..3d175e84 --- /dev/null +++ b/Tests/Fixtures/SPM/project.yml @@ -0,0 +1,22 @@ +name: SPM +packages: + Codability: + url: https://github.com/yonaskolb/Codability + majorVersion: 0.2.1 +localPackages: + - ../../.. #XcodeGen itself +targets: + App: + type: application + platform: iOS + sources: [SPM] + scheme: {} + dependencies: + - package: Codability + - target: StaticLibrary + StaticLibrary: + type: library.static + platform: iOS + sources: StaticLibrary + dependencies: + - package: Codability diff --git a/Tests/XcodeGenKitTests/GeneratorHelpers.swift b/Tests/XcodeGenKitTests/GeneratorHelpers.swift index f5dffefe..7fbd207f 100644 --- a/Tests/XcodeGenKitTests/GeneratorHelpers.swift +++ b/Tests/XcodeGenKitTests/GeneratorHelpers.swift @@ -8,18 +8,22 @@ import Yams extension Project { - func generateXcodeProject(file: String = #file, line: Int = #line) throws -> XcodeProj { + func generateXcodeProject(validate: Bool = true, file: String = #file, line: Int = #line) throws -> XcodeProj { return try doThrowing(file: file, line: line) { - try validate() + if validate { + try self.validate() + } let generator = ProjectGenerator(project: self) return try generator.generateXcodeProject() } } - func generatePbxProj(file: String = #file, line: Int = #line) throws -> PBXProj { + func generatePbxProj(specValidate: Bool = true, projectValidate: Bool = true, file: String = #file, line: Int = #line) throws -> PBXProj { return try doThrowing(file: file, line: line) { - let xcodeProject = try generateXcodeProject().pbxproj - try xcodeProject.validate() + let xcodeProject = try generateXcodeProject(validate: specValidate).pbxproj + if projectValidate { + try xcodeProject.validate() + } return xcodeProject } } diff --git a/Tests/XcodeGenKitTests/ProjectFixtureTests.swift b/Tests/XcodeGenKitTests/ProjectFixtureTests.swift index 585b11e4..faeb8498 100644 --- a/Tests/XcodeGenKitTests/ProjectFixtureTests.swift +++ b/Tests/XcodeGenKitTests/ProjectFixtureTests.swift @@ -9,8 +9,10 @@ class ProjectFixtureTests: XCTestCase { func testProjectFixture() { describe { - $0.it("generates") { + $0.it("generates fixtures") { try generateXcodeProject(specPath: fixturePath + "TestProject/project.yml") + try generateXcodeProject(specPath: fixturePath + "CarthageProject/project.yml") + try generateXcodeProject(specPath: fixturePath + "SPM/project.yml") } } } diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift index a8fce072..266e1b42 100644 --- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift @@ -907,6 +907,53 @@ class ProjectGeneratorTests: XCTestCase { try expect(buildFileSettings.compactMap { $0?["ATTRIBUTES"] as? [String] }.first) == ["Weak"] } + $0.it("generates swift packages") { + let app = Target( + name: "MyApp", + type: .application, + platform: .iOS, + dependencies: [ + Dependency(type: .package(product: "ProjectSpec"), reference: "XcodeGen"), + Dependency(type: .package(product: nil), reference: "Codability"), + ] + ) + + let project = Project(name: "test", targets: [app], packages: [ + "XcodeGen": SwiftPackage(url: "http://github.com/yonaskolb/XcodeGen", versionRequirement: .branch("master")), + "Codability": SwiftPackage(url: "http://github.com/yonaskolb/Codability", versionRequirement: .exact("1.0.0")), + ], localPackages: ["../XcodeGen"], options: .init(localPackagesGroup: "MyPackages")) + + let pbxProject = try project.generatePbxProj(specValidate: false) + guard let nativeTarget = pbxProject.nativeTargets.first(where: { $0.name == app.name }) else { + throw failure("PBXNativeTarget for \(app.name) not found") + } + + guard let projectSpecDependency = nativeTarget.packageProductDependencies.first(where: { $0.productName == "ProjectSpec" }) else { + throw failure("XCSwiftPackageProductDependency for \(app.name) not found") + } + + try expect(projectSpecDependency.package?.name) == "XcodeGen" + try expect(projectSpecDependency.package?.versionRequirement) == .branch("master") + + guard let codabilityDependency = nativeTarget.packageProductDependencies.first(where: { $0.productName == "Codability" }) else { + throw failure("XCSwiftPackageProductDependency for \(app.name) not found") + } + + try expect(codabilityDependency.package?.name) == "Codability" + try expect(codabilityDependency.package?.versionRequirement) == .exact("1.0.0") + + guard let localPackagesGroup = try pbxProject.getMainGroup().children.first(where: { $0.name == "MyPackages" }) as? PBXGroup else { + throw failure("Group not found") + } + + guard let localPackageFile = pbxProject.fileReferences.first(where: { $0.path == "../XcodeGen" }) else { + throw failure("FileReference not found") + } + + try expect(localPackagesGroup.children.contains(localPackageFile)) == true + try expect(localPackageFile.lastKnownFileType) == "folder" + } + $0.it("generates info.plist") { let plist = Plist(path: "Info.plist", attributes: ["UISupportedInterfaceOrientations": ["UIInterfaceOrientationPortrait", "UIInterfaceOrientationLandscapeLeft"]]) let tempPath = Path.temporary + "info" diff --git a/Tests/XcodeGenKitTests/ProjectSpecTests.swift b/Tests/XcodeGenKitTests/ProjectSpecTests.swift index 362a657e..dff5c9a2 100644 --- a/Tests/XcodeGenKitTests/ProjectSpecTests.swift +++ b/Tests/XcodeGenKitTests/ProjectSpecTests.swift @@ -96,6 +96,7 @@ class ProjectSpecTests: XCTestCase { project.settings = invalidSettings project.configFiles = ["invalidConfig": "invalidConfigFile"] project.fileGroups = ["invalidFileGroup"] + project.localPackages = ["invalidLocalPackage"] project.settingGroups = ["settingGroup1": Settings( configSettings: ["invalidSettingGroupConfig": [:]], groups: ["invalidSettingGroupSettingGroup"] @@ -106,6 +107,7 @@ class ProjectSpecTests: XCTestCase { try expectValidationError(project, .invalidConfigFile(configFile: "invalidConfigFile", config: "invalidConfig")) try expectValidationError(project, .invalidSettingsGroup("invalidSettingGroup")) try expectValidationError(project, .invalidFileGroup("invalidFileGroup")) + try expectValidationError(project, .invalidLocalPackage("invalidLocalPackage")) try expectValidationError(project, .invalidSettingsGroup("invalidSettingGroupSettingGroup")) try expectValidationError(project, .invalidBuildSettingConfig("invalidSettingGroupConfig")) } @@ -134,7 +136,10 @@ class ProjectSpecTests: XCTestCase { settings: invalidSettings, configFiles: ["invalidConfig": "invalidConfigFile"], sources: ["invalidSource"], - dependencies: [Dependency(type: .target, reference: "invalidDependency")], + dependencies: [ + Dependency(type: .target, reference: "invalidDependency"), + Dependency(type: .package(product: nil), reference: "invalidPackage") + ], preBuildScripts: [BuildScript(script: .path("invalidPreBuildScript"), name: "preBuildScript1")], postCompileScripts: [BuildScript(script: .path("invalidPostCompileScript"))], postBuildScripts: [BuildScript(script: .path("invalidPostBuildScript"))], @@ -142,6 +147,7 @@ class ProjectSpecTests: XCTestCase { )] try expectValidationError(project, .invalidTargetDependency(target: "target1", dependency: "invalidDependency")) + try expectValidationError(project, .invalidSwiftPackage(name: "invalidPackage", target: "target1")) try expectValidationError(project, .invalidTargetConfigFile(target: "target1", configFile: "invalidConfigFile", config: "invalidConfig")) try expectValidationError(project, .invalidTargetSchemeTest(target: "target1", testTarget: "invalidTarget")) try expectValidationError(project, .invalidTargetSource(target: "target1", source: "invalidSource")) @@ -458,6 +464,12 @@ class ProjectSpecTests: XCTestCase { postActions: [Scheme.ExecutionAction(name: "postAction", script: "bar", settingsTarget: "foo")]))], + packages: [ + "Yams": SwiftPackage( + url: "https://github.com/jpsim/Yams", + versionRequirement: .upToNextMajorVersion("2.0.0")) + ], + localPackages: ["../../Package"], options: SpecOptions(minimumXcodeGenVersion: Version(major: 3, minor: 4, patch: 5), carthageBuildPath: "carthageBuildPath", carthageExecutablePath: "carthageExecutablePath", @@ -496,6 +508,7 @@ class ProjectSpecTests: XCTestCase { try expect(proj.options) == restoredProj.options try expect(proj.settingGroups) == restoredProj.settingGroups try expect(proj.targets) == restoredProj.targets + try expect(proj.packages) == restoredProj.packages try expect(proj) == restoredProj } diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift index 3c9f3892..0b80fc2a 100644 --- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift +++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift @@ -960,6 +960,39 @@ class SpecLoadingTests: XCTestCase { let parsedSpec = try getProjectSpec(dictionary) try expect(parsedSpec) == expected } + + $0.it("parses packages") { + let project = Project(name: "spm", packages: [ + "package1": SwiftPackage(url: "package.git", versionRequirement: .exact("1.2.2")), + "package2": SwiftPackage(url: "package.git", versionRequirement: .upToNextMajorVersion("1.2.2")), + "package3": SwiftPackage(url: "package.git", versionRequirement: .upToNextMinorVersion("1.2.2")), + "package4": SwiftPackage(url: "package.git", versionRequirement: .branch("master")), + "package5": SwiftPackage(url: "package.git", versionRequirement: .revision("x")), + "package6": SwiftPackage(url: "package.git", versionRequirement: .range(from: "1.2.0", to: "1.2.5")), + "package7": SwiftPackage(url: "package.git", versionRequirement: .exact("1.2.2")), + ], + localPackages: ["../../Package"], + options: .init(localPackagesGroup: "MyPackages")) + + let dictionary: [String: Any] = [ + "name": "spm", + "options": [ + "localPackagesGroup": "MyPackages" + ], + "packages": [ + "package1": ["url": "package.git", "exactVersion": "1.2.2"], + "package2": ["url": "package.git", "majorVersion": "1.2.2"], + "package3": ["url": "package.git", "minorVersion": "1.2.2"], + "package4": ["url": "package.git", "branch": "master"], + "package5": ["url": "package.git", "revision": "x"], + "package6": ["url": "package.git", "minVersion": "1.2.0", "maxVersion": "1.2.5"], + "package7": ["url": "package.git", "version": "1.2.2"], + ], + "localPackages": ["../../Package"] + ] + let parsedSpec = try getProjectSpec(dictionary) + try expect(parsedSpec) == project + } } } diff --git a/scripts/gen-fixtures.sh b/scripts/gen-fixtures.sh index 1674196f..e191a416 100755 --- a/scripts/gen-fixtures.sh +++ b/scripts/gen-fixtures.sh @@ -3,3 +3,4 @@ set -e swift run xcodegen --spec Tests/Fixtures/TestProject/project.yml swift run xcodegen --spec Tests/Fixtures/CarthageProject/project.yml +swift run xcodegen --spec Tests/Fixtures/SPM/project.yml