Skip to content

Commit 25bf41a

Browse files
committed
refactor: extract common logic for watch app and extensions
1 parent 7fdb7b7 commit 25bf41a

File tree

5 files changed

+124
-123
lines changed

5 files changed

+124
-123
lines changed

lib/definitions/project.d.ts

+27-2
Original file line numberDiff line numberDiff line change
@@ -592,17 +592,42 @@ interface IIOSExtensionsService {
592592
removeExtensions(options: IRemoveExtensionsOptions): void;
593593
}
594594

595-
interface IAddExtensionsFromPathOptions {
596-
extensionsFolderPath: string;
595+
interface IIOSNativeTargetServiceBase {
596+
}
597+
598+
/**
599+
* Describes a service used to add and remove iOS extension
600+
*/
601+
interface IIOSExtensionsService {
602+
addExtensionsFromPath(options: IAddExtensionsFromPathOptions): Promise<boolean>;
603+
removeExtensions(options: IRemoveExtensionsOptions): void;
604+
}
605+
606+
interface IIOSWatchAppService {
607+
addWatchAppFromPath(options: IAddWatchAppFromPathOptions): Promise<boolean>;
608+
removeWatchApp(options: IRemoveWatchAppOptions): void;
609+
}
610+
611+
interface IAddTargetFromPathOptions {
597612
projectData: IProjectData;
598613
platformData: IPlatformData;
599614
pbxProjPath: string;
600615
}
601616

617+
interface IAddExtensionsFromPathOptions extends IAddTargetFromPathOptions {
618+
extensionsFolderPath: string;
619+
}
620+
621+
interface IAddWatchAppFromPathOptions extends IAddTargetFromPathOptions {
622+
watchAppFolderPath: string;
623+
}
624+
602625
interface IRemoveExtensionsOptions {
603626
pbxProjPath: string
604627
}
605628

629+
interface IRemoveWatchAppOptions extends IRemoveExtensionsOptions{}
630+
606631
interface IRubyFunction {
607632
functionName: string;
608633
functionParameters?: string;

lib/services/ios-extensions-service.ts

+14-44
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as path from "path";
2+
import { NativeTargetServiceBase } from "./ios-native-target-service-base";
23

3-
export class IOSExtensionsService implements IIOSExtensionsService {
4-
constructor(private $fs: IFileSystem,
5-
private $pbxprojDomXcode: IPbxprojDomXcode,
6-
private $xcode: IXcode) {
4+
export class IOSExtensionsService extends NativeTargetServiceBase implements IIOSExtensionsService {
5+
constructor(protected $fs: IFileSystem,
6+
protected $pbxprojDomXcode: IPbxprojDomXcode,
7+
protected $xcode: IXcode) {
8+
super($fs, $pbxprojDomXcode, $xcode);
79
}
810

911
public async addExtensionsFromPath({extensionsFolderPath, projectData, platformData, pbxProjPath}: IAddExtensionsFromPathOptions): Promise<boolean> {
@@ -22,29 +24,20 @@ export class IOSExtensionsService implements IIOSExtensionsService {
2224
return stats.isDirectory() && !fileName.startsWith(".");
2325
})
2426
.forEach(extensionFolder => {
25-
const targetUuid = this.addExtensionToProject(extensionsFolderPath, extensionFolder, project, projectData, platformData);
26-
targetUuids.push(targetUuid);
27+
const target = this.addTargetToProject(extensionsFolderPath, extensionFolder, 'app_extension', project, platformData);
28+
this.configureTarget(extensionFolder, path.join(extensionsFolderPath, extensionFolder), target, project, projectData);
29+
targetUuids.push(target.uuid);
2730
addedExtensions = true;
2831
});
2932

3033
this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true}));
31-
this.prepareExtensionSigning(targetUuids, projectData, pbxProjPath);
34+
this.prepareSigning(targetUuids, projectData, pbxProjPath);
3235

3336
return addedExtensions;
3437
}
3538

36-
private addExtensionToProject(extensionsFolderPath: string, extensionFolder: string, project: IXcode.project, projectData: IProjectData, platformData: IPlatformData): string {
37-
const extensionPath = path.join(extensionsFolderPath, extensionFolder);
38-
const extensionRelativePath = path.relative(platformData.projectRoot, extensionPath);
39-
const files = this.$fs.readDirectory(extensionPath)
40-
.filter(filePath => !filePath.startsWith("."))
41-
.map(filePath => path.join(extensionPath, filePath));
42-
const target = project.addTarget(extensionFolder, 'app_extension', extensionRelativePath);
43-
project.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', target.uuid);
44-
project.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid);
45-
project.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid);
46-
47-
const extJsonPath = path.join(extensionsFolderPath, extensionFolder, "extension.json");
39+
private configureTarget(extensionName: string, extensionPath: string, target: IXcode.target, project: IXcode.project, projectData: IProjectData) {
40+
const extJsonPath = path.join(extensionPath, "extension.json");
4841
if (this.$fs.exists(extJsonPath)) {
4942
const extensionJson = this.$fs.readJson(extJsonPath);
5043
_.forEach(extensionJson.frameworks, framework => {
@@ -58,31 +51,8 @@ export class IOSExtensionsService implements IIOSExtensionsService {
5851
}
5952
}
6053

61-
project.addPbxGroup(files, extensionFolder, extensionPath, null, { isMain: true, target: target.uuid, filesRelativeToProject: true });
62-
project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", `${projectData.projectIdentifiers.ios}.${extensionFolder}`, "Debug", extensionFolder);
63-
project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", `${projectData.projectIdentifiers.ios}.${extensionFolder}`, "Release", extensionFolder);
64-
project.addToHeaderSearchPaths(extensionPath, target.pbxNativeTarget.productName);
65-
66-
return target.uuid;
67-
}
68-
69-
private prepareExtensionSigning(targetUuids: string[], projectData:IProjectData, projectPath: string) {
70-
const xcode = this.$pbxprojDomXcode.Xcode.open(projectPath);
71-
const signing = xcode.getSigning(projectData.projectName);
72-
if (signing !== undefined) {
73-
_.forEach(targetUuids, targetUuid => {
74-
if (signing.style === "Automatic") {
75-
xcode.setAutomaticSigningStyleByTargetKey(targetUuid, signing.team);
76-
} else {
77-
for (const config in signing.configurations) {
78-
const signingConfiguration = signing.configurations[config];
79-
xcode.setManualSigningStyleByTargetKey(targetUuid, signingConfiguration);
80-
break;
81-
}
82-
}
83-
});
84-
}
85-
xcode.save();
54+
project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", `${projectData.projectIdentifiers.ios}.${extensionName}`, "Debug", extensionName);
55+
project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", `${projectData.projectIdentifiers.ios}.${extensionName}`, "Release", extensionName);
8656
}
8757

8858
public removeExtensions({pbxProjPath}: IRemoveExtensionsOptions): void {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as path from "path";
2+
3+
export abstract class NativeTargetServiceBase implements IIOSNativeTargetServiceBase {
4+
constructor(protected $fs: IFileSystem,
5+
protected $pbxprojDomXcode: IPbxprojDomXcode,
6+
protected $xcode: IXcode) {
7+
}
8+
9+
protected addTargetToProject(extensionsFolderPath: string, extensionFolder: string, targetType: string, project: IXcode.project, platformData: IPlatformData, parentTarget?: string): IXcode.target {
10+
const extensionPath = path.join(extensionsFolderPath, extensionFolder);
11+
const extensionRelativePath = path.relative(platformData.projectRoot, extensionPath);
12+
const files = this.$fs.readDirectory(extensionPath)
13+
.filter(filePath => !filePath.startsWith("."))
14+
.map(filePath => path.join(extensionPath, filePath));
15+
const target = project.addTarget(extensionFolder, targetType, extensionRelativePath, parentTarget);
16+
project.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', target.uuid);
17+
project.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid);
18+
project.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid);
19+
20+
project.addPbxGroup(files, extensionFolder, extensionPath, null, { isMain: true, target: target.uuid, filesRelativeToProject: true });
21+
project.addToHeaderSearchPaths(extensionPath, target.pbxNativeTarget.productName);
22+
return target;
23+
}
24+
25+
protected prepareSigning(targetUuids: string[], projectData:IProjectData, projectPath: string) {
26+
const xcode = this.$pbxprojDomXcode.Xcode.open(projectPath);
27+
const signing = xcode.getSigning(projectData.projectName);
28+
if (signing !== undefined) {
29+
_.forEach(targetUuids, targetUuid => {
30+
if (signing.style === "Automatic") {
31+
xcode.setAutomaticSigningStyleByTargetKey(targetUuid, signing.team);
32+
} else {
33+
for (const config in signing.configurations) {
34+
const signingConfiguration = signing.configurations[config];
35+
xcode.setManualSigningStyleByTargetKey(targetUuid, signingConfiguration);
36+
break;
37+
}
38+
}
39+
});
40+
}
41+
xcode.save();
42+
}
43+
}

lib/services/ios-project-service.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
5454
private $sysInfo: ISysInfo,
5555
private $xcconfigService: IXcconfigService,
5656
private $iOSExtensionsService: IIOSExtensionsService,
57-
private $iOSWatchAppService: IIOSExtensionsService) {
57+
private $iOSWatchAppService: IIOSWatchAppService) {
5858
super($fs, $projectDataService);
5959
}
6060

