Skip to content

Commit c61f101

Browse files
Merge pull request #3943 from NativeScript/vladimirov/cocoapods-errors-parsing
fix: CLI breaks process when pod install has not failed
2 parents 6ba2981 + 2db98d0 commit c61f101

File tree

7 files changed

+301
-65
lines changed

7 files changed

+301
-65
lines changed

lib/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,8 @@ export class PluginNativeDirNames {
222222
}
223223

224224
export const PODFILE_NAME = "Podfile";
225+
226+
export class IosProjectConstants {
227+
public static XcodeProjExtName = ".xcodeproj";
228+
public static XcodeSchemeExtName = ".xcscheme";
229+
}

lib/declarations.d.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -773,17 +773,27 @@ interface IProjectNameService {
773773
ensureValidName(projectName: string, validateOptions?: { force: boolean }): Promise<string>;
774774
}
775775

776+
/**
777+
* Describes options that can be passed to xcprojService.verifyXcproj method.
778+
*/
779+
interface IVerifyXcprojOptions {
780+
/**
781+
* Whether to fail with error message or not
782+
*/
783+
shouldFail: boolean;
784+
}
785+
776786
/**
777787
* Designed for getting information about xcproj.
778788
*/
779789
interface IXcprojService {
780790
/**
781791
* Checks whether the system needs xcproj to execute ios builds successfully.
782792
* In case the system does need xcproj but does not have it, prints an error message.
783-
* @param {boolean} whether to fail with error message or not
793+
* @param {IVerifyXcprojOptions} opts whether to fail with error message or not
784794
* @return {Promise<boolean>} whether an error occurred or not.
785795
*/
786-
verifyXcproj(shouldFail: boolean): Promise<boolean>;
796+
verifyXcproj(opts: IVerifyXcprojOptions): Promise<boolean>;
787797
/**
788798
* Collects information about xcproj.
789799
* @return {Promise<XcprojInfo>} collected info about xcproj.
@@ -903,4 +913,4 @@ interface IRuntimeGradleVersions {
903913

904914
interface INetworkConnectivityValidator {
905915
validate(): Promise<void>;
906-
}
916+
}

lib/definitions/project.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,14 @@ interface ICocoaPodsService {
493493
* @returns {string} Path to project's Podfile.
494494
*/
495495
getProjectPodfilePath(nativeProjectPath: string): string;
496+
497+
/**
498+
* Executes `pod install` or `sanboxpod install` in the passed project.
499+
* @param {string} projectRoot The root directory of the native iOS project.
500+
* @param {string} xcodeProjPath The full path to the .xcodeproj file.
501+
* @returns {Promise<ISpawnResult>} Information about the spawned process.
502+
*/
503+
executePodInstall(projectRoot: string, xcodeProjPath: string): Promise<ISpawnResult>;
496504
}
497505

