diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..5e4f446b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: CI +on: [push, pull_request] +jobs: + swift_5: + runs-on: macos-latest + name: Swift 5.0 + steps: + - uses: actions/checkout@master + - name: Info + run: swift --version; swift package --version + - name: Resolve + run: swift package resolve + - name: Build + run: swift build + - name: Test + run: swift test + - name: Gen fixtures + run: scripts/gen-fixtures.sh + - name: Check fixtures + run: scripts/diff-fixtures.sh + - name: Build fixtures + run: scripts/build-fixtures.sh + - name: Build release + run: make build + # swift_5.1: + # runs-on: macos-latest + # name: Swift 5.1 + # steps: + # - uses: actions/checkout@master + # - name: Xcode version + # run: sudo xcode-select -s /Applications/Xcode_11_beta.app + # - name: Info + # run: swift --version; swift package --version + # - name: Resolve + # run: swift package resolve + # - name: Build + # run: swift build + # - name: Test + # run: swift test + # - name: Gen fixtures + # run: scripts/gen-fixtures.sh + # - name: Check fixtures + # run: scripts/diff-fixtures.sh + # - name: Build fixtures + # run: scripts/build-fixtures.sh + # - name: Build release + # run: make build + diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ede40f5..393a4151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,15 @@ ## Next Version #### Added - - Added ability to disable main thread checker on Run and Test schemes and TargetSchemes [#601](https://github.com/yonaskolb/XcodeGen/pull/601) @wag-miles +#### Fixed +- Fixed included specs that were referenced multiple times from duplicating content [#599](https://github.com/yonaskolb/XcodeGen/pull/599) @haritowa +- Fixed `.orig` files being added to the project [#627](https://github.com/yonaskolb/XcodeGen/pull/627) @keith + +#### Changed +- Allow linking of dependencies into static libraries when `link` is set to true [#635](https://github.com/yonaskolb/XcodeGen/pull/635) @kateinoigakukun + ## 2.6.0 #### Added diff --git a/Docs/FAQ.md b/Docs/FAQ.md index 80f19ddf..eff84e8f 100644 --- a/Docs/FAQ.md +++ b/Docs/FAQ.md @@ -1,6 +1,7 @@ # Frequently asked questions - [Can I still check in my project](#can-i-still-check-in-my-project) - [Can I use CocoaPods](#can-i-use-cocoapods) +- [Can I use Crashlytics](#can-i-use-crashlytics) - [How do I setup code signing](#how-do-i-setup-code-signing) ## Can I still check in my project @@ -15,6 +16,22 @@ For now you can always add xcodegen as a git `post-checkout` hook. ## Can I use CocoaPods Yes, simply generate your project and then run `pod install` which will integrate with your project and create a workspace. +## Can I use Crashlytics +Yes, but you need to use a little trick when using CocoaPods. Add this script in your `Podfile`: + +```ruby:Podfile +// Your dependencies +pod 'Fabric' +pod 'Crashlytics' + +script_phase :name => 'Run Fabric', + :script => '"${PODS_ROOT}/Fabric/run"' + :input_files => ['$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)'] + +``` + +This script will be added after `[CP] Embed Pods Frameworks.` + ## How do I setup code signing At the moment there are no special options for code signing in XcodeGen, and this must be configured via regular build settings. For code signing to work, you need to tell Xcode which development team to use. This requires setting the `DEVELOPMENT_TEAM` and possibly `CODE_SIGN_STYLE` build settings. See [Configuring build settings](Usage.md#configuring-build-settings) for how to do that diff --git a/README.md b/README.md index 139087f7..b57e8910 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ mint install yonaskolb/xcodegen ```shell git clone https://github.com/yonaskolb/XcodeGen.git cd XcodeGen -make +make install ``` ### Homebrew diff --git a/Sources/ProjectSpec/SpecFile.swift b/Sources/ProjectSpec/SpecFile.swift index 2cfa55cf..98a39542 100644 --- a/Sources/ProjectSpec/SpecFile.swift +++ b/Sources/ProjectSpec/SpecFile.swift @@ -8,6 +8,8 @@ public struct SpecFile { public let jsonDictionary: JSONDictionary public let subSpecs: [SpecFile] + private let filename: String + fileprivate struct Include { let path: Path let relativePaths: Bool @@ -42,11 +44,12 @@ public struct SpecFile { try self.init(filename: path.lastComponent, basePath: path.parent()) } - public init(jsonDictionary: JSONDictionary, basePath: Path = "", relativePath: Path = "", subSpecs: [SpecFile] = []) { + public init(filename: String, jsonDictionary: JSONDictionary, basePath: Path = "", relativePath: Path = "", subSpecs: [SpecFile] = []) { self.basePath = basePath self.relativePath = relativePath self.jsonDictionary = jsonDictionary self.subSpecs = subSpecs + self.filename = filename } fileprivate init(include: Include, basePath: Path, relativePath: Path) throws { @@ -65,7 +68,7 @@ public struct SpecFile { try SpecFile(include: include, basePath: basePath, relativePath: relativePath) } - self.init(jsonDictionary: jsonDictionary, basePath: basePath, relativePath: relativePath, subSpecs: subSpecs) + self.init(filename: filename, jsonDictionary: jsonDictionary, basePath: basePath, relativePath: relativePath, subSpecs: subSpecs) } static func loadDictionary(path: Path) throws -> JSONDictionary { @@ -83,17 +86,36 @@ public struct SpecFile { } public func resolvedDictionary(variables: [String: String] = [:]) -> JSONDictionary { - var resolvedSpec = resolvingPaths().mergedDictionary() + let resolvedDictionary = resolvedDictionaryWithUniqueTargets() + return substitute(variables: variables, in: resolvedDictionary) + } + + private func resolvedDictionaryWithUniqueTargets() -> JSONDictionary { + let resolvedSpec = resolvingPaths() + + var value = Set() + return resolvedSpec.mergedDictionary(set: &value) + } + + private func substitute(variables: [String: String], in mergedDictionary: JSONDictionary) -> JSONDictionary { + var resolvedSpec = mergedDictionary + for (key, value) in variables { resolvedSpec = resolvedSpec.replaceString("${\(key)}", with: value) } + return resolvedSpec } - - func mergedDictionary() -> JSONDictionary { + + func mergedDictionary(set mergedTargets: inout Set) -> JSONDictionary { + let name = (basePath + relativePath + Path(filename)).description + + guard !mergedTargets.contains(name) else { return [:] } + mergedTargets.insert(name) + return jsonDictionary.merged(onto: subSpecs - .map { $0.mergedDictionary() } + .map { $0.mergedDictionary(set: &mergedTargets) } .reduce([:]) { $1.merged(onto: $0) }) } @@ -105,6 +127,7 @@ public struct SpecFile { let jsonDictionary = Project.pathProperties.resolvingPaths(in: self.jsonDictionary, relativeTo: relativePath) return SpecFile( + filename: filename, jsonDictionary: jsonDictionary, relativePath: self.relativePath, subSpecs: subSpecs.map { $0.resolvingPaths(relativeTo: relativePath) } diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index 4af316cb..6b580e08 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -504,9 +504,6 @@ public class PBXProjGenerator { let buildPath = Path(dependency.reference).parent().string.quoted frameworkBuildPaths.insert(buildPath) - // Static libraries can't link or embed dynamic frameworks - guard target.type != .staticLibrary else { break } - let fileReference: PBXFileElement if dependency.implicit { fileReference = sourceGenerator.getFileReference( @@ -521,7 +518,7 @@ public class PBXProjGenerator { ) } - if dependency.link ?? true { + if dependency.link ?? (target.type != .staticLibrary) { let buildFile = addObject( PBXBuildFile(file: fileReference, settings: getDependencyFrameworkSettings(dependency: dependency)) ) @@ -540,8 +537,6 @@ public class PBXProjGenerator { copyFrameworksReferences.append(embedFile) } case .sdk(let root): - // Static libraries can't link or embed dynamic frameworks - guard target.type != .staticLibrary else { break } var dependencyPath = Path(dependency.reference) if !dependency.reference.contains("/") { @@ -589,8 +584,6 @@ public class PBXProjGenerator { let allDependencies = findFrameworks ? carthageResolver.relatedDependencies(for: dependency, in: target.platform) : [dependency] allDependencies.forEach { dependency in - // Static libraries can't link or embed dynamic frameworks - guard target.type != .staticLibrary else { return } var platformPath = Path(carthageResolver.buildPath(for: target.platform)) var frameworkPath = platformPath + dependency.reference @@ -601,7 +594,7 @@ public class PBXProjGenerator { self.carthageFrameworksByPlatform[target.platform.carthageName, default: []].insert(fileReference) - if dependency.link ?? true { + if dependency.link ?? (target.type != .staticLibrary) { let buildFile = self.addObject( PBXBuildFile(file: fileReference, settings: getDependencyFrameworkSettings(dependency: dependency)) ) @@ -613,7 +606,6 @@ public class PBXProjGenerator { } for dependency in carthageDependencies { - guard target.type != .staticLibrary else { break } let embed = dependency.embed ?? target.shouldEmbedCarthageDependencies diff --git a/Sources/XcodeGenKit/SourceGenerator.swift b/Sources/XcodeGenKit/SourceGenerator.swift index 7010613c..2ab9a4a4 100644 --- a/Sources/XcodeGenKit/SourceGenerator.swift +++ b/Sources/XcodeGenKit/SourceGenerator.swift @@ -24,6 +24,9 @@ class SourceGenerator { var defaultExcludedFiles = [ ".DS_Store", ] + private let defaultExcludedExtensions = [ + "orig", + ] private(set) var knownRegions: Set = [] @@ -317,6 +320,7 @@ class SourceGenerator { /// Checks whether the path is not in any default or TargetSource excludes func isIncludedPath(_ path: Path) -> Bool { return !defaultExcludedFiles.contains(where: { path.lastComponent.contains($0) }) + && !(path.extension.map(defaultExcludedExtensions.contains) ?? false) && !targetSourceExcludePaths.contains(path) } diff --git a/Tests/Fixtures/duplicated_include/duplicated_import_root.yml b/Tests/Fixtures/duplicated_include/duplicated_import_root.yml new file mode 100644 index 00000000..cf462bd4 --- /dev/null +++ b/Tests/Fixtures/duplicated_include/duplicated_import_root.yml @@ -0,0 +1,13 @@ +name: DuplicatedImportRoot +fileGroups: + - First + - Second +targetTemplates: + IncludedTemplate: + type: application + platform: iOS + sources: + - template + preBuildScripts: + - script: swiftlint + name: Swiftlint diff --git a/Tests/Fixtures/duplicated_include/duplicated_import_sut.yml b/Tests/Fixtures/duplicated_include/duplicated_import_sut.yml new file mode 100644 index 00000000..a7253625 --- /dev/null +++ b/Tests/Fixtures/duplicated_include/duplicated_import_sut.yml @@ -0,0 +1,9 @@ +include: + - duplicated_import_transitive.yml + - duplicated_import_root.yml + - duplicated_import_root.yml +name: DuplicatedImportDependent +targets: + IncludedTarget: + templates: + - IncludedTemplate \ No newline at end of file diff --git a/Tests/Fixtures/duplicated_include/duplicated_import_transitive.yml b/Tests/Fixtures/duplicated_include/duplicated_import_transitive.yml new file mode 100644 index 00000000..6bdecd2b --- /dev/null +++ b/Tests/Fixtures/duplicated_include/duplicated_import_transitive.yml @@ -0,0 +1,3 @@ +include: + - duplicated_import_root.yml +name: DuplicatedImportTransitive diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift index 60dfda7c..3e3be70b 100644 --- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift @@ -339,6 +339,8 @@ class ProjectGeneratorTests: XCTestCase { // embed: false // iOSFrameworkZ: // dependencies: [] + // iOSFrameworkX: + // dependencies: [] // StaticLibrary: // dependencies: // - target: iOSFrameworkZ @@ -430,18 +432,33 @@ class ProjectGeneratorTests: XCTestCase { expectedLinkedFiles[iosFrameworkZ.name] = Set() expectedEmbeddedFrameworks[iosFrameworkZ.name] = Set() + let iosFrameworkX = Target( + name: "iOSFrameworkX", + type: .framework, + platform: .iOS, + dependencies: [] + ) + expectedResourceFiles[iosFrameworkX.name] = Set() + expectedLinkedFiles[iosFrameworkX.name] = Set() + expectedEmbeddedFrameworks[iosFrameworkX.name] = Set() + let staticLibrary = Target( name: "StaticLibrary", type: .staticLibrary, platform: .iOS, dependencies: [ - Dependency(type: .target, reference: iosFrameworkZ.name), - Dependency(type: .framework, reference: "FrameworkZ.framework"), + Dependency(type: .target, reference: iosFrameworkZ.name, link: true), + Dependency(type: .framework, reference: "FrameworkZ.framework", link: true), + Dependency(type: .target, reference: iosFrameworkX.name/*, link: false */), + Dependency(type: .framework, reference: "FrameworkX.framework"/*, link: false */), Dependency(type: .carthage(findFrameworks: false), reference: "CarthageZ"), ] ) expectedResourceFiles[staticLibrary.name] = Set() - expectedLinkedFiles[staticLibrary.name] = Set([]) + expectedLinkedFiles[staticLibrary.name] = Set([ + iosFrameworkZ.filename, + "FrameworkZ.framework", + ]) expectedEmbeddedFrameworks[staticLibrary.name] = Set() let resourceBundle = Target( @@ -471,7 +488,9 @@ class ProjectGeneratorTests: XCTestCase { expectedLinkedFiles[iosFrameworkA.name] = Set([ "FrameworkC.framework", iosFrameworkZ.filename, + iosFrameworkX.filename, "FrameworkZ.framework", + "FrameworkX.framework", "CarthageZ.framework", "CarthageA.framework", "CarthageB.framework", @@ -496,7 +515,9 @@ class ProjectGeneratorTests: XCTestCase { expectedLinkedFiles[iosFrameworkB.name] = Set([ iosFrameworkA.filename, iosFrameworkZ.filename, + iosFrameworkX.filename, "FrameworkZ.framework", + "FrameworkX.framework", "CarthageZ.framework", "FrameworkC.framework", "FrameworkD.framework", @@ -528,7 +549,9 @@ class ProjectGeneratorTests: XCTestCase { iosFrameworkA.filename, staticLibrary.filename, iosFrameworkZ.filename, + iosFrameworkX.filename, "FrameworkZ.framework", + "FrameworkX.framework", "CarthageZ.framework", "FrameworkC.framework", iosFrameworkB.filename, @@ -539,7 +562,9 @@ class ProjectGeneratorTests: XCTestCase { expectedEmbeddedFrameworks[appTest.name] = Set([ iosFrameworkA.filename, iosFrameworkZ.filename, + iosFrameworkX.filename, "FrameworkZ.framework", + "FrameworkX.framework", "FrameworkC.framework", iosFrameworkB.filename, "FrameworkD.framework", @@ -572,7 +597,7 @@ class ProjectGeneratorTests: XCTestCase { "NotificationCenter.framework", ]) - let targets = [app, iosFrameworkZ, staticLibrary, resourceBundle, iosFrameworkA, iosFrameworkB, appTest, appTestWithoutTransitive, stickerPack] + let targets = [app, iosFrameworkZ, iosFrameworkX, staticLibrary, resourceBundle, iosFrameworkA, iosFrameworkB, appTest, appTestWithoutTransitive, stickerPack] let project = Project( name: "test", diff --git a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift index 6112c1fe..bac0e043 100644 --- a/Tests/XcodeGenKitTests/SourceGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SourceGeneratorTests.swift @@ -312,6 +312,24 @@ class SourceGeneratorTests: XCTestCase { try test(generateEmptyDirectories: true) } + $0.it("excludes certain ignored files") { + let directories = """ + Sources: + A: + - a.swift + - .DS_Store + - a.swift.orig + """ + try createDirectories(directories) + + let target = Target(name: "Test", type: .application, platform: .iOS, sources: [TargetSource(path: "Sources")]) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + let pbxProj = try project.generatePbxProj() + try pbxProj.expectFile(paths: ["Sources", "A", "a.swift"]) + try pbxProj.expectFileMissing(paths: ["Sources", "A", ".DS_Store"]) + try pbxProj.expectFileMissing(paths: ["Sources", "A", "a.swift.orig"]) + } + $0.it("generates file sources") { let directories = """ Sources: diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift index 781a8f98..9a745e97 100644 --- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift +++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift @@ -9,6 +9,21 @@ import Yams class SpecLoadingTests: XCTestCase { + func testSpecLoaderDuplicateImports() { + describe { + $0.it("merges each file only once") { + let path = fixturePath + "duplicated_include/duplicated_import_sut.yml" + let project = try loadSpec(path: path) + + try expect(project.fileGroups) == ["First", "Second"] + + let sutTarget = project.targets.first + try expect(sutTarget?.sources) == [TargetSource(path: "template")] + try expect(sutTarget?.preBuildScripts) == [BuildScript(script: .script("swiftlint"), name: "Swiftlint")] + } + } + } + func testSpecLoader() { describe { $0.it("merges includes") {