@@ -785,17 +785,18 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
785785

786786
this.savePbxProj(project, projectData);
787787
}
788-
788+
789789
const platformData = this.getPlatformData(projectData);
790-
const resourlcesDirectoryPath = projectData.getAppResourcesDirectoryPath();
790+
const resourcesDirectoryPath = projectData.getAppResourcesDirectoryPath();
791791
const pbxProjPath = this.getPbxProjPath(projectData);
792792
const resourcesNativeCodePath = path.join(
793793
projectData.getAppResourcesDirectoryPath(),
794794
platformData.normalizedPlatformName,
795795
constants.NATIVE_SOURCE_FOLDER
796796
);
797797
await this.prepareNativeSourceCode(constants.TNS_NATIVE_SOURCE_GROUP_NAME, resourcesNativeCodePath, projectData);
798-
await this.$iOSWatchAppService.addExtensionsFromPath({ extensionsFolderPath: path.join(resourlcesDirectoryPath, platformData.normalizedPlatformName), projectData, platformData, pbxProjPath });
798+
this.$iOSWatchAppService.removeWatchApp({ pbxProjPath });
799+
await this.$iOSWatchAppService.addWatchAppFromPath({ watchAppFolderPath: path.join(resourcesDirectoryPath, platformData.normalizedPlatformName), projectData, platformData, pbxProjPath });
799800

