From e9193cfb299823bd2a89eb29907e42e2e049114e Mon Sep 17 00:00:00 2001 From: yonaskolb Date: Fri, 27 Sep 2019 21:36:07 +1000 Subject: [PATCH] add support for localPackages --- Docs/ProjectSpec.md | 3 +- Docs/Usage.md | 9 +++ Sources/ProjectSpec/Project.swift | 7 ++ Sources/ProjectSpec/SpecValidation.swift | 6 ++ Sources/ProjectSpec/SpecValidationError.swift | 3 + Sources/XcodeGenKit/PBXProjGenerator.swift | 6 ++ Sources/XcodeGenKit/SourceGenerator.swift | 12 +++ .../SPM/SPM.xcodeproj/project.pbxproj | 2 + .../xcshareddata/swiftpm/Package.resolved | 81 +++++++++++++++++++ Tests/Fixtures/SPM/project.yml | 2 + Tests/XcodeGenKitTests/ProjectSpecTests.swift | 3 + Tests/XcodeGenKitTests/SpecLoadingTests.swift | 7 +- 12 files changed, 138 insertions(+), 3 deletions(-) diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md index 0774e992..d6e722ca 100644 --- a/Docs/ProjectSpec.md +++ b/Docs/ProjectSpec.md @@ -47,7 +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)]** - +- [ ] **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 diff --git a/Docs/Usage.md b/Docs/Usage.md index bbfa63a0..31d4c646 100644 --- a/Docs/Usage.md +++ b/Docs/Usage.md @@ -178,6 +178,15 @@ If you want to check in the `Package.resolved` file so that everyone is on the s > 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 +``` +> 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/Sources/ProjectSpec/Project.swift b/Sources/ProjectSpec/Project.swift index 95858840..3e2f1e0d 100644 --- a/Sources/ProjectSpec/Project.swift +++ b/Sources/ProjectSpec/Project.swift @@ -20,6 +20,7 @@ public struct Project: BuildSettingsContainer { } public var packages: [String: SwiftPackage] + public var localPackages: [String] public var settings: Settings public var settingGroups: [String: Settings] @@ -44,6 +45,7 @@ public struct Project: BuildSettingsContainer { settingGroups: [String: Settings] = [:], schemes: [Scheme] = [], packages: [String: SwiftPackage] = [:], + localPackages: [String] = [], options: SpecOptions = SpecOptions(), fileGroups: [String] = [], configFiles: [String: String] = [:], @@ -60,6 +62,7 @@ public struct Project: BuildSettingsContainer { self.settingGroups = settingGroups self.schemes = schemes self.packages = packages + self.localPackages = localPackages self.options = options self.fileGroups = fileGroups self.configFiles = configFiles @@ -131,6 +134,7 @@ extension Project: Equatable { lhs.configFiles == rhs.configFiles && lhs.options == rhs.options && lhs.packages == rhs.packages && + lhs.localPackages == rhs.localPackages && NSDictionary(dictionary: lhs.attributes).isEqual(to: rhs.attributes) } } @@ -170,6 +174,7 @@ extension Project { } else { packages = [:] } + localPackages = jsonDictionary.json(atKeyPath: "localPackages") ?? [] if jsonDictionary["options"] != nil { options = try jsonDictionary.json(atKeyPath: "options") } else { @@ -197,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), @@ -261,6 +267,7 @@ extension Project: JSONEncodable { 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) diff --git a/Sources/ProjectSpec/SpecValidation.swift b/Sources/ProjectSpec/SpecValidation.swift index 5967e79b..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)) diff --git a/Sources/ProjectSpec/SpecValidationError.swift b/Sources/ProjectSpec/SpecValidationError.swift index d1cdf0c9..98e78d2d 100644 --- a/Sources/ProjectSpec/SpecValidationError.swift +++ b/Sources/ProjectSpec/SpecValidationError.swift @@ -19,6 +19,7 @@ public struct SpecValidationError: Error, CustomStringConvertible { 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) @@ -64,6 +65,8 @@ public struct SpecValidationError: Error, CustomStringConvertible { 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/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index dcb7059e..bb21d786 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -48,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() + sourceGenerator.createLocalPackage(path: path) + } + let buildConfigs: [XCBuildConfiguration] = project.configs.map { config in let buildSettings = project.getProjectBuildSettings(config: config) var baseConfiguration: PBXFileReference? diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index c09c1666..89c531ad 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -41,6 +41,18 @@ class SourceGenerator { return object } + func createLocalPackage(path: Path) { + let fileReference = addObject( + PBXFileReference( + sourceTree: .absolute, + name: path.lastComponent, + lastKnownFileType: "folder", + path: path.string + ) + ) + rootGroups.insert(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 index e7dc8cd1..2394fee4 100644 --- a/Tests/Fixtures/SPM/SPM.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/SPM/SPM.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 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 = ""; }; + EA6A9C2D515205854C4FC36F /* XcodeGen */ = {isa = PBXFileReference; lastKnownFileType = folder; name = XcodeGen; path = /Users/Yonas/Developer/XcodeGen; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -67,6 +68,7 @@ 218F6C96DF9E182F526258CF = { isa = PBXGroup; children = ( + EA6A9C2D515205854C4FC36F /* XcodeGen */, 17DD374CC81D710476AFF41C /* SPM */, 1FA59BFD192FB5A68D5F587C /* StaticLibrary */, 5D68FDDE55EE935627A1B376 /* Products */, 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 index 25f62935..1bb3b087 100644 --- a/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Tests/Fixtures/SPM/SPM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { "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", @@ -9,6 +18,78 @@ "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" + } } ] }, diff --git a/Tests/Fixtures/SPM/project.yml b/Tests/Fixtures/SPM/project.yml index 8b184f40..3d175e84 100644 --- a/Tests/Fixtures/SPM/project.yml +++ b/Tests/Fixtures/SPM/project.yml @@ -3,6 +3,8 @@ packages: Codability: url: https://github.com/yonaskolb/Codability majorVersion: 0.2.1 +localPackages: + - ../../.. #XcodeGen itself targets: App: type: application diff --git a/Tests/XcodeGenKitTests/ProjectSpecTests.swift b/Tests/XcodeGenKitTests/ProjectSpecTests.swift index 8860f8e1..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")) } @@ -467,6 +469,7 @@ class ProjectSpecTests: XCTestCase { 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", diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift index 2663a20d..556d9dd7 100644 --- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift +++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift @@ -969,7 +969,8 @@ class SpecLoadingTests: XCTestCase { "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")), - ]) + ], + localPackages: ["../../Package"]) let dictionary: [String: Any] = [ "name": "spm", @@ -980,7 +981,9 @@ class SpecLoadingTests: XCTestCase { "package4": ["url": "package.git", "branch": "master"], "package5": ["url": "package.git", "revision": "x"], "package6": ["url": "package.git", "minVersion": "1.2.0", "maxVersion": "1.2.5"], - ]] + ], + "localPackages": ["../../Package"] + ] let parsedSpec = try getProjectSpec(dictionary) try expect(parsedSpec) == project }