diff --git a/CHANGELOG.md b/CHANGELOG.md index ccbe589aa..48ef673de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Next Version +#### Fixed +- Fixed issue which caused watch app schemes to be generated incorrectly, preventing these apps from launching. [#798](https://github.com/yonaskolb/XcodeGen/pull/798) @daltonclaybrook + ## 2.15.0 #### Added diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift index afd240be8..4df57ff39 100644 --- a/Sources/XcodeGenKit/SchemeGenerator.swift +++ b/Sources/XcodeGenKit/SchemeGenerator.swift @@ -57,14 +57,15 @@ public class SchemeGenerator { let debugConfig = suitableConfig(for: .debug, in: project) let releaseConfig = suitableConfig(for: .release, in: project) - let scheme = Scheme.init( + let scheme = Scheme( name: schemeName, target: target, targetScheme: targetScheme, + project: project, debugConfig: debugConfig.name, releaseConfig: releaseConfig.name ) - let xcscheme = try generateScheme(scheme) + let xcscheme = try generateScheme(scheme, for: target) xcschemes.append(xcscheme) } else { for configVariant in targetScheme.configVariants { @@ -80,10 +81,11 @@ public class SchemeGenerator { name: schemeName, target: target, targetScheme: targetScheme, + project: project, debugConfig: debugConfig.name, releaseConfig: releaseConfig.name ) - let xcscheme = try generateScheme(scheme) + let xcscheme = try generateScheme(scheme, for: target) xcschemes.append(xcscheme) } } @@ -93,7 +95,7 @@ public class SchemeGenerator { return xcschemes } - public func generateScheme(_ scheme: Scheme) throws -> XCScheme { + public func generateScheme(_ scheme: Scheme, for target: Target? = nil) throws -> XCScheme { func getBuildableReference(_ target: TargetReference) throws -> XCScheme.BuildableReference { let pbxProj: PBXProj @@ -159,11 +161,11 @@ public class SchemeGenerator { return XCScheme.ExecutionAction(scriptText: action.script, title: action.name, environmentBuildable: environmentBuildable) } - let target = project.getTarget(scheme.build.targets.first!.target.name) - let shouldExecuteOnLaunch = target?.type.isExecutable == true + let schemeTarget = target ?? project.getTarget(scheme.build.targets.first!.target.name) + let shouldExecuteOnLaunch = schemeTarget?.type.isExecutable == true let buildableReference = buildActionEntries.first!.buildableReference - let productRunable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference) + let runnables = makeProductRunnables(for: schemeTarget, buildableReference: buildableReference) let buildAction = XCScheme.BuildAction( buildActionEntries: buildActionEntries, @@ -225,7 +227,7 @@ public class SchemeGenerator { locationScenarioReference = XCScheme.LocationScenarioReference(identifier: identifier, referenceType: referenceType.rawValue) } let launchAction = XCScheme.LaunchAction( - runnable: shouldExecuteOnLaunch ? productRunable : nil, + runnable: shouldExecuteOnLaunch ? runnables.launch : nil, buildConfiguration: scheme.run?.config ?? defaultDebugConfig.name, preActions: scheme.run?.preActions.map(getExecutionAction) ?? [], postActions: scheme.run?.postActions.map(getExecutionAction) ?? [], @@ -243,7 +245,7 @@ public class SchemeGenerator { ) let profileAction = XCScheme.ProfileAction( - buildableProductRunnable: productRunable, + buildableProductRunnable: runnables.profile, buildConfiguration: scheme.profile?.config ?? defaultReleaseConfig.name, preActions: scheme.profile?.preActions.map(getExecutionAction) ?? [], postActions: scheme.profile?.postActions.map(getExecutionAction) ?? [], @@ -274,6 +276,20 @@ public class SchemeGenerator { archiveAction: archiveAction ) } + + private func makeProductRunnables(for target: Target?, buildableReference: XCScheme.BuildableReference) -> (launch: XCScheme.Runnable, profile: XCScheme.BuildableProductRunnable) { + let buildable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference) + if target?.type.isWatchApp == true { + let remote = XCScheme.RemoteRunnable( + buildableReference: buildableReference, + bundleIdentifier: "com.apple.Carousel", + runnableDebuggingMode: "2" + ) + return (remote, buildable) + } else { + return (buildable, buildable) + } + } } enum SchemeGenerationError: Error, CustomStringConvertible { @@ -292,11 +308,11 @@ enum SchemeGenerationError: Error, CustomStringConvertible { } extension Scheme { - public init(name: String, target: Target, targetScheme: TargetScheme, debugConfig: String, releaseConfig: String) { + public init(name: String, target: Target, targetScheme: TargetScheme, project: Project, debugConfig: String, releaseConfig: String) { self.init( name: name, build: .init( - targets: [Scheme.BuildTarget(target: TargetReference.local(target.name))], + targets: Scheme.buildTargets(for: target, project: project), buildImplicitDependencies: targetScheme.buildImplicitDependencies ), run: .init( @@ -339,4 +355,30 @@ extension Scheme { ) ) } + + private static func buildTargets(for target: Target, project: Project) -> [BuildTarget] { + let buildTarget = Scheme.BuildTarget(target: TargetReference.local(target.name)) + switch target.type { + case .watchApp, .watch2App: + let hostTarget = project.targets + .first { projectTarget in + projectTarget.dependencies.contains { $0.reference == target.name } + } + .map { BuildTarget(target: TargetReference.local($0.name)) } + return hostTarget.map { [buildTarget, $0] } ?? [buildTarget] + default: + return [buildTarget] + } + } +} + +extension PBXProductType { + var isWatchApp: Bool { + switch self { + case .watchApp, .watch2App: + return true + default: + return false + } + } } diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/App_watchOS.xcscheme b/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/App_watchOS.xcscheme index 5daa2ee99..c4928b9f8 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/App_watchOS.xcscheme +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/App_watchOS.xcscheme @@ -20,6 +20,20 @@ ReferencedContainer = "container:Project.xcodeproj"> + + + + - + - + diff --git a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift index b3459ff3c..81ddb5e9a 100644 --- a/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/SchemeGeneratorTests.swift @@ -336,6 +336,61 @@ class SchemeGeneratorTests: XCTestCase { try expect(buildableReference?.blueprintName) == "ExternalTarget" try expect(buildableReference?.referencedContainer) == "container:\(externalProject.string)" } + + $0.it("generates scheme with buildable product runnable for ios app target") { + let app = Target( + name: "MyApp", + type: .application, + platform: .iOS, + scheme: TargetScheme() + ) + let project = Project(name: "ios_test", targets: [app]) + let xcodeProject = try project.generateXcodeProject() + let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first) + try expect(xcscheme.launchAction?.runnable).beOfType(XCScheme.BuildableProductRunnable.self) + } + + $0.it("generates scheme with remote runnable for watch app target") { + let xcscheme = try self.makeWatchScheme(appType: .watch2App, extensionType: .watch2Extension) + try expect(xcscheme.launchAction?.runnable).beOfType(XCScheme.RemoteRunnable.self) + } + + $0.it("generates scheme with host target build action for watch") { + let xcscheme = try self.makeWatchScheme(appType: .watch2App, extensionType: .watch2Extension) + let buildEntries = xcscheme.buildAction?.buildActionEntries ?? [] + try expect(buildEntries.count) == 2 + try expect(buildEntries.first?.buildableReference.blueprintName) == "WatchApp" + try expect(buildEntries.last?.buildableReference.blueprintName) == "HostApp" + } } } + + // MARK: - Helpers + + private func makeWatchScheme(appType: PBXProductType, extensionType: PBXProductType) throws -> XCScheme { + let watchExtension = Target( + name: "WatchExtension", + type: extensionType, + platform: .watchOS + ) + let watchApp = Target( + name: "WatchApp", + type: appType, + platform: .watchOS, + dependencies: [Dependency(type: .target, reference: watchExtension.name)], + scheme: TargetScheme() + ) + let hostApp = Target( + name: "HostApp", + type: .application, + platform: .iOS, + dependencies: [Dependency(type: .target, reference: watchApp.name)] + ) + let project = Project( + name: "watch_test", + targets: [hostApp, watchApp, watchExtension] + ) + let xcodeProject = try project.generateXcodeProject() + return try unwrap(xcodeProject.sharedData?.schemes.first) + } }