800801
}
801802

@@ -809,6 +810,8 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
809810
// src folder should not be copied as the pbxproject will have references to its files
810811
this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, constants.NATIVE_SOURCE_FOLDER));
811812
this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, constants.NATIVE_EXTENSION_FOLDER));
813+
this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, "watchapp"));
814+
this.$fs.deleteDirectory(path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, "watchextension"));
812815

813816
this.$fs.deleteDirectory(this.getAppResourcesDestinationDirectoryPath(projectData));
814817
}

lib/services/ios-watch-app-service.ts

+33-73
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import * as path from "path";
2+
import { NativeTargetServiceBase } from "./ios-native-target-service-base";
23

3-
export class IOSWatchAppService implements IIOSExtensionsService {
4-
constructor(private $fs: IFileSystem,
5-
private $pbxprojDomXcode: IPbxprojDomXcode,
6-
private $xcode: IXcode) {
4+
export class IOSWatchAppService extends NativeTargetServiceBase implements IIOSWatchAppService {
5+
constructor(protected $fs: IFileSystem,
6+
protected $pbxprojDomXcode: IPbxprojDomXcode,
7+
protected $xcode: IXcode) {
8+
super($fs, $pbxprojDomXcode, $xcode);
79
}
810

9-
public async addExtensionsFromPath({extensionsFolderPath, projectData, platformData, pbxProjPath}: IAddExtensionsFromPathOptions): Promise<boolean> {
11+
public async addWatchAppFromPath({watchAppFolderPath, projectData, platformData, pbxProjPath}: IAddWatchAppFromPathOptions): Promise<boolean> {
1012
const targetUuids: string[] = [];
1113
let addedExtensions = false;
12-
if (!this.$fs.exists(extensionsFolderPath)) {
14+
if (!this.$fs.exists(watchAppFolderPath)) {
1315
return false;
1416
}
1517
const project = new this.$xcode.project(pbxProjPath);
16-
const appPath = path.join(extensionsFolderPath, "watchapp");
17-
const extensionPath = path.join(extensionsFolderPath, "watchextension");
18+
const appPath = path.join(watchAppFolderPath, "watchapp");
19+
const extensionPath = path.join(watchAppFolderPath, "watchextension");
1820
project.parseSync();
1921
const appFolder = this.$fs.readDirectory(appPath)
2022
.filter(fileName => {
@@ -32,84 +34,42 @@ export class IOSWatchAppService implements IIOSExtensionsService {
3234
return stats.isDirectory() && !fileName.startsWith(".");
3335
})[0];
3436

35-
let targetUuid = this.addExtensionToProject(appPath, appFolder, project, projectData, platformData, "watch_app", `${projectData.projectIdentifiers.ios}.watchkitapp`, project.getFirstTarget().uuid);
36-
targetUuids.push(targetUuid);
37-
targetUuid = this.addExtensionToProject(extensionPath, extensionFolder, project, projectData, platformData, "watch_extension", `${projectData.projectIdentifiers.ios}.watchkitapp.watchkitextension`, targetUuid);
38-
targetUuids.push(targetUuid);
39-
addedExtensions = true;
37+
const watchApptarget = this.addTargetToProject(appPath, appFolder, "watch_app", project, platformData, project.getFirstTarget().uuid);
38+
this.configureTarget(appFolder, path.join(appPath, appFolder), `${projectData.projectIdentifiers.ios}.watchkitapp`, watchApptarget, project);
39+
targetUuids.push(watchApptarget.uuid);
40+
const watchExtensionTarget = this.addTargetToProject(extensionPath, extensionFolder, "watch_extension", project, platformData, watchApptarget.uuid);
41+
this.configureTarget(extensionFolder, path.join(extensionPath, extensionFolder), `${projectData.projectIdentifiers.ios}.watchkitapp.watchkitextension`, watchExtensionTarget, project);
42+
targetUuids.push(watchExtensionTarget.uuid);
43+
addedExtensions = true;
4044

4145
this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true}));
42-
this.prepareExtensionSigning(targetUuids, projectData, pbxProjPath);
46+
this.prepareSigning(targetUuids, projectData, pbxProjPath);
4347

4448
return addedExtensions;
4549
}
4650

47-
private addExtensionToProject(extensionsFolderPath: string, extensionFolder: string, project: IXcode.project, projectData: IProjectData, platformData: IPlatformData, targetType: string, identifier: string, parentTarget: string): string {
48-
const extensionPath = path.join(extensionsFolderPath, extensionFolder);
49-
const extensionRelativePath = path.relative(platformData.projectRoot, extensionPath);
50-
const files = this.$fs.readDirectory(extensionPath)
51-
.filter(filePath => !filePath.startsWith("."))
52-
.map(filePath => path.join(extensionPath, filePath));
53-
const target = project.addTarget(extensionFolder, targetType, extensionRelativePath, parentTarget);
54-
project.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', target.uuid);
55-
project.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid);
56-
project.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid);
57-
58-
const extJsonPath = path.join(extensionsFolderPath, extensionFolder, "extension.json");
59-
if (this.$fs.exists(extJsonPath)) {
60-
const extensionJson = this.$fs.readJson(extJsonPath);
61-
_.forEach(extensionJson.frameworks, framework => {
62-
project.addFramework(
63-
framework,
64-
{ target: target.uuid }
65-
);
66-
});
67-
if (extensionJson.assetcatalogCompilerAppiconName) {
68-
project.addToBuildSettings("ASSETCATALOG_COMPILER_APPICON_NAME", extensionJson.assetcatalogCompilerAppiconName, target.uuid);
69-
}
70-
}
51+
private configureTarget(targetName: string, targetPath: string, identifier: string, target: IXcode.target, project: IXcode.project) {
7152
const identifierParts = identifier.split(".");
7253
identifierParts.pop();
7354
const wkAppBundleIdentifier = identifierParts.join(".");
74-
project.addPbxGroup(files, extensionFolder, extensionPath, null, { isMain: true, target: target.uuid, filesRelativeToProject: true });
75-
project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", identifier, "Debug", extensionFolder);
76-
project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", identifier, "Release", extensionFolder);
77-
project.addBuildProperty("SDKROOT", "watchos", "Debug", extensionFolder);
78-
project.addBuildProperty("SDKROOT", "watchos", "Release", extensionFolder);
79-
project.addBuildProperty("TARGETED_DEVICE_FAMILY", 4, "Debug", extensionFolder);
80-
project.addBuildProperty("TARGETED_DEVICE_FAMILY", 4, "Release", extensionFolder);
81-
project.addBuildProperty("WATCHOS_DEPLOYMENT_TARGET", 4.1, "Debug", extensionFolder);
82-
project.addBuildProperty("WATCHOS_DEPLOYMENT_TARGET", 4.1, "Release", extensionFolder);
83-
project.addBuildProperty("WK_APP_BUNDLE_IDENTIFIER", wkAppBundleIdentifier, "Debug", extensionFolder);
84-
project.addBuildProperty("WK_APP_BUNDLE_IDENTIFIER", wkAppBundleIdentifier, "Release", extensionFolder);
85-
project.addToHeaderSearchPaths(extensionPath, target.pbxNativeTarget.productName);
86-
87-
return target.uuid;
88-
}
89-
90-
private prepareExtensionSigning(targetUuids: string[], projectData:IProjectData, projectPath: string) {
91-
const xcode = this.$pbxprojDomXcode.Xcode.open(projectPath);
92-
const signing = xcode.getSigning(projectData.projectName);
93-
if (signing !== undefined) {
94-
_.forEach(targetUuids, targetUuid => {
95-
if (signing.style === "Automatic") {
96-
xcode.setAutomaticSigningStyleByTargetKey(targetUuid, signing.team);
97-
} else {
98-
for (const config in signing.configurations) {
99-
const signingConfiguration = signing.configurations[config];
100-
xcode.setManualSigningStyleByTargetKey(targetUuid, signingConfiguration);
101-
break;
102-
}
103-
}
104-
});
105-
}
106-
xcode.save();
55+
project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", identifier, "Debug", targetName);
56+
project.addBuildProperty("PRODUCT_BUNDLE_IDENTIFIER", identifier, "Release", targetName);
57+
project.addBuildProperty("SDKROOT", "watchos", "Debug", targetName);
58+
project.addBuildProperty("SDKROOT", "watchos", "Release", targetName);
59+
project.addBuildProperty("TARGETED_DEVICE_FAMILY", 4, "Debug", targetName);
60+
project.addBuildProperty("TARGETED_DEVICE_FAMILY", 4, "Release", targetName);
61+
project.addBuildProperty("WATCHOS_DEPLOYMENT_TARGET", 4.1, "Debug", targetName);
62+
project.addBuildProperty("WATCHOS_DEPLOYMENT_TARGET", 4.1, "Release", targetName);
63+
project.addBuildProperty("WK_APP_BUNDLE_IDENTIFIER", wkAppBundleIdentifier, "Debug", targetName);
64+
project.addBuildProperty("WK_APP_BUNDLE_IDENTIFIER", wkAppBundleIdentifier, "Release", targetName);
65+
project.addToHeaderSearchPaths(targetPath, target.pbxNativeTarget.productName);
10766
}
10867

109-
public removeExtensions({pbxProjPath}: IRemoveExtensionsOptions): void {
68+
public removeWatchApp({pbxProjPath}: IRemoveWatchAppOptions): void {
11069
const project = new this.$xcode.project(pbxProjPath);
11170
project.parseSync();
112-
project.removeTargetsByProductType("com.apple.product-type.app-extension");
71+
project.removeTargetsByProductType("com.apple.product-type.application.watchapp2");
72+
project.removeTargetsByProductType("com.apple.product-type.watchkit2-extension");
11373
this.$fs.writeFile(pbxProjPath, project.writeSync({omitEmptyValues: true}));
11474
}
11575
}

0 commit comments

Comments
 (0)