diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 0cdbeb89bd..d99ca32d44 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -13,6 +13,7 @@ $injector.require("androidProjectService", "./services/android-project-service") $injector.require("androidPluginBuildService", "./services/android-plugin-build-service"); $injector.require("iOSEntitlementsService", "./services/ios-entitlements-service"); $injector.require("iOSExtensionsService", "./services/ios-extensions-service"); +$injector.require("iOSWatchAppService", "./services/ios-watch-app-service"); $injector.require("iOSProjectService", "./services/ios-project-service"); $injector.require("iOSProvisionService", "./services/ios-provision-service"); $injector.require("xcconfigService", "./services/xcconfig-service"); diff --git a/lib/constants.ts b/lib/constants.ts index a1a8961018..7443a4632a 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -49,6 +49,8 @@ export const TNS_NATIVE_SOURCE_GROUP_NAME = "TNSNativeSource"; export const NATIVE_SOURCE_FOLDER = "src"; export const APPLICATION_RESPONSE_TIMEOUT_SECONDS = 60; export const NATIVE_EXTENSION_FOLDER = "extensions"; +export const IOS_WATCHAPP_FOLDER = "watchapp"; +export const IOS_WATCHAPP_EXTENSION_FOLDER = "watchextension"; export class PackageVersion { static NEXT = "next"; @@ -280,3 +282,20 @@ export const LiveSyncEvents = { liveSyncStarted: "liveSyncStarted", liveSyncNotification: "notify" }; + +export enum IOSDeviceTargets { + ios = "1,2", + watchos = 4 +} + +export enum IOSNativeTargetProductTypes { + watchApp = "com.apple.product-type.application.watchapp2", + watchExtension = "com.apple.product-type.watchkit2-extension", + appExtension = "com.apple.product-type.app-extension" +} + +export enum IOSNativeTargetTypes { + watchApp = "watch_app", + watchExtension = "watch_extension", + appExtension = "app_extension" +} diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 37be0d1158..1b52116980 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -592,17 +592,39 @@ interface IIOSExtensionsService { removeExtensions(options: IRemoveExtensionsOptions): void; } -interface IAddExtensionsFromPathOptions { - extensionsFolderPath: string; +/** + * Describes a service used to add and remove iOS extension + */ +interface IIOSExtensionsService { + addExtensionsFromPath(options: IAddExtensionsFromPathOptions): Promise; + removeExtensions(options: IRemoveExtensionsOptions): void; +} + +interface IIOSWatchAppService { + addWatchAppFromPath(options: IAddWatchAppFromPathOptions): Promise; + removeWatchApp(options: IRemoveWatchAppOptions): void; +} + +interface IAddTargetFromPathOptions { projectData: IProjectData; platformData: IPlatformData; pbxProjPath: string; } +interface IAddExtensionsFromPathOptions extends IAddTargetFromPathOptions { + extensionsFolderPath: string; +} + +interface IAddWatchAppFromPathOptions extends IAddTargetFromPathOptions { + watchAppFolderPath: string; +} + interface IRemoveExtensionsOptions { pbxProjPath: string } +interface IRemoveWatchAppOptions extends IRemoveExtensionsOptions{} + interface IRubyFunction { functionName: string; functionParameters?: string; diff --git a/lib/definitions/xcode.d.ts b/lib/definitions/xcode.d.ts index 065fbe2067..01c561dab3 100644 --- a/lib/definitions/xcode.d.ts +++ b/lib/definitions/xcode.d.ts @@ -28,7 +28,7 @@ declare module "nativescript-dev-xcode" { pbxXCBuildConfigurationSection(): any; - addTarget(targetName: string, targetType: string, targetPath?: string): target; + addTarget(targetName: string, targetType: string, targetPath?: string, parentTarget?: string): target; addBuildPhase(filePathsArray: string[], buildPhaseType: string, comment: string, @@ -47,6 +47,7 @@ declare module "nativescript-dev-xcode" { addBuildProperty(prop: string, value: any, build_name?: string, productName?: string): void; addToHeaderSearchPaths(file: string|Object, productName?: string): void; removeTargetsByProductType(targetType: string): void + getFirstTarget(): {uuid: string} } class target { diff --git a/lib/services/ios-extensions-service.ts b/lib/services/ios-extensions-service.ts index e54420b635..fc83e100cf 100644 --- a/lib/services/ios-extensions-service.ts +++ b/lib/services/ios-extensions-service.ts @@ -1,9 +1,12 @@ import * as path from "path"; - -export class IOSExtensionsService implements IIOSExtensionsService { - constructor(private $fs: IFileSystem, - private $pbxprojDomXcode: IPbxprojDomXcode, - private $xcode: IXcode) { +import { NativeTargetServiceBase } from "./ios-native-target-service-base"; +import { IOSNativeTargetProductTypes, IOSNativeTargetTypes } from "../constants"; + +export class IOSExtensionsService extends NativeTargetServiceBase implements IIOSExtensionsService { + constructor(protected $fs: IFileSystem, + protected $pbxprojDomXcode: IPbxprojDomXcode, + protected $xcode: IXcode) { + super($fs, $pbxprojDomXcode, $xcode); } public async addExtensionsFromPath({extensionsFolderPath, projectData, platformData, pbxProjPath}: IAddExtensionsFromPathOptions): Promise { @@ -14,81 +17,35 @@ export class IOSExtensionsService implements IIOSExtensionsService { } const project = new this.$xcode.project(pbxProjPath); project.parseSync(); - this.$fs.readDirectory(extensionsFolderPath) - .filter(fileName => { - const filePath = path.join(extensionsFolderPath, fileName); - const stats = this.$fs.getFsStats(filePath); - - return stats.isDirectory() && !fileName.startsWith("."); - }) + this.getTargetDirectories(extensionsFolderPath) .forEach(extensionFolder => { - const targetUuid = this.addExtensionToProject(extensionsFolderPath, extensionFolder, project, projectData, platformData); - targetUuids.push(targetUuid); + const target = this.addTargetToProject(extensionsFolderPath, extensionFolder, IOSNativeTargetTypes.appExtension, project, platformData); + this.configureTarget(extensionFolder, path.join(extensionsFolderPath, extensionFolder), target, project, projectData); + targetUuids.push(target.uuid); addedExtensions = true; }); this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); - this.prepareExtensionSigning(targetUuids, projectData, pbxProjPath); + this.prepareSigning(targetUuids, projectData, pbxProjPath); return addedExtensions; } - private addExtensionToProject(extensionsFolderPath: string, extensionFolder: string, project: IXcode.project, projectData: IProjectData, platformData: IPlatformData): string { - const extensionPath = path.join(extensionsFolderPath, extensionFolder); - const extensionRelativePath = path.relative(platformData.projectRoot, extensionPath); - const files = this.$fs.readDirectory(extensionPath) - .filter(filePath => !filePath.startsWith(".")) - .map(filePath => path.join(extensionPath, filePath)); - const target = project.addTarget(extensionFolder, 'app_extension', extensionRelativePath); - project.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', target.uuid); - project.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid); - project.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid); - - const extJsonPath = path.join(extensionsFolderPath, extensionFolder, "extension.json"); - if (this.$fs.exists(extJsonPath)) { - const extensionJson = this.$fs.readJson(extJsonPath); - _.forEach(extensionJson.frameworks, framework => { - project.addFramework( - framework, - { target: target.uuid } - ); - }); - if (extensionJson.assetcatalogCompilerAppiconName) { - project.addToBuildSettings("ASSETCATALOG_COMPILER_APPICON_NAME", extensionJson.assetcatalogCompilerAppiconName, target.uuid); - } - } + private configureTarget(extensionName: string, extensionPath: string, target: IXcode.target, project: IXcode.project, projectData: IProjectData) { + const extJsonPath = path.join(extensionPath, "extension.json"); - project.addPbxGroup(files, extensionFolder, extensionPath, null, { isMain: true, target: target.uuid, filesRelativeToProject: true }); - project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", `${projectData.projectIdentifiers.ios}.${extensionFolder}`, "Debug", extensionFolder); - project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", `${projectData.projectIdentifiers.ios}.${extensionFolder}`, "Release", extensionFolder); - project.addToHeaderSearchPaths(extensionPath, target.pbxNativeTarget.productName); + this.setXcodeTargetBuildConfigurationProperties( + [{name: "PRODUCT_BUNDLE_IDENTIFIER", value: `${projectData.projectIdentifiers.ios}.${extensionName}`}], + extensionName, + project); - return target.uuid; - } - - private prepareExtensionSigning(targetUuids: string[], projectData:IProjectData, projectPath: string) { - const xcode = this.$pbxprojDomXcode.Xcode.open(projectPath); - const signing = xcode.getSigning(projectData.projectName); - if (signing !== undefined) { - _.forEach(targetUuids, targetUuid => { - if (signing.style === "Automatic") { - xcode.setAutomaticSigningStyleByTargetKey(targetUuid, signing.team); - } else { - for (const config in signing.configurations) { - const signingConfiguration = signing.configurations[config]; - xcode.setManualSigningStyleByTargetKey(targetUuid, signingConfiguration); - break; - } - } - }); - } - xcode.save(); + this.setConfigurationsFromJsonFile(extJsonPath, target.uuid, extensionName, project); } public removeExtensions({pbxProjPath}: IRemoveExtensionsOptions): void { const project = new this.$xcode.project(pbxProjPath); project.parseSync(); - project.removeTargetsByProductType("com.apple.product-type.app-extension"); + project.removeTargetsByProductType(IOSNativeTargetProductTypes.appExtension); this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); } } diff --git a/lib/services/ios-native-target-service-base.ts b/lib/services/ios-native-target-service-base.ts new file mode 100644 index 0000000000..5f203d405b --- /dev/null +++ b/lib/services/ios-native-target-service-base.ts @@ -0,0 +1,96 @@ +import * as path from "path"; + +export enum BuildNames { + debug = "Debug", + release = "Release" +} + +export interface IXcodeTargetBuildConfigurationProperty { + name: string; + value: any; + buildNames?: BuildNames[]; +} + +export abstract class NativeTargetServiceBase { + constructor(protected $fs: IFileSystem, + protected $pbxprojDomXcode: IPbxprojDomXcode, + protected $xcode: IXcode) { + } + + protected addTargetToProject(extensionsFolderPath: string, extensionFolder: string, targetType: string, project: IXcode.project, platformData: IPlatformData, parentTarget?: string): IXcode.target { + const extensionPath = path.join(extensionsFolderPath, extensionFolder); + const extensionRelativePath = path.relative(platformData.projectRoot, extensionPath); + const files = this.$fs.readDirectory(extensionPath) + .filter(filePath => !filePath.startsWith(".")) + .map(filePath => path.join(extensionPath, filePath)); + const target = project.addTarget(extensionFolder, targetType, extensionRelativePath, parentTarget); + project.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', target.uuid); + project.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid); + project.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid); + + project.addPbxGroup(files, extensionFolder, extensionPath, null, { isMain: true, target: target.uuid, filesRelativeToProject: true }); + project.addToHeaderSearchPaths(extensionPath, target.pbxNativeTarget.productName); + return target; + } + + protected prepareSigning(targetUuids: string[], projectData:IProjectData, projectPath: string) { + const xcode = this.$pbxprojDomXcode.Xcode.open(projectPath); + const signing = xcode.getSigning(projectData.projectName); + if (signing !== undefined) { + _.forEach(targetUuids, targetUuid => { + if (signing.style === "Automatic") { + xcode.setAutomaticSigningStyleByTargetKey(targetUuid, signing.team); + } else { + for (const config in signing.configurations) { + const signingConfiguration = signing.configurations[config]; + xcode.setManualSigningStyleByTargetKey(targetUuid, signingConfiguration); + break; + } + } + }); + } + xcode.save(); + } + + protected getTargetDirectories(folderPath: string): string[] { + return this.$fs.readDirectory(folderPath) + .filter(fileName => { + const filePath = path.join(folderPath, fileName); + const stats = this.$fs.getFsStats(filePath); + + return stats.isDirectory() && !fileName.startsWith("."); + }); + } + + protected setXcodeTargetBuildConfigurationProperties(properties: IXcodeTargetBuildConfigurationProperty[], targetName: string, project: IXcode.project): void { + properties.forEach(property => { + const buildNames = property.buildNames || [BuildNames.debug, BuildNames.release]; + buildNames.forEach((buildName) => { + project.addBuildProperty(property.name, property.value, buildName, targetName); + }); + }); + } + + protected setConfigurationsFromJsonFile(jsonPath: string, targetUuid: string, targetName: string, project: IXcode.project) { + if (this.$fs.exists(jsonPath)) { + const configurationJson = this.$fs.readJson(jsonPath) || {}; + + _.forEach(configurationJson.frameworks, framework => { + project.addFramework( + framework, + { target: targetUuid } + ); + }); + + if (configurationJson.assetcatalogCompilerAppiconName) { + project.addToBuildSettings("ASSETCATALOG_COMPILER_APPICON_NAME", configurationJson.assetcatalogCompilerAppiconName, targetUuid); + } + + if (configurationJson.targetBuildConfigurationProperties) { + const properties: IXcodeTargetBuildConfigurationProperty[] = []; + _.forEach(configurationJson.targetBuildConfigurationProperties, (value, name: string) => properties.push({value, name})); + this.setXcodeTargetBuildConfigurationProperties(properties, targetName, project); + } + } + } +} diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 3a68c26b91..e2a4d85b96 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -13,7 +13,7 @@ import * as plist from "plist"; import { IOSProvisionService } from "./ios-provision-service"; import { IOSEntitlementsService } from "./ios-entitlements-service"; import * as mobileProvisionFinder from "ios-mobileprovision-finder"; -import { BUILD_XCCONFIG_FILE_NAME, IosProjectConstants } from "../constants"; +import { BUILD_XCCONFIG_FILE_NAME, IosProjectConstants, IOSNativeTargetProductTypes } from "../constants"; interface INativeSourceCodeGroup { name: string; @@ -21,6 +21,11 @@ interface INativeSourceCodeGroup { files: string[]; } +enum ProductArgs { + target = "target", + scheme = "scheme" +} + const DevicePlatformSdkName = "iphoneos"; const SimulatorPlatformSdkName = "iphonesimulator"; @@ -53,7 +58,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $plistParser: IPlistParser, private $sysInfo: ISysInfo, private $xcconfigService: IXcconfigService, - private $iOSExtensionsService: IIOSExtensionsService) { + private $iOSExtensionsService: IIOSExtensionsService, + private $iOSWatchAppService: IIOSWatchAppService) { super($fs, $projectDataService); } @@ -214,7 +220,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ const archivePath = options && options.archivePath ? path.resolve(options.archivePath) : path.join(platformData.getBuildOutputPath(buildConfig), projectData.projectName + ".xcarchive"); let args = ["archive", "-archivePath", archivePath, "-configuration", getConfigurationName(!buildConfig || buildConfig.release)] - .concat(this.xcbuildProjectArgs(projectRoot, projectData, "scheme")); + .concat(this.xcbuildProjectArgs(projectRoot, projectData, ProductArgs.scheme)); if (options && options.additionalArgs) { args = args.concat(options.additionalArgs); @@ -339,7 +345,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return exportFile; } - private xcbuildProjectArgs(projectRoot: string, projectData: IProjectData, product?: "scheme" | "target"): string[] { + private xcbuildProjectArgs(projectRoot: string, projectData: IProjectData, product?: ProductArgs): string[] { const xcworkspacePath = path.join(projectRoot, projectData.projectName + ".xcworkspace"); if (this.$fs.exists(xcworkspacePath)) { return ["-workspace", xcworkspacePath, product ? "-" + product : "-scheme", projectData.projectName]; @@ -413,8 +419,13 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ args = args.concat((buildConfig && buildConfig.architectures) || this.getBuildArchitectures(projectData, buildConfig, ["armv7", "arm64"])); + if (!this.hasWatchApp(projectData)) { + args = args.concat([ + "-sdk", DevicePlatformSdkName + ]); + } + args = args.concat([ - "-sdk", DevicePlatformSdkName, "BUILD_DIR=" + path.join(projectRoot, constants.BUILD_DIR) ]); @@ -502,7 +513,12 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } xcode.setAutomaticSigningStyle(projectData.projectName, teamId); - xcode.setAutomaticSigningStyleByTargetProductType("com.apple.product-type.app-extension", teamId); + xcode.setAutomaticSigningStyleByTargetProductTypesList([ + IOSNativeTargetProductTypes.appExtension, + IOSNativeTargetProductTypes.watchApp, + IOSNativeTargetProductTypes.watchExtension + ], + teamId); xcode.save(); this.$logger.trace(`Set Automatic signing style and team id ${teamId}.`); @@ -544,7 +560,12 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ identity: mobileprovision.Type === "Development" ? "iPhone Developer" : "iPhone Distribution" }; xcode.setManualSigningStyle(projectData.projectName, configuration); - xcode.setManualSigningStyleByTargetProductType("com.apple.product-type.app-extension", configuration); + xcode.setManualSigningStyleByTargetProductTypesList([ + IOSNativeTargetProductTypes.appExtension, + IOSNativeTargetProductTypes.watchApp, + IOSNativeTargetProductTypes.watchExtension + ], + configuration); xcode.save(); // this.cache(uuid); @@ -578,18 +599,25 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private async buildForSimulator(projectRoot: string, args: string[], projectData: IProjectData, buildConfig?: IBuildConfig): Promise { const architectures = this.getBuildArchitectures(projectData, buildConfig, ["i386", "x86_64"]); + let product = ProductArgs.target; args = args .concat(architectures) .concat([ "build", "-configuration", getConfigurationName(buildConfig.release), - "-sdk", SimulatorPlatformSdkName, "ONLY_ACTIVE_ARCH=NO", "BUILD_DIR=" + path.join(projectRoot, constants.BUILD_DIR), - "CODE_SIGN_IDENTITY=", - ]) - .concat(this.xcbuildProjectArgs(projectRoot, projectData)); + ]); + + if (this.hasWatchApp(projectData)) { + product = ProductArgs.scheme; + args = args.concat(["-destination", "generic/platform=iOS Simulator", "CODE_SIGNING_ALLOWED=NO"]); + } else { + args = args.concat(["-sdk", SimulatorPlatformSdkName, "CODE_SIGN_IDENTITY="]); + } + + args = args.concat(this.xcbuildProjectArgs(projectRoot, projectData, product)); await this.xcodebuild(args, projectRoot, buildConfig.buildOutputStdio); } @@ -745,6 +773,8 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f public async prepareProject(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise { const projectRoot = path.join(projectData.platformsDir, "ios"); + const platformData = this.getPlatformData(projectData); + const resourcesDirectoryPath = projectData.getAppResourcesDirectoryPath(); const provision = platformSpecificData && platformSpecificData.provision; const teamId = platformSpecificData && platformSpecificData.teamId; @@ -783,14 +813,21 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f this.savePbxProj(project, projectData); const resourcesNativeCodePath = path.join( - projectData.getAppResourcesDirectoryPath(), - this.getPlatformData(projectData).normalizedPlatformName, + resourcesDirectoryPath, + platformData.normalizedPlatformName, constants.NATIVE_SOURCE_FOLDER ); - await this.prepareNativeSourceCode(constants.TNS_NATIVE_SOURCE_GROUP_NAME, resourcesNativeCodePath, projectData); } + const pbxProjPath = this.getPbxProjPath(projectData); + this.$iOSWatchAppService.removeWatchApp({ pbxProjPath }); + const addedWatchApp = await this.$iOSWatchAppService.addWatchAppFromPath({ watchAppFolderPath: path.join(resourcesDirectoryPath, platformData.normalizedPlatformName), projectData, platformData, pbxProjPath }); + + if (addedWatchApp) { + this.$logger.warn("The support for Apple Watch App is currently in Beta. For more information about the current development state and any known issues, please check the relevant GitHub issue: ISSUE LINK"); + } + } public prepareAppResources(appResourcesDirectoryPath: string, projectData: IProjectData): void { @@ -803,6 +840,8 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f // src folder should not be copied as the pbxproject will have references to its files this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, constants.NATIVE_SOURCE_FOLDER)); this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, constants.NATIVE_EXTENSION_FOLDER)); + this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, "watchapp")); + this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, "watchextension")); this.$fs.deleteDirectory(this.getAppResourcesDestinationDirectoryPath(projectData)); } @@ -1389,6 +1428,17 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f "Enterprise": "enterprise" }[provision.Type]; } + + private hasWatchApp(projectData: IProjectData) { + const platformData = this.getPlatformData(projectData); + const watchAppPath = path.join( + projectData.getAppResourcesDirectoryPath(), + platformData.normalizedPlatformName, + constants.IOS_WATCHAPP_FOLDER + ); + + return this.$fs.exists(watchAppPath); + } } $injector.register("iOSProjectService", IOSProjectService); diff --git a/lib/services/ios-watch-app-service.ts b/lib/services/ios-watch-app-service.ts new file mode 100644 index 0000000000..dae287e9fb --- /dev/null +++ b/lib/services/ios-watch-app-service.ts @@ -0,0 +1,84 @@ +import * as path from "path"; +import { NativeTargetServiceBase } from "./ios-native-target-service-base"; +import { IOSDeviceTargets, IOS_WATCHAPP_FOLDER, IOS_WATCHAPP_EXTENSION_FOLDER, IOSNativeTargetProductTypes, IOSNativeTargetTypes } from "../constants"; + +export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSWatchAppService { + private static WATCH_APP_IDENTIFIER = "watchkitapp"; + private static WACTCH_EXTENSION_IDENTIFIER = "watchkitextension"; + constructor(protected $fs: IFileSystem, + protected $pbxprojDomXcode: IPbxprojDomXcode, + protected $xcode: IXcode) { + super($fs, $pbxprojDomXcode, $xcode); + } + + public async addWatchAppFromPath({watchAppFolderPath, projectData, platformData, pbxProjPath}: IAddWatchAppFromPathOptions): Promise { + const targetUuids: string[] = []; + const appPath = path.join(watchAppFolderPath, IOS_WATCHAPP_FOLDER); + const extensionPath = path.join(watchAppFolderPath, IOS_WATCHAPP_EXTENSION_FOLDER); + + if (!this.$fs.exists(appPath) || !this.$fs.exists(extensionPath)) { + return false; + } + + const appFolder = this.getTargetDirectories(appPath)[0]; + const extensionFolder = this.getTargetDirectories(extensionPath)[0]; + + const project = new this.$xcode.project(pbxProjPath); + project.parseSync(); + + const watchApptarget = this.addTargetToProject(appPath, appFolder, IOSNativeTargetTypes.watchApp, project, platformData, project.getFirstTarget().uuid); + this.configureTarget( + appFolder, + path.join(appPath, appFolder), + `${projectData.projectIdentifiers.ios}.${IOSWatchAppService.WATCH_APP_IDENTIFIER}`, + "watchapp.json", + watchApptarget, + project + ); + targetUuids.push(watchApptarget.uuid); + + const watchExtensionTarget = this.addTargetToProject(extensionPath, extensionFolder, IOSNativeTargetTypes.watchExtension, project, platformData, watchApptarget.uuid); + this.configureTarget( + extensionFolder, + path.join(extensionPath, extensionFolder), + `${projectData.projectIdentifiers.ios}.${IOSWatchAppService.WATCH_APP_IDENTIFIER}.${IOSWatchAppService.WACTCH_EXTENSION_IDENTIFIER}`, + "extension.json", + watchExtensionTarget, + project); + targetUuids.push(watchExtensionTarget.uuid); + + this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); + this.prepareSigning(targetUuids, projectData, pbxProjPath); + + return true; + } + + public removeWatchApp({pbxProjPath}: IRemoveWatchAppOptions): void { + const project = new this.$xcode.project(pbxProjPath); + project.parseSync(); + project.removeTargetsByProductType(IOSNativeTargetProductTypes.watchApp); + project.removeTargetsByProductType(IOSNativeTargetProductTypes.watchExtension); + this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true})); + } + + private configureTarget(targetName: string, targetPath: string, identifier: string, configurationFileName: string, target: IXcode.target, project: IXcode.project) { + const targetConfigurationJsonPath = path.join(targetPath, configurationFileName); + + const identifierParts = identifier.split("."); + identifierParts.pop(); + const wkAppBundleIdentifier = identifierParts.join("."); + + this.setXcodeTargetBuildConfigurationProperties([ + {name: "PRODUCT_BUNDLE_IDENTIFIER", value: identifier}, + {name: "SDKROOT", value: "watchos"}, + {name: "TARGETED_DEVICE_FAMILY", value: IOSDeviceTargets.watchos}, + {name: "WATCHOS_DEPLOYMENT_TARGET", value: 5.2}, + {name: "WK_APP_BUNDLE_IDENTIFIER", value: wkAppBundleIdentifier} + ], targetName, project); + + this.setConfigurationsFromJsonFile(targetConfigurationJsonPath, target.uuid, targetName, project); + project.addToHeaderSearchPaths(targetPath, target.pbxNativeTarget.productName); + } +} + +$injector.register("iOSWatchAppService", IOSWatchAppService); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2c6008a7fe..46a9da7799 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -5316,9 +5316,8 @@ } }, "nativescript-dev-xcode": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/nativescript-dev-xcode/-/nativescript-dev-xcode-0.1.0.tgz", - "integrity": "sha512-auQd2/qVdOwD/8v+rHKLflYE8jpT4o1wMwtEFkFGp7sewLkBQz7y9pH79oNM7ztyw0gnL/cFAhLT/ViGLWdw6w==", + "version": "https://github.com/NativeScript/nativescript-dev-xcode/tarball/ec70f5d6032a72b65298ad56fa6ec0c4b2ef03a3", + "integrity": "sha512-CIZGo20Mulu/0u5Im6MYfh/gt2jldK3zt/HtWjUBogV/0QVQNKry2ewC6BudpjetUw4otjmqTRlUhY6wXpZ9Bg==", "requires": { "simple-plist": "^1.0.0", "uuid": "^3.3.2" @@ -6066,9 +6065,9 @@ "dev": true }, "pbxproj-dom": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pbxproj-dom/-/pbxproj-dom-1.1.0.tgz", - "integrity": "sha512-ti2ZuXEptfYbkkUweP6sBtLBlRjTcWkxWG450lbJ+PwNMk8nvhhAtV7lIuQPHR29h7hPUHOIvUBAIljIx8ygxg==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pbxproj-dom/-/pbxproj-dom-1.2.0.tgz", + "integrity": "sha512-K2czrWqA68AR0q1UXz5EBi/zoxcljrkO4RSJX0jPnVn3iyE0HYnYOzaEEDYMpueczkT/Vtdm3SCc3NM+12kMaQ==" }, "pend": { "version": "1.2.0", diff --git a/package.json b/package.json index ad975a9576..4ea1444a73 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "minimatch": "3.0.2", "mkdirp": "0.5.1", "mute-stream": "0.0.5", - "nativescript-dev-xcode": "0.1.0", + "nativescript-dev-xcode": "https://github.com/NativeScript/nativescript-dev-xcode/tarball/ec70f5d6032a72b65298ad56fa6ec0c4b2ef03a3", "nativescript-doctor": "1.9.2", "nativescript-preview-sdk": "0.3.4", "open": "0.0.5", @@ -64,7 +64,7 @@ "osenv": "0.1.3", "pacote": "8.1.6", "pako": "1.0.6", - "pbxproj-dom": "1.1.0", + "pbxproj-dom": "1.2.0", "plist": "1.1.0", "plist-merge-patch": "0.1.1", "proper-lockfile": "3.2.0", diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index d01f8df6c0..71c1fd3061 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -159,6 +159,10 @@ function createTestInjector(projectPath: string, projectName: string, xCode?: IX removeExtensions: () => { /* */ }, addExtensionsFromPath: () => Promise.resolve() }); + testInjector.register("iOSWatchAppService", { + removeWatchApp: () => { /* */ }, + addWatchAppFromPath: () => Promise.resolve() + }); return testInjector; } @@ -1064,6 +1068,7 @@ describe("iOS Project Service Signing", () => { stack.push({ targetName, manualSigning }); }, setManualSigningStyleByTargetProductType: () => ({}), + setManualSigningStyleByTargetProductTypesList: () => ({}), setManualSigningStyleByTargetKey: () => ({}) }; }; @@ -1085,6 +1090,7 @@ describe("iOS Project Service Signing", () => { stack.push({ targetName, manualSigning }); }, setManualSigningStyleByTargetProductType: () => ({}), + setManualSigningStyleByTargetProductTypesList: () => ({}), setManualSigningStyleByTargetKey: () => ({}) }; }; @@ -1106,6 +1112,7 @@ describe("iOS Project Service Signing", () => { stack.push({ targetName, manualSigning }); }, setManualSigningStyleByTargetProductType: () => ({}), + setManualSigningStyleByTargetProductTypesList: () => ({}), setManualSigningStyleByTargetKey: () => ({}) }; }; @@ -1296,6 +1303,7 @@ describe("buildProject", () => { getSigning: () => ({}), setAutomaticSigningStyle: () => ({}), setAutomaticSigningStyleByTargetProductType: () => ({}), + setAutomaticSigningStyleByTargetProductTypesList: () => ({}), setAutomaticSigningStyleByTargetKey: () => ({}), save: () => ({}) })