UNPKG

nativescript

Version:

Command-line interface for building NativeScript projects

353 lines • 20.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CocoaPodsService = void 0; const os_1 = require("os"); const path = require("path"); const _ = require("lodash"); const constants_1 = require("../constants"); const helpers_1 = require("../common/helpers"); const yok_1 = require("../common/yok"); const constants = require("../constants"); class CocoaPodsService { constructor($cocoaPodsPlatformManager, $fs, $childProcess, $errors, $logger, $config, $xcconfigService, $xcodeSelectService) { this.$cocoaPodsPlatformManager = $cocoaPodsPlatformManager; this.$fs = $fs; this.$childProcess = $childProcess; this.$errors = $errors; this.$logger = $logger; this.$config = $config; this.$xcconfigService = $xcconfigService; this.$xcodeSelectService = $xcodeSelectService; this.getCocoaPodsFromPodfile = _.memoize(this._getCocoaPodsFromPodfile, helpers_1.getHash); } getPodfileHeader(targetName) { return `use_frameworks!${os_1.EOL}${os_1.EOL}target "${targetName}" do${os_1.EOL}`; } getPodfileFooter() { return `${os_1.EOL}end`; } getProjectPodfilePath(projectRoot) { return path.join(projectRoot, constants_1.PODFILE_NAME); } async executePodInstall(projectRoot, xcodeProjPath) { this.$logger.info("Installing pods..."); let podTool = this.$config.USE_POD_SANDBOX ? "sandbox-pod" : "pod"; const args = ["install"]; if (process.platform === "darwin" && process.arch === "arm64") { // check if pod is installed as an x86_64 binary or a native arm64 one // we run the following: // arch -x86_64 pod --version // if it's an arm64 binary, we'll get something like this as a result: // arch: posix_spawnp: pod: Bad CPU type in executable // in which case, we should run it natively. const res = await this.$childProcess .exec("arch -x86_64 pod --version", null, { showStderr: true, }) .then((res) => res.stdout + " " + res.stderr) .catch((err) => err.message); if (!res.includes("Bad CPU type in executable")) { this.$logger.trace("Running on arm64 but pod is installed under rosetta2 - running pod through rosetta2"); args.unshift(podTool); args.unshift("-x86_64"); podTool = "arch"; } } // cocoapods print a lot of non-error information on stderr. Pipe the `stderr` to `stdout`, so we won't polute CLI's stderr output. const podInstallResult = await this.$childProcess.spawnFromEvent(podTool, args, "close", { cwd: projectRoot, stdio: ["pipe", process.stdout, process.stdout] }, { throwError: false }); if (podInstallResult.exitCode !== 0) { // https://github.com/CocoaPods/CocoaPods/blob/92aaf0f1120d32f3487960b485fb69fcaf61486c/lib/cocoapods/resolver.rb#L498 // TODO add article const versionResolutionHint = podInstallResult.exitCode === 31 ? `For more information on resolving CocoaPod issues in NativeScript read.` : ""; this.$errors.fail(`'${podTool} install' command failed.${podInstallResult.stderr ? " Error is: " + podInstallResult.stderr : ""} ${versionResolutionHint}`); } return podInstallResult; } async mergePodXcconfigFile(projectData, platformData) { const podFilesRootDirName = path.join("Pods", "Target Support Files", `Pods-${projectData.projectName}`); const podFolder = path.join(platformData.projectRoot, podFilesRootDirName); if (this.$fs.exists(podFolder)) { const pluginsXcconfigFilePaths = this.$xcconfigService.getPluginsXcconfigFilePaths(platformData.projectRoot); for (const configuration in pluginsXcconfigFilePaths) { const pluginsXcconfigFilePath = pluginsXcconfigFilePaths[configuration]; const podXcconfigFilePath = path.join(podFolder, `Pods-${projectData.projectName}.${configuration}.xcconfig`); await this.$xcconfigService.mergeFiles(podXcconfigFilePath, pluginsXcconfigFilePath); } } } async applyPodfileFromAppResources(projectData, platformData) { const { projectRoot, normalizedPlatformName } = platformData; const mainPodfilePath = path.join(projectData.appResourcesDirectoryPath, normalizedPlatformName, constants_1.PODFILE_NAME); const projectPodfilePath = this.getProjectPodfilePath(projectRoot); if (this.$fs.exists(projectPodfilePath) || this.$fs.exists(mainPodfilePath)) { await this.applyPodfileToProject(constants_1.NS_BASE_PODFILE, mainPodfilePath, projectData, platformData); } } async applyPodfileArchExclusions(projectData, platformData) { const xcodeVersionData = await this.$xcodeSelectService.getXcodeVersion(); // only apply EXCLUDED_ARCHS workaround on XCode 12 if (+xcodeVersionData.major !== 12) { return; } const { projectRoot } = platformData; const exclusionsPodfile = path.join(projectRoot, "Podfile-exclusions"); if (!this.$fs.exists(exclusionsPodfile)) { const exclusions = ` post_install do |installer| installer.pods_project.build_configurations.each do |config| config.build_settings.delete "VALID_ARCHS" config.build_settings["EXCLUDED_ARCHS_x86_64"] = "arm64 arm64e" config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "i386 armv6 armv7 armv7s armv8 $(EXCLUDED_ARCHS_$(NATIVE_ARCH_64_BIT))" config.build_settings["EXCLUDED_ARCHS[sdk=iphoneos*]"] = "i386 armv6 armv7 armv7s armv8 x86_64" end end`.trim(); this.$fs.writeFile(exclusionsPodfile, exclusions); } await this.applyPodfileToProject("NativeScript-CLI-Architecture-Exclusions", exclusionsPodfile, projectData, platformData); // clean up this.$fs.deleteFile(exclusionsPodfile); } async applyPodfileFromExtensions(projectData, platformData) { const extensionFolderPath = path.join(projectData.getAppResourcesDirectoryPath(), constants.iOSAppResourcesFolderName, constants.NATIVE_EXTENSION_FOLDER); const projectPodfilePath = this.getProjectPodfilePath(platformData.projectRoot); if (!this.$fs.exists(extensionFolderPath) || !this.$fs.exists(projectPodfilePath)) { return; } let projectPodFileContent = this.$fs.readText(projectPodfilePath); const extensionsPodfile = this.$fs .readDirectory(extensionFolderPath) .filter((name) => { const extensionPath = path.join(extensionFolderPath, name); const stats = this.$fs.getFsStats(extensionPath); return stats.isDirectory() && !name.startsWith("."); }) .map((name) => ({ targetName: name, podfilePath: path.join(extensionFolderPath, name, constants.PODFILE_NAME), })); extensionsPodfile.forEach(({ targetName, podfilePath }) => { // Remove the data between #Begin Podfile and #EndPodfile const regExpToRemove = new RegExp(`${this.getExtensionPodfileHeader(podfilePath, targetName)}[\\s\\S]*?${this.getExtensionPodfileEnd()}`, "mg"); projectPodFileContent = projectPodFileContent.replace(regExpToRemove, ""); if (this.$fs.exists(podfilePath)) { const podfileContentWithoutTarget = this.$fs.readText(podfilePath); const podFileContent = this.getExtensionPodfileHeader(podfilePath, targetName) + os_1.EOL + podfileContentWithoutTarget + os_1.EOL + this.getExtensionPodfileEnd(); projectPodFileContent += os_1.EOL + podFileContent; } }); this.$fs.writeFile(projectPodfilePath, projectPodFileContent); } async applyPodfileToProject(moduleName, podfilePath, projectData, platformData) { const nativeProjectPath = platformData.projectRoot; if (!this.$fs.exists(podfilePath)) { this.removePodfileFromProject(moduleName, podfilePath, projectData, nativeProjectPath); return; } const { podfileContent, replacedFunctions, podfilePlatformData } = this.buildPodfileContent(podfilePath, moduleName, projectData, platformData); const pathToProjectPodfile = this.getProjectPodfilePath(nativeProjectPath); const projectPodfileContent = this.$fs.exists(pathToProjectPodfile) ? this.$fs.readText(pathToProjectPodfile).trim() : ""; if (projectPodfileContent.indexOf(podfileContent) === -1) { // Remove old occurences of the plugin from the project's Podfile. this.removePodfileFromProject(moduleName, podfilePath, projectData, nativeProjectPath); let finalPodfileContent = this.$fs.exists(pathToProjectPodfile) ? this.getPodfileContentWithoutTarget(projectData, this.$fs.readText(pathToProjectPodfile)) : ""; if (podfileContent.indexOf(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME) !== -1) { finalPodfileContent = this.addPostInstallHook(replacedFunctions, finalPodfileContent, podfileContent); } if (podfilePlatformData) { finalPodfileContent = this.$cocoaPodsPlatformManager.addPlatformSection(projectData, podfilePlatformData, finalPodfileContent); } finalPodfileContent = `${finalPodfileContent.trim()}${os_1.EOL}${os_1.EOL}${podfileContent.trim()}${os_1.EOL}`; this.saveProjectPodfile(projectData, finalPodfileContent, nativeProjectPath); } } removePodfileFromProject(moduleName, podfilePath, projectData, 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(podfilePath)}[\\s\\S]*?${this.getPluginPodfileEnd()}`, "mg"); projectPodFileContent = projectPodFileContent.replace(regExpToRemove, ""); projectPodFileContent = this.removePostInstallHook(moduleName, projectPodFileContent); projectPodFileContent = this.$cocoaPodsPlatformManager.removePlatformSection(moduleName, projectPodFileContent, podfilePath); const defaultPodfileBeginning = this.getPodfileHeader(projectData.projectName); const defaultContentWithPostInstallHook = `${defaultPodfileBeginning}${this.getPostInstallHookHeader()}end${os_1.EOL}end`; const defaultContentWithoutPostInstallHook = `${defaultPodfileBeginning}${os_1.EOL}end`; const trimmedProjectPodFileContent = projectPodFileContent.trim(); if (!trimmedProjectPodFileContent || trimmedProjectPodFileContent === defaultContentWithPostInstallHook || trimmedProjectPodFileContent === defaultContentWithoutPostInstallHook) { this.$fs.deleteFile(this.getProjectPodfilePath(projectRoot)); } else { this.$fs.writeFile(this.getProjectPodfilePath(projectRoot), projectPodFileContent); } } } getPluginPodfilePath(pluginData) { const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath("ios" /* PlatformTypes.ios */); const pluginPodFilePath = path.join(pluginPlatformsFolderPath, constants_1.PODFILE_NAME); return pluginPodFilePath; } addPostInstallHook(replacedFunctions, finalPodfileContent, pluginPodfileContent) { const postInstallHookStart = this.getPostInstallHookHeader(); let postInstallHookContent = ""; _.each(replacedFunctions, (rubyFunction) => { let functionExecution = rubyFunction.functionName; if (rubyFunction.functionParameters && rubyFunction.functionParameters.length) { functionExecution = `${functionExecution} ${CocoaPodsService.INSTALLER_BLOCK_PARAMETER_NAME}`; } postInstallHookContent += ` ${functionExecution}${os_1.EOL}`; }); if (postInstallHookContent) { const index = finalPodfileContent.indexOf(postInstallHookStart); if (index !== -1) { const regExp = new RegExp(`(${(0, helpers_1.regExpEscape)(postInstallHookStart)}[\\s\\S]*?)(\\bend\\b)`, "m"); finalPodfileContent = finalPodfileContent.replace(regExp, `$1${postInstallHookContent.trimRight()}${os_1.EOL}$2`); } else { if (finalPodfileContent.length > 0) { finalPodfileContent += `${os_1.EOL}${os_1.EOL}`; } const postInstallHook = `${postInstallHookStart}${postInstallHookContent}end`; finalPodfileContent = `${finalPodfileContent}${postInstallHook}`; } } return finalPodfileContent; } getPodfileContentWithoutTarget(projectData, projectPodfileContent) { const podFileHeader = this.getPodfileHeader(projectData.projectName); if (_.startsWith(projectPodfileContent, podFileHeader)) { projectPodfileContent = projectPodfileContent.substr(podFileHeader.length); const podFileFooter = this.getPodfileFooter(); // Only remove the final end in case the file starts with the podFileHeader if (_.endsWith(projectPodfileContent, podFileFooter)) { projectPodfileContent = projectPodfileContent.substr(0, projectPodfileContent.length - podFileFooter.length); } } return projectPodfileContent.trim(); } saveProjectPodfile(projectData, projectPodfileContent, projectRoot) { projectPodfileContent = this.getPodfileContentWithoutTarget(projectData, projectPodfileContent); const podFileHeader = this.getPodfileHeader(projectData.projectName); const podFileFooter = this.getPodfileFooter(); const contentToWrite = `${podFileHeader}${projectPodfileContent}${podFileFooter}`; const projectPodfilePath = this.getProjectPodfilePath(projectRoot); this.$fs.writeFile(projectPodfilePath, contentToWrite); } removePostInstallHook(moduleName, projectPodFileContent) { const regExp = new RegExp(`^.*?${this.getHookBasicFuncNameForPlugin(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME, moduleName)}.*?$\\r?\\n`, "gm"); projectPodFileContent = projectPodFileContent.replace(regExp, ""); return projectPodFileContent; } getHookBasicFuncNameForPlugin(hookName, pluginName) { // nativescript-hook and nativescript_hook should have different names, so replace all _ with ___ first and then replace all special symbols with _ // This will lead to a clash in case plugins are called nativescript-hook and nativescript___hook const replacedPluginName = pluginName .replace(/_/g, "___") .replace(/[^A-Za-z0-9_]/g, "_"); return `${hookName}${replacedPluginName}`; } replaceHookContent(hookName, podfileContent, pluginName) { const hookStart = `${hookName} do`; const hookDefinitionRegExp = new RegExp(`${hookStart} *(\\|(\\w+)\\|)?`, "g"); const newFunctions = []; const replacedContent = podfileContent.replace(hookDefinitionRegExp, (substring, firstGroup, secondGroup, index) => { const newFunctionName = `${this.getHookBasicFuncNameForPlugin(hookName, pluginName)}_${newFunctions.length}`; let newDefinition = `def ${newFunctionName}`; const rubyFunction = { functionName: newFunctionName }; // firstGroup is the block parameter, secondGroup is the block parameter name. if (firstGroup && secondGroup) { newDefinition = `${newDefinition} (${secondGroup})`; rubyFunction.functionParameters = secondGroup; } newFunctions.push(rubyFunction); return newDefinition; }); return { replacedContent, newFunctions }; } getPluginPodfileHeader(pluginPodFilePath) { // escape special + from the podfile path (pnpm) pluginPodFilePath = pluginPodFilePath.replace(/\+/g, "\\+"); return `# Begin Podfile - ${pluginPodFilePath}`; } getPluginPodfileEnd() { return `# End Podfile${os_1.EOL}`; } getExtensionPodfileHeader(extensionPodFilePath, targetName) { const targetHeader = `target "${targetName.trim()}" do`; return `${this.getPluginPodfileHeader(extensionPodFilePath)}${os_1.EOL}${targetHeader}`; } getExtensionPodfileEnd() { return `end${os_1.EOL}${this.getPluginPodfileEnd()}`; } getPostInstallHookHeader() { return `${CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME} do |${CocoaPodsService.INSTALLER_BLOCK_PARAMETER_NAME}|${os_1.EOL}`; } buildPodfileContent(pluginPodFilePath, pluginName, projectData, platformData) { const pluginPodfileContent = this.$fs.readText(pluginPodFilePath); const data = this.replaceHookContent(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME, pluginPodfileContent, pluginName); const cocoapodsData = this.$cocoaPodsPlatformManager.replacePlatformRow(data.replacedContent, pluginPodFilePath); const podfilePlatformData = cocoapodsData.podfilePlatformData; let replacedContent = cocoapodsData.replacedContent; if (projectData.nsConfig && projectData.nsConfig.overridePods && !this.isMainPodFile(pluginPodFilePath, projectData, platformData)) { replacedContent = this.overridePodsFromFile(replacedContent, projectData, platformData); } return { podfileContent: `${this.getPluginPodfileHeader(pluginPodFilePath)}${os_1.EOL}${replacedContent}${os_1.EOL}${this.getPluginPodfileEnd()}`, replacedFunctions: data.newFunctions, podfilePlatformData, }; } getMainPodFilePath(projectData, platformData) { return path.join(projectData.appResourcesDirectoryPath, platformData.normalizedPlatformName, constants_1.PODFILE_NAME); } isMainPodFile(podFilePath, projectData, platformData) { const mainPodfilePath = this.getMainPodFilePath(projectData, platformData); return podFilePath === mainPodfilePath; } overridePodsFromFile(podfileContent, projectData, platformData) { const mainPodfilePath = this.getMainPodFilePath(projectData, platformData); if (this.$fs.exists(mainPodfilePath)) { const mainPodfileContent = this.$fs.readText(mainPodfilePath); const pods = this.getCocoaPodsFromPodfile(mainPodfileContent); _.forEach(pods, (pod) => { podfileContent = podfileContent.replace(new RegExp(`^[ ]*pod\\s*["']${pod}['"].*$`, "gm"), "#$&"); }); } return podfileContent; } _getCocoaPodsFromPodfile(podfileContent) { const pods = []; const podsRegex = /^\s*pod\s*["'](.*?)['"].*$/gm; let match = podsRegex.exec(podfileContent); while (match != null) { const podName = match[1]; if (podName) { pods.push(podName); } match = podsRegex.exec(podfileContent); } return pods; } } exports.CocoaPodsService = CocoaPodsService; CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME = "post_install"; CocoaPodsService.INSTALLER_BLOCK_PARAMETER_NAME = "installer"; yok_1.injector.register("cocoapodsService", CocoaPodsService); //# sourceMappingURL=cocoapods-service.js.map