Support generating info.plist for Watch Apps & Extensions (#756)
Part of: https://github.com/tuist/tuist/issues/628 - Added watchOS App / Extension defaults to the info plist content provider - Watch apps reference their host applications bundle identifier in the info plist `WKCompanionAppBundleIdentifier` key - Watch app extensions reference their host watch apps bundle identifier in the info plist `NSExtension.NSExtensionAttributes.WKAppBundleIdentifier` - As such the parent project is now used to perform lookups for those hosts to extract their bundle identifiers - Updated fixture to leverage generated info.plist files Test Plan: - run `tuist generate` within `fixtures/ios_app_with_watchapp2` - Verify the info.plist files generated in `Derrived/InfoPlists` matche the ones created by Xcode (They were previously checked in under `Support`)
This commit is contained in:
parent
b61d9a4ed5
commit
de14ceb7dc
|
@ -36,6 +36,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
|
|||
- Reactive interface to the System utility https://github.com/tuist/tuist/pull/770 by @pepibumur
|
||||
- Workflow to make sure that documentation and website build https://github.com/tuist/tuist/pull/783 by @pepibumur.
|
||||
- Support for `xcframework` https://github.com/tuist/tuist/pull/769 by @lakpa
|
||||
- Support generating info.plist for Watch Apps & Extensions https://github.com/tuist/tuist/pull/756 by @kwridan
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
@ -134,6 +134,16 @@ public protocol Graphing: AnyObject, Encodable {
|
|||
/// Returns all the transitive dependencies of the given target that are static libraries.
|
||||
/// - Parameter targetNode: Target node whose transitive static libraries will be returned.
|
||||
func transitiveStaticTargetNodes(for targetNode: TargetNode) -> Set<TargetNode>
|
||||
|
||||
/// Retuns the first host target node for a given target node
|
||||
///
|
||||
/// (e.g. finding host application for an extension)
|
||||
///
|
||||
/// - Parameter path: Path of the hosted target
|
||||
/// - Parameter name: Name of the hosted target
|
||||
///
|
||||
/// - Note: Search is limited to nodes with a matching path (i.e. targets within the same project)
|
||||
func hostTargetNodeFor(path: AbsolutePath, name: String) -> TargetNode?
|
||||
}
|
||||
|
||||
public class Graph: Graphing {
|
||||
|
@ -486,6 +496,15 @@ public class Graph: Graphing {
|
|||
skip: canLinkStaticProducts)
|
||||
}
|
||||
|
||||
public func hostTargetNodeFor(path: AbsolutePath, name: String) -> TargetNode? {
|
||||
guard let cachedTargetNodesForPath = cache.targetNodes[path] else {
|
||||
return nil
|
||||
}
|
||||
return cachedTargetNodesForPath.values.first {
|
||||
$0.dependencies.contains(where: { $0.path == path && $0.name == name })
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Fileprivate
|
||||
|
||||
fileprivate func productDependencyReference(for targetNode: TargetNode) -> GraphDependencyReference {
|
||||
|
|
|
@ -8,11 +8,12 @@ protocol DerivedFileGenerating {
|
|||
/// Generates the derived files that are associated to the given project.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - graph: The dependencies graph.
|
||||
/// - project: Project whose derived files will be generated.
|
||||
/// - sourceRootPath: Path to the directory in which the Xcode project will be generated.
|
||||
/// - Throws: An error if the generation of the derived files errors.
|
||||
/// - Returns: A function to be called after the project generation to delete the derived files that are not necessary anymore.
|
||||
func generate(project: Project, sourceRootPath: AbsolutePath) throws -> () throws -> Void
|
||||
func generate(graph: Graphing, project: Project, sourceRootPath: AbsolutePath) throws -> () throws -> Void
|
||||
}
|
||||
|
||||
final class DerivedFileGenerator: DerivedFileGenerating {
|
||||
|
@ -33,16 +34,17 @@ final class DerivedFileGenerator: DerivedFileGenerating {
|
|||
/// Generates the derived files that are associated to the given project.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - graph: The dependencies graph.
|
||||
/// - project: Project whose derived files will be generated.
|
||||
/// - sourceRootPath: Path to the directory in which the Xcode project will be generated.
|
||||
/// - Throws: An error if the generation of the derived files errors.
|
||||
/// - Returns: A function to be called after the project generation to delete the derived files that are not necessary anymore.
|
||||
func generate(project: Project, sourceRootPath: AbsolutePath) throws -> () throws -> Void {
|
||||
func generate(graph: Graphing, project: Project, sourceRootPath: AbsolutePath) throws -> () throws -> Void {
|
||||
/// The files that are not necessary anymore should be deleted after we generate the project.
|
||||
/// Otherwise, Xcode will try to reload their references before the project generation.
|
||||
var toDelete: Set<AbsolutePath> = []
|
||||
|
||||
toDelete.formUnion(try generateInfoPlists(project: project, sourceRootPath: sourceRootPath))
|
||||
toDelete.formUnion(try generateInfoPlists(graph: graph, project: project, sourceRootPath: sourceRootPath))
|
||||
|
||||
return {
|
||||
try toDelete.forEach { try FileHandler.shared.delete($0) }
|
||||
|
@ -52,11 +54,12 @@ final class DerivedFileGenerator: DerivedFileGenerating {
|
|||
/// Genreates the Info.plist files.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - graph: The dependencies graph.
|
||||
/// - project: Project that contains the targets whose Info.plist files will be generated.
|
||||
/// - sourceRootPath: Path to the directory in which the project is getting generated.
|
||||
/// - Returns: A set with paths to the Info.plist files that are no longer necessary and therefore need to be removed.
|
||||
/// - Throws: An error if the encoding of the Info.plist content fails.
|
||||
func generateInfoPlists(project: Project, sourceRootPath: AbsolutePath) throws -> Set<AbsolutePath> {
|
||||
func generateInfoPlists(graph: Graphing, project: Project, sourceRootPath: AbsolutePath) throws -> Set<AbsolutePath> {
|
||||
let infoPlistsPath = DerivedFileGenerator.infoPlistsPath(sourceRootPath: sourceRootPath)
|
||||
let targetsWithGeneratableInfoPlists = project.targets.filter {
|
||||
if let infoPlist = $0.infoPlist, case InfoPlist.file = infoPlist {
|
||||
|
@ -86,7 +89,10 @@ final class DerivedFileGenerator: DerivedFileGenerating {
|
|||
if case let InfoPlist.dictionary(content) = infoPlist {
|
||||
dictionary = content.mapValues { $0.value }
|
||||
} else if case let InfoPlist.extendingDefault(extended) = infoPlist,
|
||||
let content = self.infoPlistContentProvider.content(target: target, extendedWith: extended) {
|
||||
let content = self.infoPlistContentProvider.content(graph: graph,
|
||||
project: project,
|
||||
target: target,
|
||||
extendedWith: extended) {
|
||||
dictionary = content
|
||||
} else {
|
||||
return
|
||||
|
|
|
@ -8,10 +8,12 @@ protocol InfoPlistContentProviding {
|
|||
/// and product, and extends them with the values provided by the user.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - graph: The dependencies graph.
|
||||
/// - project: The project that hosts the target for which the Info.plist content will be returned
|
||||
/// - target: Target whose Info.plist content will be returned.
|
||||
/// - extendedWith: Values provided by the user to extend the default ones.
|
||||
/// - Returns: Content to generate the Info.plist file.
|
||||
func content(target: Target, extendedWith: [String: InfoPlist.Value]) -> [String: Any]?
|
||||
func content(graph: Graphing, project: Project, target: Target, extendedWith: [String: InfoPlist.Value]) -> [String: Any]?
|
||||
}
|
||||
|
||||
final class InfoPlistContentProvider: InfoPlistContentProviding {
|
||||
|
@ -20,10 +22,12 @@ final class InfoPlistContentProvider: InfoPlistContentProviding {
|
|||
/// and product, and extends them with the values provided by the user.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - graph: The dependencies graph.
|
||||
/// - project: The project that hosts the target for which the Info.plist content will be returned
|
||||
/// - target: Target whose Info.plist content will be returned.
|
||||
/// - extendedWith: Values provided by the user to extend the default ones.
|
||||
/// - Returns: Content to generate the Info.plist file.
|
||||
func content(target: Target, extendedWith: [String: InfoPlist.Value]) -> [String: Any]? {
|
||||
func content(graph: Graphing, project: Project, target: Target, extendedWith: [String: InfoPlist.Value]) -> [String: Any]? {
|
||||
if target.product == .staticLibrary || target.product == .dynamicLibrary {
|
||||
return nil
|
||||
}
|
||||
|
@ -48,6 +52,20 @@ final class InfoPlistContentProvider: InfoPlistContentProviding {
|
|||
extend(&content, with: macos())
|
||||
}
|
||||
|
||||
// watchOS app
|
||||
if target.product == .watch2App, target.platform == .watchOS {
|
||||
let host = graph.hostTargetNodeFor(path: project.path, name: target.name)
|
||||
extend(&content, with: watchosApp(name: target.name,
|
||||
hostAppBundleId: host?.target.bundleId))
|
||||
}
|
||||
|
||||
// watchOS app extension
|
||||
if target.product == .watch2Extension, target.platform == .watchOS {
|
||||
let host = graph.hostTargetNodeFor(path: project.path, name: target.name)
|
||||
extend(&content, with: watchosAppExtension(name: target.name,
|
||||
hostAppBundleId: host?.target.bundleId))
|
||||
}
|
||||
|
||||
extend(&content, with: extendedWith.unwrappingValues())
|
||||
|
||||
return content
|
||||
|
@ -145,6 +163,39 @@ final class InfoPlistContentProvider: InfoPlistContentProviding {
|
|||
]
|
||||
}
|
||||
|
||||
/// Returns the default Info.plist content for a watchOS App
|
||||
///
|
||||
/// - Parameter hostAppBundleId: The host application's bundle identifier
|
||||
private func watchosApp(name: String, hostAppBundleId: String?) -> [String: Any] {
|
||||
var infoPlist: [String: Any] = [
|
||||
"CFBundleDisplayName": name,
|
||||
"WKWatchKitApp": true,
|
||||
"UISupportedInterfaceOrientations": [
|
||||
"UIInterfaceOrientationPortrait",
|
||||
"UIInterfaceOrientationPortraitUpsideDown",
|
||||
],
|
||||
]
|
||||
if let hostAppBundleId = hostAppBundleId {
|
||||
infoPlist["WKCompanionAppBundleIdentifier"] = hostAppBundleId
|
||||
}
|
||||
return infoPlist
|
||||
}
|
||||
|
||||
/// Returns the default Info.plist content for a watchOS App Extension
|
||||
///
|
||||
/// - Parameter hostAppBundleId: The host application's bundle identifier
|
||||
private func watchosAppExtension(name: String, hostAppBundleId: String?) -> [String: Any] {
|
||||
let extensionAttributes: [String: Any] = hostAppBundleId.map { ["WKAppBundleIdentifier": $0] } ?? [:]
|
||||
return [
|
||||
"CFBundleDisplayName": name,
|
||||
"NSExtension": [
|
||||
"NSExtensionAttributes": extensionAttributes,
|
||||
"NSExtensionPointIdentifier": "com.apple.watchkit",
|
||||
],
|
||||
"WKExtensionDelegateClassName": "$(PRODUCT_MODULE_NAME).ExtensionDelegate",
|
||||
]
|
||||
}
|
||||
|
||||
/// Given a dictionary, it extends it with another dictionary.
|
||||
///
|
||||
/// - Parameters:
|
||||
|
|
|
@ -88,7 +88,7 @@ final class ProjectGenerator: ProjectGenerating {
|
|||
sourceRootPath: AbsolutePath,
|
||||
xcodeprojPath: AbsolutePath) throws -> GeneratedProject {
|
||||
// Derived files
|
||||
let deleteOldDerivedFiles = try derivedFileGenerator.generate(project: project, sourceRootPath: sourceRootPath)
|
||||
let deleteOldDerivedFiles = try derivedFileGenerator.generate(graph: graph, project: project, sourceRootPath: sourceRootPath)
|
||||
|
||||
let workspaceData = XCWorkspaceData(children: [])
|
||||
let workspace = XCWorkspace(data: workspaceData)
|
||||
|
|
|
@ -859,6 +859,44 @@ final class GraphTests: TuistUnitTestCase {
|
|||
XCTAssertEqual(got.first?.name, "StickerPackExtension")
|
||||
}
|
||||
|
||||
func test_hostTargetNode_watchApp() {
|
||||
// Given
|
||||
let app = Target.test(name: "App", platform: .iOS, product: .app)
|
||||
let watchApp = Target.test(name: "WatchApp", platform: .watchOS, product: .watch2App)
|
||||
let project = Project.test(path: "/path/a")
|
||||
|
||||
let graph = Graph.create(project: project,
|
||||
dependencies: [
|
||||
(target: app, dependencies: [watchApp]),
|
||||
(target: watchApp, dependencies: []),
|
||||
])
|
||||
|
||||
// When
|
||||
let result = graph.hostTargetNodeFor(path: project.path, name: "WatchApp")
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result?.target, app)
|
||||
}
|
||||
|
||||
func test_hostTargetNode_watchAppExtension() {
|
||||
// Given
|
||||
let watchApp = Target.test(name: "WatchApp", platform: .watchOS, product: .watch2App)
|
||||
let watchAppExtension = Target.test(name: "WatchAppExtension", platform: .watchOS, product: .watch2Extension)
|
||||
let project = Project.test(path: "/path/a")
|
||||
|
||||
let graph = Graph.create(project: project,
|
||||
dependencies: [
|
||||
(target: watchApp, dependencies: [watchAppExtension]),
|
||||
(target: watchAppExtension, dependencies: []),
|
||||
])
|
||||
|
||||
// When
|
||||
let result = graph.hostTargetNodeFor(path: project.path, name: "WatchAppExtension")
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result?.target, watchApp)
|
||||
}
|
||||
|
||||
func test_encode() {
|
||||
// Given
|
||||
System.shared = System()
|
||||
|
|
|
@ -33,7 +33,7 @@ final class DerivedFileGeneratorTests: TuistUnitTestCase {
|
|||
let path = infoPlistsPath.appending(component: "Target.plist")
|
||||
|
||||
// When
|
||||
_ = try subject.generate(project: project, sourceRootPath: temporaryPath)
|
||||
_ = try subject.generate(graph: Graph.test(), project: project, sourceRootPath: temporaryPath)
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(FileHandler.shared.exists(path))
|
||||
|
@ -53,7 +53,7 @@ final class DerivedFileGeneratorTests: TuistUnitTestCase {
|
|||
infoPlistContentProvider.contentStub = ["test": "value"]
|
||||
|
||||
// When
|
||||
_ = try subject.generate(project: project, sourceRootPath: temporaryPath)
|
||||
_ = try subject.generate(graph: Graph.test(), project: project, sourceRootPath: temporaryPath)
|
||||
|
||||
// Then
|
||||
XCTAssertTrue(FileHandler.shared.exists(path))
|
||||
|
@ -78,7 +78,8 @@ final class DerivedFileGeneratorTests: TuistUnitTestCase {
|
|||
try FileHandler.shared.touch(oldPlistPath)
|
||||
|
||||
// When
|
||||
let deleteOldDerivedFiles = try subject.generate(project: project,
|
||||
let deleteOldDerivedFiles = try subject.generate(graph: Graph.test(),
|
||||
project: project,
|
||||
sourceRootPath: temporaryPath)
|
||||
try deleteOldDerivedFiles()
|
||||
|
||||
|
@ -95,7 +96,7 @@ final class DerivedFileGeneratorTests: TuistUnitTestCase {
|
|||
let path = infoPlistsPath.appending(component: "Target.plist")
|
||||
|
||||
// When
|
||||
_ = try subject.generate(project: project, sourceRootPath: temporaryPath)
|
||||
_ = try subject.generate(graph: Graph.test(), project: project, sourceRootPath: temporaryPath)
|
||||
|
||||
// Then
|
||||
XCTAssertFalse(FileHandler.shared.exists(path))
|
||||
|
|
|
@ -17,7 +17,10 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
let target = Target.test(platform: .iOS, product: .app)
|
||||
|
||||
// When
|
||||
let got = subject.content(target: target, extendedWith: ["ExtraAttribute": "Value"])
|
||||
let got = subject.content(graph: Graph.test(),
|
||||
project: .empty(),
|
||||
target: target,
|
||||
extendedWith: ["ExtraAttribute": "Value"])
|
||||
|
||||
// Then
|
||||
assertEqual(got, [
|
||||
|
@ -53,7 +56,10 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
let target = Target.test(platform: .macOS, product: .app)
|
||||
|
||||
// When
|
||||
let got = subject.content(target: target, extendedWith: ["ExtraAttribute": "Value"])
|
||||
let got = subject.content(graph: Graph.test(),
|
||||
project: .empty(),
|
||||
target: target,
|
||||
extendedWith: ["ExtraAttribute": "Value"])
|
||||
|
||||
// Then
|
||||
assertEqual(got, [
|
||||
|
@ -79,7 +85,10 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
let target = Target.test(platform: .macOS, product: .framework)
|
||||
|
||||
// When
|
||||
let got = subject.content(target: target, extendedWith: ["ExtraAttribute": "Value"])
|
||||
let got = subject.content(graph: Graph.test(),
|
||||
project: .empty(),
|
||||
target: target,
|
||||
extendedWith: ["ExtraAttribute": "Value"])
|
||||
|
||||
// Then
|
||||
assertEqual(got, [
|
||||
|
@ -101,7 +110,10 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
let target = Target.test(platform: .macOS, product: .staticLibrary)
|
||||
|
||||
// When
|
||||
let got = subject.content(target: target, extendedWith: ["ExtraAttribute": "Value"])
|
||||
let got = subject.content(graph: Graph.test(),
|
||||
project: .empty(),
|
||||
target: target,
|
||||
extendedWith: ["ExtraAttribute": "Value"])
|
||||
|
||||
// Then
|
||||
XCTAssertNil(got)
|
||||
|
@ -112,21 +124,128 @@ final class InfoPlistContentProviderTests: XCTestCase {
|
|||
let target = Target.test(platform: .macOS, product: .dynamicLibrary)
|
||||
|
||||
// When
|
||||
let got = subject.content(target: target, extendedWith: ["ExtraAttribute": "Value"])
|
||||
let got = subject.content(graph: Graph.test(),
|
||||
project: .empty(),
|
||||
target: target,
|
||||
extendedWith: ["ExtraAttribute": "Value"])
|
||||
|
||||
// Then
|
||||
XCTAssertNil(got)
|
||||
}
|
||||
|
||||
func test_contentPackageType() {
|
||||
assertPackageType(subject.content(target: .test(product: .app), extendedWith: [:]), "APPL")
|
||||
assertPackageType(subject.content(target: .test(product: .unitTests), extendedWith: [:]), "BNDL")
|
||||
assertPackageType(subject.content(target: .test(product: .uiTests), extendedWith: [:]), "BNDL")
|
||||
assertPackageType(subject.content(target: .test(product: .bundle), extendedWith: [:]), "BNDL")
|
||||
assertPackageType(subject.content(target: .test(product: .framework), extendedWith: [:]), "FMWK")
|
||||
assertPackageType(subject.content(target: .test(product: .staticFramework), extendedWith: [:]), "FMWK")
|
||||
func content(for target: Target) -> [String: Any]? {
|
||||
subject.content(graph: Graph.test(),
|
||||
project: .empty(),
|
||||
target: target,
|
||||
extendedWith: [:])
|
||||
}
|
||||
|
||||
assertPackageType(content(for: .test(product: .app)), "APPL")
|
||||
assertPackageType(content(for: .test(product: .unitTests)), "BNDL")
|
||||
assertPackageType(content(for: .test(product: .uiTests)), "BNDL")
|
||||
assertPackageType(content(for: .test(product: .bundle)), "BNDL")
|
||||
assertPackageType(content(for: .test(product: .framework)), "FMWK")
|
||||
assertPackageType(content(for: .test(product: .staticFramework)), "FMWK")
|
||||
assertPackageType(content(for: .test(product: .watch2App)), "$(PRODUCT_BUNDLE_PACKAGE_TYPE)")
|
||||
}
|
||||
|
||||
func test_content_whenWatchOSApp() {
|
||||
// Given
|
||||
let watchApp = Target.test(name: "MyWatchApp",
|
||||
platform: .watchOS,
|
||||
product: .watch2App)
|
||||
let app = Target.test(platform: .iOS,
|
||||
product: .app,
|
||||
bundleId: "io.tuist.my.app.id")
|
||||
let project = Project.test(targets: [
|
||||
app,
|
||||
watchApp,
|
||||
])
|
||||
let graph = Graph.create(project: project, dependencies: [
|
||||
(target: app, dependencies: [watchApp]),
|
||||
(target: watchApp, dependencies: []),
|
||||
])
|
||||
|
||||
// When
|
||||
let got = subject.content(graph: graph,
|
||||
project: project,
|
||||
target: watchApp,
|
||||
extendedWith: [
|
||||
"ExtraAttribute": "Value",
|
||||
])
|
||||
|
||||
// Then
|
||||
assertEqual(got, [
|
||||
"CFBundleName": "$(PRODUCT_NAME)",
|
||||
"CFBundleShortVersionString": "1.0",
|
||||
"CFBundlePackageType": "$(PRODUCT_BUNDLE_PACKAGE_TYPE)",
|
||||
"UISupportedInterfaceOrientations": [
|
||||
"UIInterfaceOrientationPortrait",
|
||||
"UIInterfaceOrientationPortraitUpsideDown",
|
||||
],
|
||||
"CFBundleIdentifier": "$(PRODUCT_BUNDLE_IDENTIFIER)",
|
||||
"CFBundleInfoDictionaryVersion": "6.0",
|
||||
"CFBundleVersion": "1",
|
||||
"CFBundleDevelopmentRegion": "$(DEVELOPMENT_LANGUAGE)",
|
||||
"CFBundleExecutable": "$(EXECUTABLE_NAME)",
|
||||
"CFBundleDisplayName": "MyWatchApp",
|
||||
"WKWatchKitApp": true,
|
||||
"WKCompanionAppBundleIdentifier": "io.tuist.my.app.id",
|
||||
"ExtraAttribute": "Value",
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
func test_content_whenWatchOSAppExtension() {
|
||||
// Given
|
||||
let watchAppExtension = Target.test(name: "MyWatchAppExtension",
|
||||
platform: .watchOS,
|
||||
product: .watch2Extension)
|
||||
let watchApp = Target.test(platform: .watchOS,
|
||||
product: .watch2App,
|
||||
bundleId: "io.tuist.my.app.id.mywatchapp")
|
||||
let project = Project.test(targets: [
|
||||
watchApp,
|
||||
watchAppExtension,
|
||||
])
|
||||
let graph = Graph.create(project: project, dependencies: [
|
||||
(target: watchApp, dependencies: [watchAppExtension]),
|
||||
(target: watchAppExtension, dependencies: []),
|
||||
])
|
||||
|
||||
// When
|
||||
let got = subject.content(graph: graph,
|
||||
project: project,
|
||||
target: watchAppExtension,
|
||||
extendedWith: [
|
||||
"ExtraAttribute": "Value",
|
||||
])
|
||||
|
||||
// Then
|
||||
assertEqual(got, [
|
||||
"CFBundleName": "$(PRODUCT_NAME)",
|
||||
"CFBundleShortVersionString": "1.0",
|
||||
"CFBundlePackageType": "$(PRODUCT_BUNDLE_PACKAGE_TYPE)",
|
||||
"CFBundleIdentifier": "$(PRODUCT_BUNDLE_IDENTIFIER)",
|
||||
"CFBundleInfoDictionaryVersion": "6.0",
|
||||
"CFBundleVersion": "1",
|
||||
"CFBundleDevelopmentRegion": "$(DEVELOPMENT_LANGUAGE)",
|
||||
"CFBundleExecutable": "$(EXECUTABLE_NAME)",
|
||||
"CFBundleDisplayName": "MyWatchAppExtension",
|
||||
"NSExtension": [
|
||||
"NSExtensionAttributes": [
|
||||
"WKAppBundleIdentifier": "io.tuist.my.app.id.mywatchapp",
|
||||
],
|
||||
"NSExtensionPointIdentifier": "com.apple.watchkit",
|
||||
],
|
||||
"WKExtensionDelegateClassName": "$(PRODUCT_MODULE_NAME).ExtensionDelegate",
|
||||
"ExtraAttribute": "Value",
|
||||
])
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
fileprivate func assertPackageType(_ lhs: [String: Any]?,
|
||||
_ packageType: String?,
|
||||
file: StaticString = #file,
|
||||
|
|
|
@ -4,11 +4,11 @@ import TuistCoreTesting
|
|||
@testable import TuistGenerator
|
||||
|
||||
final class MockInfoPlistContentProvider: InfoPlistContentProviding {
|
||||
var contentArgs: [(target: Target, extendedWith: [String: InfoPlist.Value])] = []
|
||||
var contentArgs: [(graph: Graphing, project: Project, target: Target, extendedWith: [String: InfoPlist.Value])] = []
|
||||
var contentStub: [String: Any]?
|
||||
|
||||
func content(target: Target, extendedWith: [String: InfoPlist.Value]) -> [String: Any]? {
|
||||
contentArgs.append((target: target, extendedWith: extendedWith))
|
||||
func content(graph: Graphing, project: Project, target: Target, extendedWith: [String: InfoPlist.Value]) -> [String: Any]? {
|
||||
contentArgs.append((graph: graph, project: project, target: target, extendedWith: extendedWith))
|
||||
return contentStub ?? [:]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ let project = Project(name: "App",
|
|||
platform: .watchOS,
|
||||
product: .watch2App,
|
||||
bundleId: "io.tuist.App.watchkitapp",
|
||||
infoPlist: "Support/WatchApp-Info.plist",
|
||||
infoPlist: .default,
|
||||
resources: "WatchApp/**",
|
||||
dependencies: [
|
||||
.target(name: "WatchAppExtension")
|
||||
|
@ -30,7 +30,9 @@ let project = Project(name: "App",
|
|||
platform: .watchOS,
|
||||
product: .watch2Extension,
|
||||
bundleId: "io.tuist.App.watchkitapp.watchkitextension",
|
||||
infoPlist: "Support/WatchAppExtension-Info.plist",
|
||||
infoPlist: .extendingDefault(with: [
|
||||
"CFBundleDisplayName": "WatchApp Extension"
|
||||
]),
|
||||
sources: ["WatchAppExtension/**"],
|
||||
resources: ["WatchAppExtension/**/*.xcassets"],
|
||||
dependencies: [
|
||||
|
|
Loading…
Reference in New Issue