498506
interface IRubyFunction {

lib/services/cocoapods-service.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ export class CocoaPodsService implements ICocoaPodsService {
66
private static PODFILE_POST_INSTALL_SECTION_NAME = "post_install";
77
private static INSTALLER_BLOCK_PARAMETER_NAME = "installer";
88

9-
constructor(private $fs: IFileSystem) { }
9+
constructor(private $fs: IFileSystem,
10+
private $childProcess: IChildProcess,
11+
private $errors: IErrors,
12+
private $xcprojService: IXcprojService,
13+
private $logger: ILogger,
14+
private $config: IConfiguration) { }
1015

1116
public getPodfileHeader(targetName: string): string {
1217
return `use_frameworks!${EOL}${EOL}target "${targetName}" do${EOL}`;
@@ -20,6 +25,33 @@ export class CocoaPodsService implements ICocoaPodsService {
2025
return path.join(projectRoot, PODFILE_NAME);
2126
}
2227

28+
public async executePodInstall(projectRoot: string, xcodeProjPath: string): Promise<ISpawnResult> {
29+
// Check availability
30+
try {
31+
await this.$childProcess.exec("which pod");
32+
await this.$childProcess.exec("which xcodeproj");
33+
} catch (e) {
34+
this.$errors.failWithoutHelp("CocoaPods or ruby gem 'xcodeproj' is not installed. Run `sudo gem install cocoapods` and try again.");
35+
}
36+
37+
await this.$xcprojService.verifyXcproj({ shouldFail: true });
38+
39+
this.$logger.info("Installing pods...");
40+
const podTool = this.$config.USE_POD_SANDBOX ? "sandbox-pod" : "pod";
41+
// cocoapods print a lot of non-error information on stderr. Pipe the `stderr` to `stdout`, so we won't polute CLI's stderr output.
42+
const podInstallResult = await this.$childProcess.spawnFromEvent(podTool, ["install"], "close", { cwd: projectRoot, stdio: ['pipe', process.stdout, process.stdout] }, { throwError: false });
43+
44+
if (podInstallResult.exitCode !== 0) {
45+
this.$errors.failWithoutHelp(`'${podTool} install' command failed.${podInstallResult.stderr ? " Error is: " + podInstallResult.stderr : ""}`);
46+
}
47+
48+
if ((await this.$xcprojService.getXcprojInfo()).shouldUseXcproj) {
49+
await this.$childProcess.spawnFromEvent("xcproj", ["--project", xcodeProjPath, "touch"], "close");
50+
}
51+
52+
return podInstallResult;
53+
}
54+
2355
public async applyPluginPodfileToProject(pluginData: IPluginData, projectData: IProjectData, nativeProjectPath: string): Promise<void> {
2456
const pluginPodFilePath = this.getPluginPodfilePath(pluginData);
2557
if (!this.$fs.exists(pluginPodFilePath)) {

lib/services/ios-project-service.ts

Lines changed: 13 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { IOSProvisionService } from "./ios-provision-service";
1313
import { IOSEntitlementsService } from "./ios-entitlements-service";
1414
import { XCConfigService } from "./xcconfig-service";
1515
import * as mobileprovision from "ios-mobileprovision-finder";
16-
import { BUILD_XCCONFIG_FILE_NAME } from "../constants";
16+
import { BUILD_XCCONFIG_FILE_NAME, IosProjectConstants } from "../constants";
1717

1818
interface INativeSourceCodeGroup {
1919
name: string;
@@ -22,8 +22,6 @@ interface INativeSourceCodeGroup {
2222
}
2323

2424
export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService {
25-
private static XCODE_PROJECT_EXT_NAME = ".xcodeproj";
26-
private static XCODE_SCHEME_EXT_NAME = ".xcscheme";
2725
private static XCODEBUILD_MIN_VERSION = "6.0";
2826
private static IOS_PROJECT_NAME_PLACEHOLDER = "__PROJECT_NAME__";
2927
private static IOS_PLATFORM_NAME = "ios";
@@ -36,7 +34,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
3634
private $injector: IInjector,
3735
$projectDataService: IProjectDataService,
3836
private $prompter: IPrompter,
39-
private $config: IConfiguration,
4037
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
4138
private $devicesService: Mobile.IDevicesService,
4239
private $mobileHelper: Mobile.IMobileHelper,
@@ -174,21 +171,21 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
174171
}
175172
this.replaceFileName("-Prefix.pch", projectRootFilePath, projectData);
176173

177-
const xcschemeDirPath = path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + IOSProjectService.XCODE_PROJECT_EXT_NAME, "xcshareddata/xcschemes");
178-
const xcschemeFilePath = path.join(xcschemeDirPath, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + IOSProjectService.XCODE_SCHEME_EXT_NAME);
174+
const xcschemeDirPath = path.join(this.getPlatformData(projectData).projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + IosProjectConstants.XcodeProjExtName, "xcshareddata/xcschemes");
175+
const xcschemeFilePath = path.join(xcschemeDirPath, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + IosProjectConstants.XcodeSchemeExtName);
179176

180177
if (this.$fs.exists(xcschemeFilePath)) {
181178
this.$logger.debug("Found shared scheme at xcschemeFilePath, renaming to match project name.");
182179
this.$logger.debug("Checkpoint 0");
183180
this.replaceFileContent(xcschemeFilePath, projectData);
184181
this.$logger.debug("Checkpoint 1");
185-
this.replaceFileName(IOSProjectService.XCODE_SCHEME_EXT_NAME, xcschemeDirPath, projectData);
182+
this.replaceFileName(IosProjectConstants.XcodeSchemeExtName, xcschemeDirPath, projectData);
186183
this.$logger.debug("Checkpoint 2");
187184
} else {
188185
this.$logger.debug("Copying xcscheme from template not found at " + xcschemeFilePath);
189186
}
190187

191-
this.replaceFileName(IOSProjectService.XCODE_PROJECT_EXT_NAME, this.getPlatformData(projectData).projectRoot, projectData);
188+
this.replaceFileName(IosProjectConstants.XcodeProjExtName, this.getPlatformData(projectData).projectRoot, projectData);
192189

193190
const pbxprojFilePath = this.getPbxProjPath(projectData);
194191
this.replaceFileContent(pbxprojFilePath, projectData);
@@ -891,7 +888,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
891888
}
892889

893890
private getXcodeprojPath(projectData: IProjectData): string {
894-
return path.join(this.getPlatformData(projectData).projectRoot, projectData.projectName + IOSProjectService.XCODE_PROJECT_EXT_NAME);
891+
return path.join(this.getPlatformData(projectData).projectRoot, projectData.projectName + IosProjectConstants.XcodeProjExtName);
895892
}
896893

897894
private getPluginsDebugXcconfigFilePath(projectData: IProjectData): string {
@@ -941,7 +938,9 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
941938
await this.prepareResources(pluginPlatformsFolderPath, pluginData, projectData);
942939
await this.prepareFrameworks(pluginPlatformsFolderPath, pluginData, projectData);
943940
await this.prepareStaticLibs(pluginPlatformsFolderPath, pluginData, projectData);
944-
await this.prepareCocoapods(pluginPlatformsFolderPath, pluginData, projectData);
941+
942+
const projectRoot = this.getPlatformData(projectData).projectRoot;
943+
await this.$cocoapodsService.applyPluginPodfileToProject(pluginData, projectData, projectRoot);
945944
}
946945

947946
public async removePluginNativeCode(pluginData: IPluginData, projectData: IProjectData): Promise<void> {
@@ -958,8 +957,9 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
958957
public async afterPrepareAllPlugins(projectData: IProjectData): Promise<void> {
959958
const projectRoot = this.getPlatformData(projectData).projectRoot;
960959
if (this.$fs.exists(this.$cocoapodsService.getProjectPodfilePath(projectRoot))) {
961-
const xcuserDataPath = path.join(this.getXcodeprojPath(projectData), "xcuserdata");
962-
const sharedDataPath = path.join(this.getXcodeprojPath(projectData), "xcshareddata");
960+
const xcodeProjPath = this.getXcodeprojPath(projectData);
961+
const xcuserDataPath = path.join(xcodeProjPath, "xcuserdata");
962+
const sharedDataPath = path.join(xcodeProjPath, "xcshareddata");
963963

964964
if (!this.$fs.exists(xcuserDataPath) && !this.$fs.exists(sharedDataPath)) {
965965
this.$logger.info("Creating project scheme...");
@@ -969,7 +969,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
969969
await this.$childProcess.exec(createSchemeRubyScript, { cwd: this.getPlatformData(projectData).projectRoot });
970970
}
971971

972-
await this.executePodInstall(projectData);
972+
await this.$cocoapodsService.executePodInstall(projectRoot, xcodeProjPath);
973973
}
974974
}
975975

@@ -1070,43 +1070,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
10701070
this.$fs.rename(path.join(fileRootLocation, oldFileName), path.join(fileRootLocation, newFileName));
10711071
}
10721072

1073-
private async executePodInstall(projectData: IProjectData): Promise<any> {
1074-
// Check availability
1075-
try {
1076-
await this.$childProcess.exec("which pod");
1077-
await this.$childProcess.exec("which xcodeproj");
1078-
} catch (e) {
1079-
this.$errors.failWithoutHelp("CocoaPods or ruby gem 'xcodeproj' is not installed. Run `sudo gem install cocoapods` and try again.");
1080-
}
1081-
1082-
await this.$xcprojService.verifyXcproj(true);
1083-
1084-
this.$logger.info("Installing pods...");
1085-
const podTool = this.$config.USE_POD_SANDBOX ? "sandbox-pod" : "pod";
1086-
const childProcess = await this.$childProcess.spawnFromEvent(podTool, ["install"], "close", { cwd: this.getPlatformData(projectData).projectRoot, stdio: ['pipe', process.stdout, 'pipe'] });
1087-
if (childProcess.stderr) {
1088-
const warnings = childProcess.stderr.match(/(\u001b\[(?:\d*;){0,5}\d*m[\s\S]+?\u001b\[(?:\d*;){0,5}\d*m)|(\[!\].*?\n)|(.*?warning.*)/gi);
1089-
_.each(warnings, (warning: string) => {
1090-
this.$logger.warnWithLabel(warning.replace("\n", ""));
1091-
});
1092-
1093-
let errors = childProcess.stderr;
1094-
_.each(warnings, warning => {
1095-
errors = errors.replace(warning, "");
1096-
});
1097-
1098-
if (errors.trim()) {
1099-
this.$errors.failWithoutHelp(`Pod install command failed. Error output: ${errors}`);
1100-
}
1101-
}
1102-
1103-
if ((await this.$xcprojService.getXcprojInfo()).shouldUseXcproj) {
1104-
await this.$childProcess.spawnFromEvent("xcproj", ["--project", this.getXcodeprojPath(projectData), "touch"], "close");
1105-
}
1106-
1107-
return childProcess;
1108-
}
1109-
11101073
private async prepareNativeSourceCode(pluginName: string, pluginPlatformsFolderPath: string, projectData: IProjectData): Promise<void> {
11111074
const project = this.createPbxProj(projectData);
11121075
const group = this.getRootGroup(pluginName, pluginPlatformsFolderPath);
@@ -1153,15 +1116,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
11531116
}
11541117
}
11551118

1156-
private async prepareCocoapods(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData, opts?: any): Promise<void> {
1157-
const projectRoot = this.getPlatformData(projectData).projectRoot;
1158-
await this.$cocoapodsService.applyPluginPodfileToProject(pluginData, projectData, projectRoot);
1159-
const pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile");
1160-
1161-
if (opts && opts.executePodInstall && this.$fs.exists(pluginPodFilePath)) {
1162-
await this.executePodInstall(projectData);
1163-
}
1164-
}
11651119
private removeNativeSourceCode(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData): void {
11661120
const project = this.createPbxProj(projectData);
11671121
const group = this.getRootGroup(pluginData.name, pluginPlatformsFolderPath);

lib/services/xcproj-service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ class XcprojService implements IXcprojService {
1212
private $xcodeSelectService: IXcodeSelectService) {
1313
}
1414

15-
public async verifyXcproj(shouldFail: boolean): Promise<boolean> {
15+
public async verifyXcproj(opts: IVerifyXcprojOptions): Promise<boolean> {
1616
const xcprojInfo = await this.getXcprojInfo();
1717
if (xcprojInfo.shouldUseXcproj && !xcprojInfo.xcprojAvailable) {
1818
const errorMessage = `You are using CocoaPods version ${xcprojInfo.cocoapodVer} which does not support Xcode ${xcprojInfo.xcodeVersion.major}.${xcprojInfo.xcodeVersion.minor} yet.${EOL}${EOL}You can update your cocoapods by running $sudo gem install cocoapods from a terminal.${EOL}${EOL}In order for the NativeScript CLI to be able to work correctly with this setup you need to install xcproj command line tool and add it to your PATH. Xcproj can be installed with homebrew by running $ brew install xcproj from the terminal`;
19-
if (shouldFail) {
19+
if (opts.shouldFail) {
2020
this.$errors.failWithoutHelp(errorMessage);
2121
} else {
2222
this.$logger.warn(errorMessage);

0 commit comments

Comments
 (0)