Skip to content

Commit

Permalink
Use remote-runnable launch actions for Apple Watch schemes (#798)
Browse files Browse the repository at this point in the history
* Add support for using remote runnables with an Apple Watch scheme
  • Loading branch information
daltonclaybrook authored Mar 26, 2020
1 parent 94018e1 commit 039f6c3
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 14 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
64 changes: 53 additions & 11 deletions Sources/XcodeGenKit/SchemeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
}
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) ?? [],
Expand All @@ -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) ?? [],
Expand Down Expand Up @@ -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 {
Expand All @@ -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(
Expand Down Expand Up @@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@
ReferencedContainer = "container:Project.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0867B0DACEF28C11442DE8F7"
BuildableName = "App_iOS.app"
BlueprintName = "App_iOS"
ReferencedContainer = "container:Project.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
Expand Down Expand Up @@ -56,16 +70,17 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<RemoteRunnable
BundleIdentifier = "com.apple.Carousel"
runnableDebuggingMode = "2">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "208179651927D1138D19B5AD"
BuildableName = "App_watchOS.app"
BlueprintName = "App_watchOS"
ReferencedContainer = "container:Project.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</RemoteRunnable>
<CommandLineArguments>
</CommandLineArguments>
<AdditionalOptions>
Expand Down
55 changes: 55 additions & 0 deletions Tests/XcodeGenKitTests/SchemeGeneratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

0 comments on commit 039f6c3

Please sign in to comment.