Skip to content

Native source code and a Podfile without a plugin #4282

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 9 commits into from
Jan 17, 2019
3 changes: 3 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ require("colors");
export const APP_FOLDER_NAME = "app";
export const APP_RESOURCES_FOLDER_NAME = "App_Resources";
export const PROJECT_FRAMEWORK_FOLDER_NAME = "framework";
export const NS_BASE_PODFILE = "NSPodfileBase";
export const NATIVESCRIPT_KEY_NAME = "nativescript";
export const NODE_MODULES_FOLDER_NAME = "node_modules";
export const TNS_MODULES_FOLDER_NAME = "tns_modules";
Expand Down Expand Up @@ -43,6 +44,8 @@ export const DEPENDENCIES_JSON_NAME = "dependencies.json";
export const APK_EXTENSION_NAME = ".apk";
export const AAB_EXTENSION_NAME = ".aab";
export const HASHES_FILE_NAME = ".nshashes";
export const TNS_NATIVE_SOURCE_GROUP_NAME = "TNSNativeSource";
export const NATIVE_SOURCE_FOLDER = "src";

export class PackageVersion {
static NEXT = "next";
Expand Down
24 changes: 17 additions & 7 deletions lib/definitions/project.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ interface IProjectData extends ICreateProjectData {
gradleFilesDirectoryPath: string;
infoPlistPath: string;
buildXcconfigPath: string;
podfilePath: string;
/**
* Defines if the project is a code sharing one.
* Value is true when project has nsconfig.json and it has `shared: true` in it.
Expand Down Expand Up @@ -409,7 +410,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS
getAppResourcesDestinationDirectoryPath(projectData: IProjectData): string;

cleanDeviceTempFolder(deviceIdentifier: string, projectData: IProjectData): Promise<void>;
processConfigurationFilesFromAppResources(release: boolean, projectData: IProjectData): Promise<void>;
processConfigurationFilesFromAppResources(projectData: IProjectData, opts: { release: boolean, installPods: boolean }): Promise<void>;

/**
* Ensures there is configuration file (AndroidManifest.xml, Info.plist) in app/App_Resources.
Expand Down Expand Up @@ -449,7 +450,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS
* Traverse through the production dependencies and find plugins that need build/rebuild
*/
checkIfPluginsNeedBuild(projectData: IProjectData): Promise<Array<any>>;

/**
* Get the deployment target's version
* Currently implemented only for iOS -> returns the value of IPHONEOS_DEPLOYMENT_TARGET property from xcconfig file
Expand Down Expand Up @@ -484,21 +485,30 @@ interface ICocoaPodsService {

/**
* Prepares the Podfile content of a plugin and merges it in the project's Podfile.
* @param {IPluginData} pluginData Information about the plugin.
* @param {string} moduleName The module which the Podfile is from.
* @param {string} podfilePath The path to the podfile.
* @param {IProjectData} projectData Information about the project.
* @param {string} nativeProjectPath Path to the native Xcode project.
* @returns {Promise<void>}
*/
applyPluginPodfileToProject(pluginData: IPluginData, projectData: IProjectData, nativeProjectPath: string): Promise<void>;
applyPodfileToProject(moduleName: string, podfilePath: string, projectData: IProjectData, nativeProjectPath: string): Promise<void>;

/**
* Removes plugins Podfile content from the project.
* Gives the path to the plugin's Podfile.
* @param {IPluginData} pluginData Information about the plugin.
* @param {IProjectData} projectData Information about the project.
* @returns {string} Path to plugin's Podfile
*/
getPluginPodfilePath(pluginData: IPluginData): string;

/**
* Removes plugins Podfile content from the project.
* @param {string} moduleName The name of the module.
* @param {string} podfilePath The path to the module's Podfile.
* @param {string} projectData Information about the project.
* @param {string} nativeProjectPath Path to the native Xcode project.
* @returns {void}
*/
removePluginPodfileFromProject(pluginData: IPluginData, projectData: IProjectData, nativeProjectPath: string): void;
removePodfileFromProject(moduleName: string, podfilePath: string, projectData: IProjectData, nativeProjectPath: string): void;

/**
* Gives the path to project's Podfile.
Expand Down
2 changes: 2 additions & 0 deletions lib/project-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export class ProjectData implements IProjectData {
public appGradlePath: string;
public gradleFilesDirectoryPath: string;
public buildXcconfigPath: string;
public podfilePath: string;
public isShared: boolean;

constructor(private $fs: IFileSystem,
Expand Down Expand Up @@ -132,6 +133,7 @@ export class ProjectData implements IProjectData {
this.appGradlePath = path.join(this.gradleFilesDirectoryPath, constants.APP_GRADLE_FILE_NAME);
this.infoPlistPath = path.join(this.appResourcesDirectoryPath, this.$devicePlatformsConstants.iOS, constants.INFO_PLIST_FILE_NAME);
this.buildXcconfigPath = path.join(this.appResourcesDirectoryPath, this.$devicePlatformsConstants.iOS, constants.BUILD_XCCONFIG_FILE_NAME);
this.podfilePath = path.join(this.appResourcesDirectoryPath, this.$devicePlatformsConstants.iOS, constants.PODFILE_NAME);
this.isShared = !!(this.nsConfig && this.nsConfig.shared);
return;
}
Expand Down
37 changes: 18 additions & 19 deletions lib/services/cocoapods-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,39 +52,38 @@ export class CocoaPodsService implements ICocoaPodsService {
return podInstallResult;
}

public async applyPluginPodfileToProject(pluginData: IPluginData, projectData: IProjectData, nativeProjectPath: string): Promise<void> {
const pluginPodFilePath = this.getPluginPodfilePath(pluginData);
if (!this.$fs.exists(pluginPodFilePath)) {
public async applyPodfileToProject(moduleName: string, podfilePath: string, projectData: IProjectData, nativeProjectPath: string): Promise<void> {
if (!this.$fs.exists(podfilePath)) {
this.removePodfileFromProject(moduleName, podfilePath, projectData, nativeProjectPath);
return;
}

const { pluginPodfileContent, replacedFunctions } = this.buildPodfileContent(pluginPodFilePath, pluginData.name);
const { podfileContent, replacedFunctions } = this.buildPodfileContent(podfilePath, moduleName);
const pathToProjectPodfile = this.getProjectPodfilePath(nativeProjectPath);
const projectPodfileContent = this.$fs.exists(pathToProjectPodfile) ? this.$fs.readText(pathToProjectPodfile).trim() : "";

if (projectPodfileContent.indexOf(pluginPodfileContent) === -1) {
if (projectPodfileContent.indexOf(podfileContent) === -1) {
// Remove old occurences of the plugin from the project's Podfile.
this.removePluginPodfileFromProject(pluginData, projectData, nativeProjectPath);
this.removePodfileFromProject(moduleName, podfilePath, projectData, nativeProjectPath);
let finalPodfileContent = this.$fs.exists(pathToProjectPodfile) ? this.getPodfileContentWithoutTarget(projectData, this.$fs.readText(pathToProjectPodfile)) : "";

if (pluginPodfileContent.indexOf(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME) !== -1) {
finalPodfileContent = this.addPostInstallHook(replacedFunctions, finalPodfileContent, pluginPodfileContent);
if (podfileContent.indexOf(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME) !== -1) {
finalPodfileContent = this.addPostInstallHook(replacedFunctions, finalPodfileContent, podfileContent);
}

finalPodfileContent = `${pluginPodfileContent}${EOL}${finalPodfileContent}`;
finalPodfileContent = `${podfileContent}${EOL}${finalPodfileContent}`;
this.saveProjectPodfile(projectData, finalPodfileContent, nativeProjectPath);
}
}

public removePluginPodfileFromProject(pluginData: IPluginData, projectData: IProjectData, projectRoot: string): void {
const pluginPodfilePath = this.getPluginPodfilePath(pluginData);
public removePodfileFromProject(moduleName: string, podfilePath: string, projectData: IProjectData, projectRoot: string): void {

if (this.$fs.exists(pluginPodfilePath) && this.$fs.exists(this.getProjectPodfilePath(projectRoot))) {
if (this.$fs.exists(this.getProjectPodfilePath(projectRoot))) {
let projectPodFileContent = this.$fs.readText(this.getProjectPodfilePath(projectRoot));
// Remove the data between #Begin Podfile and #EndPodfile
const regExpToRemove = new RegExp(`${this.getPluginPodfileHeader(pluginPodfilePath)}[\\s\\S]*?${this.getPluginPodfileEnd()}`, "mg");
const regExpToRemove = new RegExp(`${this.getPluginPodfileHeader(podfilePath)}[\\s\\S]*?${this.getPluginPodfileEnd()}`, "mg");
projectPodFileContent = projectPodFileContent.replace(regExpToRemove, "");
projectPodFileContent = this.removePostInstallHook(pluginData, projectPodFileContent);
projectPodFileContent = this.removePostInstallHook(moduleName, projectPodFileContent);

const defaultPodfileBeginning = this.getPodfileHeader(projectData.projectName);
const defaultContentWithPostInstallHook = `${defaultPodfileBeginning}${EOL}${this.getPostInstallHookHeader()}end${EOL}end`;
Expand All @@ -98,7 +97,7 @@ export class CocoaPodsService implements ICocoaPodsService {
}
}

private getPluginPodfilePath(pluginData: IPluginData): string {
public getPluginPodfilePath(pluginData: IPluginData): string {
const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath(PluginNativeDirNames.iOS);
const pluginPodFilePath = path.join(pluginPlatformsFolderPath, PODFILE_NAME);
return pluginPodFilePath;
Expand Down Expand Up @@ -157,8 +156,8 @@ export class CocoaPodsService implements ICocoaPodsService {
this.$fs.writeFile(projectPodfilePath, contentToWrite);
}

private removePostInstallHook(pluginData: IPluginData, projectPodFileContent: string): string {
const regExp = new RegExp(`^.*?${this.getHookBasicFuncNameForPlugin(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME, pluginData.name)}.*?$\\r?\\n`, "gm");
private removePostInstallHook(moduleName: string, projectPodFileContent: string): string {
const regExp = new RegExp(`^.*?${this.getHookBasicFuncNameForPlugin(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME, moduleName)}.*?$\\r?\\n`, "gm");
projectPodFileContent = projectPodFileContent.replace(regExp, "");
return projectPodFileContent;
}
Expand Down Expand Up @@ -206,12 +205,12 @@ export class CocoaPodsService implements ICocoaPodsService {
return `${CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME} do |${CocoaPodsService.INSTALLER_BLOCK_PARAMETER_NAME}|${EOL}`;
}

private buildPodfileContent(pluginPodFilePath: string, pluginName: string): { pluginPodfileContent: string, replacedFunctions: IRubyFunction[] } {
private buildPodfileContent(pluginPodFilePath: string, pluginName: string): { podfileContent: string, replacedFunctions: IRubyFunction[] } {
const pluginPodfileContent = this.$fs.readText(pluginPodFilePath);
const { replacedContent, newFunctions: replacedFunctions } = this.replaceHookContent(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME, pluginPodfileContent, pluginName);

return {
pluginPodfileContent: `${this.getPluginPodfileHeader(pluginPodFilePath)}${EOL}${replacedContent}${EOL}${this.getPluginPodfileEnd()}`,
podfileContent: `${this.getPluginPodfileHeader(pluginPodFilePath)}${EOL}${replacedContent}${EOL}${this.getPluginPodfileEnd()}`,
replacedFunctions
};
}
Expand Down
34 changes: 26 additions & 8 deletions lib/services/ios-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -771,28 +771,39 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
this.$logger.trace(`Images to remove from xcode project: ${imagesToRemove.join(", ")}`);
_.each(imagesToRemove, image => project.removeResourceFile(path.join(this.getAppResourcesDestinationDirectoryPath(projectData), image)));

await this.prepareNativeSourceCode(constants.TNS_NATIVE_SOURCE_GROUP_NAME, path.join(projectData.getAppResourcesDirectoryPath(), constants.APP_RESOURCES_FOLDER_NAME, this.getPlatformData(projectData).normalizedPlatformName, constants.NATIVE_SOURCE_FOLDER), projectData);

this.savePbxProj(project, projectData);
}

}

public prepareAppResources(appResourcesDirectoryPath: string, projectData: IProjectData): void {
const platformFolder = path.join(appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName);
const filterFile = (filename: string) => this.$fs.deleteFile(path.join(platformFolder, filename));

filterFile(this.getPlatformData(projectData).configurationFileName);
filterFile(constants.PODFILE_NAME);

// 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(this.getAppResourcesDestinationDirectoryPath(projectData));
}

public async processConfigurationFilesFromAppResources(release: boolean, projectData: IProjectData): Promise<void> {
await this.mergeInfoPlists({ release }, projectData);
public async processConfigurationFilesFromAppResources(projectData: IProjectData, opts: { release: boolean, installPods: boolean }): Promise<void> {
await this.mergeInfoPlists({ release: opts.release }, projectData);
await this.$iOSEntitlementsService.merge(projectData);
await this.mergeProjectXcconfigFiles(release, projectData);
await this.mergeProjectXcconfigFiles(opts.release, projectData);
for (const pluginData of await this.getAllInstalledPlugins(projectData)) {
await this.$pluginVariablesService.interpolatePluginVariables(pluginData, this.getPlatformData(projectData).configurationFilePath, projectData.projectDir);
}

this.$pluginVariablesService.interpolateAppIdentifier(this.getPlatformData(projectData).configurationFilePath, projectData.projectIdentifiers.ios);

if (opts.installPods) {
await this.installPodsIfAny(projectData);
}
}

private getInfoPlistPath(projectData: IProjectData): string {
Expand Down Expand Up @@ -955,7 +966,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
await this.prepareStaticLibs(pluginPlatformsFolderPath, pluginData, projectData);

const projectRoot = this.getPlatformData(projectData).projectRoot;
await this.$cocoapodsService.applyPluginPodfileToProject(pluginData, projectData, projectRoot);
await this.$cocoapodsService.applyPodfileToProject(pluginData.name, this.$cocoapodsService.getPluginPodfilePath(pluginData), projectData, projectRoot);
}

public async removePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise<void> {
Expand All @@ -966,12 +977,17 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
this.removeStaticLibs(pluginPlatformsFolderPath, pluginData, projectData);
const projectRoot = this.getPlatformData(projectData).projectRoot;

this.$cocoapodsService.removePluginPodfileFromProject(pluginData, projectData, projectRoot);
this.$cocoapodsService.removePodfileFromProject(pluginData.name, this.$cocoapodsService.getPluginPodfilePath(pluginData), projectData, projectRoot);
}

public async afterPrepareAllPlugins(projectData: IProjectData): Promise<void> {
await this.installPodsIfAny(projectData);
}

public async installPodsIfAny(projectData: IProjectData): Promise<void> {
const projectRoot = this.getPlatformData(projectData).projectRoot;
if (this.$fs.exists(this.$cocoapodsService.getProjectPodfilePath(projectRoot))) {
const mainPodfilePath = path.join(projectData.appResourcesDirectoryPath, this.getPlatformData(projectData).normalizedPlatformName, constants.PODFILE_NAME);
if (this.$fs.exists(this.$cocoapodsService.getProjectPodfilePath(projectRoot)) || this.$fs.exists(mainPodfilePath)) {
const xcodeProjPath = this.getXcodeprojPath(projectData);
const xcuserDataPath = path.join(xcodeProjPath, "xcuserdata");
const sharedDataPath = path.join(xcodeProjPath, "xcshareddata");
Expand All @@ -984,6 +1000,8 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
await this.$childProcess.exec(createSchemeRubyScript, { cwd: this.getPlatformData(projectData).projectRoot });
}

await this.$cocoapodsService.applyPodfileToProject(constants.NS_BASE_PODFILE, mainPodfilePath, projectData, this.getPlatformData(projectData).projectRoot);

await this.$cocoapodsService.executePodInstall(projectRoot, xcodeProjPath);
}
}
Expand Down Expand Up @@ -1094,9 +1112,9 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
this.$fs.rename(path.join(fileRootLocation, oldFileName), path.join(fileRootLocation, newFileName));
}

private async prepareNativeSourceCode(pluginName: string, pluginPlatformsFolderPath: string, projectData: IProjectData): Promise<void> {
private async prepareNativeSourceCode(groupName: string, sourceFolderPath: string, projectData: IProjectData): Promise<void> {
const project = this.createPbxProj(projectData);
const group = this.getRootGroup(pluginName, pluginPlatformsFolderPath);
const group = this.getRootGroup(groupName, sourceFolderPath);
project.addPbxGroup(group.files, group.name, group.path, null, { isMain: true });
project.addToHeaderSearchPaths(group.path);
this.savePbxProj(project, projectData);
Expand Down
7 changes: 5 additions & 2 deletions lib/services/prepare-platform-native-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme
await config.platformData.platformProjectService.prepareProject(config.projectData, config.platformSpecificData);
}

if (!config.changesInfo || config.changesInfo.modulesChanged) {
const shouldPrepareModules = !config.changesInfo || config.changesInfo.modulesChanged;

if (shouldPrepareModules) {
await this.$pluginsService.validate(config.platformData, config.projectData);

const appDestinationDirectoryPath = path.join(config.platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
Expand All @@ -60,7 +62,8 @@ export class PreparePlatformNativeService extends PreparePlatformService impleme
}

if (!config.changesInfo || config.changesInfo.configChanged || config.changesInfo.modulesChanged) {
await config.platformData.platformProjectService.processConfigurationFilesFromAppResources(config.appFilesUpdaterOptions.release, config.projectData);
// Passing !shouldPrepareModules` we assume that if the node modules are prepared base Podfile content is added and `pod install` is executed.
await config.platformData.platformProjectService.processConfigurationFilesFromAppResources(config.projectData, {release:config.appFilesUpdaterOptions.release, installPods: !shouldPrepareModules});
}

config.platformData.platformProjectService.interpolateConfigurationFile(config.projectData, config.platformSpecificData);
Expand Down
4 changes: 2 additions & 2 deletions test/cocoapods-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ end`,
it(testCase.testCaseDescription, async () => {
mockFileSystem(testInjector, testCase.input, testCase.projectPodfileContent);

await cocoapodsService.applyPluginPodfileToProject(testCase.pluginData || mockPluginData, mockProjectData, nativeProjectPath);
await cocoapodsService.applyPodfileToProject(testCase.pluginData ? testCase.pluginData.name : mockPluginData.name, cocoapodsService.getPluginPodfilePath(testCase.pluginData || mockPluginData), mockProjectData, nativeProjectPath);

assert.deepEqual(changeNewLineCharacter(newPodfileContent), changeNewLineCharacter(testCase.output));
});
Expand Down Expand Up @@ -720,7 +720,7 @@ end`
it(testCase.testCaseDescription, async () => {
mockFileSystem(testInjector, testCase.input, testCase.projectPodfileContent);

cocoapodsService.removePluginPodfileFromProject(mockPluginData, mockProjectData, nativeProjectPath);
cocoapodsService.removePodfileFromProject(mockPluginData.name, cocoapodsService.getPluginPodfilePath(mockPluginData), mockProjectData, nativeProjectPath);

assert.deepEqual(changeNewLineCharacter(newPodfileContent), changeNewLineCharacter(testCase.output));
});
Expand Down
Loading