Skip to content

Kddimitrov/watch app #4568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 7, 2019
1 change: 1 addition & 0 deletions lib/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
19 changes: 19 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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"
}
26 changes: 24 additions & 2 deletions lib/definitions/project.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>;
removeExtensions(options: IRemoveExtensionsOptions): void;
}

interface IIOSWatchAppService {
addWatchAppFromPath(options: IAddWatchAppFromPathOptions): Promise<boolean>;
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;
Expand Down
3 changes: 2 additions & 1 deletion lib/definitions/xcode.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down
85 changes: 21 additions & 64 deletions lib/services/ios-extensions-service.ts
Original file line number Diff line number Diff line change
@@ -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<boolean> {
Expand All @@ -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}));
}
}
Expand Down
96 changes: 96 additions & 0 deletions lib/services/ios-native-target-service-base.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
Loading