nativescript
Version:
Command-line interface for building NativeScript projects
353 lines • 20.1 kB
JavaScript
"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