diff --git a/lib/constants.ts b/lib/constants.ts index 5837094b12..0d4e7939d2 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -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"; @@ -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"; diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index c2d89f1611..b9f7aa950f 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -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. @@ -409,7 +410,7 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS getAppResourcesDestinationDirectoryPath(projectData: IProjectData): string; cleanDeviceTempFolder(deviceIdentifier: string, projectData: IProjectData): Promise; - processConfigurationFilesFromAppResources(release: boolean, projectData: IProjectData): Promise; + processConfigurationFilesFromAppResources(projectData: IProjectData, opts: { release: boolean, installPods: boolean }): Promise; /** * Ensures there is configuration file (AndroidManifest.xml, Info.plist) in app/App_Resources. @@ -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>; - + /** * Get the deployment target's version * Currently implemented only for iOS -> returns the value of IPHONEOS_DEPLOYMENT_TARGET property from xcconfig file @@ -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} */ - applyPluginPodfileToProject(pluginData: IPluginData, projectData: IProjectData, nativeProjectPath: string): Promise; + applyPodfileToProject(moduleName: string, podfilePath: string, projectData: IProjectData, nativeProjectPath: string): Promise; /** - * 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. diff --git a/lib/project-data.ts b/lib/project-data.ts index 3e2fc860c1..2fe31b0f2f 100644 --- a/lib/project-data.ts +++ b/lib/project-data.ts @@ -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, @@ -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; } diff --git a/lib/services/cocoapods-service.ts b/lib/services/cocoapods-service.ts index 47c0e484e1..5f793a9be8 100644 --- a/lib/services/cocoapods-service.ts +++ b/lib/services/cocoapods-service.ts @@ -52,39 +52,38 @@ export class CocoaPodsService implements ICocoaPodsService { return podInstallResult; } - public async applyPluginPodfileToProject(pluginData: IPluginData, projectData: IProjectData, nativeProjectPath: string): Promise { - const pluginPodFilePath = this.getPluginPodfilePath(pluginData); - if (!this.$fs.exists(pluginPodFilePath)) { + public async applyPodfileToProject(moduleName: string, podfilePath: string, projectData: IProjectData, nativeProjectPath: string): Promise { + 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`; @@ -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; @@ -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; } @@ -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 }; } diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index deeb4552e8..6845651be2 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -771,8 +771,11 @@ 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 { @@ -780,19 +783,27 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f 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 { - await this.mergeInfoPlists({ release }, projectData); + public async processConfigurationFilesFromAppResources(projectData: IProjectData, opts: { release: boolean, installPods: boolean }): Promise { + 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 { @@ -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 { @@ -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 { + await this.installPodsIfAny(projectData); + } + + public async installPodsIfAny(projectData: IProjectData): Promise { 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"); @@ -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); } } @@ -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 { + private async prepareNativeSourceCode(groupName: string, sourceFolderPath: string, projectData: IProjectData): Promise { 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); diff --git a/lib/services/prepare-platform-native-service.ts b/lib/services/prepare-platform-native-service.ts index e1f01e57b7..9f6014ac13 100644 --- a/lib/services/prepare-platform-native-service.ts +++ b/lib/services/prepare-platform-native-service.ts @@ -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); @@ -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); diff --git a/test/cocoapods-service.ts b/test/cocoapods-service.ts index e00ea20393..92fe7f314a 100644 --- a/test/cocoapods-service.ts +++ b/test/cocoapods-service.ts @@ -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)); }); @@ -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)); }); diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index 93c9ab6a5c..61bfcad100 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -73,8 +73,14 @@ function createTestInjector(projectPath: string, projectName: string, xcode?: IX projectPath: projectPath, projectFilePath: path.join(projectPath, "package.json"), projectId: "", - projectIdentifiers: { android: "", ios: "" } + projectIdentifiers: { android: "", ios: "" }, + projectDir: "", + appDirectoryPath: "", + appResourcesDirectoryPath: "" }); + projectData.projectDir = temp.mkdirSync("projectDir"); + projectData.appDirectoryPath = path.join(projectData.projectDir, "app"); + projectData.appResourcesDirectoryPath = path.join(projectData.appDirectoryPath, "App_Resources"); testInjector.register("projectData", projectData); testInjector.register("projectHelper", {}); testInjector.register("xcodeSelectService", {}); @@ -335,6 +341,69 @@ describe("Cocoapods support", () => { if (require("os").platform() !== "darwin") { console.log("Skipping Cocoapods tests. They cannot work on windows"); } else { + it("adds а base Podfile", async () => { + const projectName = "projectDirectory"; + const projectPath = temp.mkdirSync(projectName); + + const testInjector = createTestInjector(projectPath, projectName); + const fs: IFileSystem = testInjector.resolve("fs"); + const cocoapodsService = testInjector.resolve("cocoapodsService"); + + const packageJsonData = { + "name": "myProject", + "version": "0.1.0", + "nativescript": { + "id": "org.nativescript.myProject", + "tns-ios": { + "version": "1.0.0" + } + } + }; + fs.writeJson(path.join(projectPath, "package.json"), packageJsonData); + + const platformsFolderPath = path.join(projectPath, "platforms", "ios"); + fs.createDirectory(platformsFolderPath); + + const iOSProjectService = testInjector.resolve("iOSProjectService"); + iOSProjectService.createPbxProj = () => { + return { + updateBuildProperty: () => { return {}; }, + pbxXCBuildConfigurationSection: () => { return {}; }, + }; + }; + iOSProjectService.savePbxProj = (): Promise => Promise.resolve(); + + const projectData: IProjectData = testInjector.resolve("projectData"); + const basePodfileModuleName = "BasePodfile"; + + const basePodfilePath = path.join(projectData.appDirectoryPath, "App_Resources", "iOS", "Podfile"); + const pluginPodfileContent = ["source 'https://github.com/CocoaPods/Specs.git'", "platform :ios, '8.1'", "pod 'GoogleMaps'"].join("\n"); + fs.writeFile(basePodfilePath, pluginPodfileContent); + + projectData.podfilePath = basePodfilePath; + + cocoapodsService.applyPodfileToProject(basePodfileModuleName, basePodfilePath, projectData, iOSProjectService.getPlatformData(projectData).projectRoot); + + const projectPodfilePath = path.join(platformsFolderPath, "Podfile"); + assert.isTrue(fs.exists(projectPodfilePath)); + + const actualProjectPodfileContent = fs.readText(projectPodfilePath); + const expectedProjectPodfileContent = ["use_frameworks!\n", + `target "${projectName}" do`, + `# Begin Podfile - ${basePodfilePath}`, + `${pluginPodfileContent}`, + "# End Podfile", + "end"] + .join("\n"); + assert.equal(actualProjectPodfileContent, expectedProjectPodfileContent); + + fs.deleteFile(basePodfilePath); + + cocoapodsService.applyPodfileToProject(basePodfileModuleName, basePodfilePath, projectData, iOSProjectService.getPlatformData(projectData).projectRoot); + assert.isFalse(fs.exists(projectPodfilePath)); + + }); + it("adds plugin with Podfile", async () => { const projectName = "projectDirectory"; const projectPath = temp.mkdirSync(projectName); @@ -482,11 +551,59 @@ describe("Cocoapods support", () => { } }); -describe("Source code in plugin support", () => { +describe("Source code support", () => { if (require("os").platform() !== "darwin") { console.log("Skipping Source code in plugin tests. They cannot work on windows"); } else { + const getProjectWithoutPlugins = async (files: string[]) => { + // Arrange + const projectName = "projectDirectory"; + const projectPath = temp.mkdirSync(projectName); + const testInjector = createTestInjector(projectPath, projectName, xcode); + const fs: IFileSystem = testInjector.resolve("fs"); + + const packageJsonData = { + "name": "myProject", + "version": "0.1.0", + "nativescript": { + "id": "org.nativescript.myProject", + "tns-ios": { + "version": "1.0.0" + } + } + }; + fs.writeJson(path.join(projectPath, "package.json"), packageJsonData); + + const platformsFolderPath = path.join(projectPath, "platforms", "ios"); + fs.createDirectory(platformsFolderPath); + + const iOSProjectService = testInjector.resolve("iOSProjectService"); + + iOSProjectService.getXcodeprojPath = () => { + return path.join(__dirname, "files"); + }; + let pbxProj: any; + iOSProjectService.savePbxProj = (project: any): Promise => { + pbxProj = project; + return Promise.resolve(); + }; + + const projectData: IProjectData = testInjector.resolve("projectData"); + + const platformSpecificAppResourcesPath = path.join(projectData.appResourcesDirectoryPath, iOSProjectService.getPlatformData(projectData).normalizedPlatformName); + + files.forEach(file => { + const fullPath = path.join(platformSpecificAppResourcesPath, file); + fs.createDirectory(path.dirname(fullPath)); + fs.writeFile(fullPath, ""); + }); + + await iOSProjectService.prepareNativeSourceCode("src", platformSpecificAppResourcesPath, projectData); + + return pbxProj; + }; + const preparePluginWithFiles = async (files: string[], prepareMethodToCall: string) => { // Arrange const projectName = "projectDirectory"; @@ -551,6 +668,45 @@ describe("Source code in plugin support", () => { return pbxProj; }; + it("adds source files as resources", async () => { + const sourceFileNames = [ + "src/Header.h", "src/ObjC.m", + "src/nested/Header.hpp", "src/nested/Source.cpp", "src/nested/ObjCpp.mm", + "src/nested/level2/Header2.hxx", "src/nested/level2/Source2.cxx", "src/nested/level2/Source3.c", + "src/SomeOtherExtension.donotadd", + ]; + + const projectName = "projectDirectory"; + const projectPath = temp.mkdirSync(projectName); + const testInjector = createTestInjector(projectPath, projectName, xcode); + const fs: IFileSystem = testInjector.resolve("fs"); + + const platformsFolderPath = path.join(projectPath, "platforms", "ios"); + fs.createDirectory(platformsFolderPath); + + const pbxProj = await await getProjectWithoutPlugins(sourceFileNames); + + const pbxFileReference = pbxProj.hash.project.objects.PBXFileReference; + const pbxFileReferenceValues = Object.keys(pbxFileReference).map(key => pbxFileReference[key]); + const buildPhaseFiles = pbxProj.hash.project.objects.PBXSourcesBuildPhase["858B83F218CA22B800AB12DE"].files; + + sourceFileNames.map(file => path.basename(file)).forEach(basename => { + const ext = path.extname(basename); + const shouldBeAdded = ext !== ".donotadd"; + if (shouldBeAdded) { + assert.notEqual(pbxFileReferenceValues.indexOf(basename), -1, `${basename} not added to PBXFileRefereces`); + + if (shouldBeAdded && !path.extname(basename).startsWith(".h")) { + const buildPhaseFile = buildPhaseFiles.find((fileObject: any) => fileObject.comment.startsWith(basename)); + assert.isDefined(buildPhaseFile, `${basename} not added to PBXSourcesBuildPhase`); + assert.include(buildPhaseFile.comment, "in Sources", `${basename} must be added to Sources group`); + } + } else { + assert.equal(pbxFileReferenceValues.indexOf(basename), -1, `${basename} was added to PBXFileRefereces, but it shouldn't have been`); + } + }); + }); + it("adds plugin with Source files", async () => { const sourceFileNames = [ "src/Header.h", "src/ObjC.m", diff --git a/test/stubs.ts b/test/stubs.ts index 1a47e49c09..dd7f6a7cd3 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -323,6 +323,7 @@ export class ProjectDataStub implements IProjectData { public appGradlePath: string; public gradleFilesDirectoryPath: string; public buildXcconfigPath: string; + public podfilePath: string; public isShared: boolean; public initializeProjectData(projectDir?: string): void {