Skip to content

fix: CLI breaks process when pod install has not failed #3943

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 4 commits into from
Sep 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,8 @@ export class PluginNativeDirNames {
}

export const PODFILE_NAME = "Podfile";

export class IosProjectConstants {
public static XcodeProjExtName = ".xcodeproj";
public static XcodeSchemeExtName = ".xcscheme";
}
16 changes: 13 additions & 3 deletions lib/declarations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -773,17 +773,27 @@ interface IProjectNameService {
ensureValidName(projectName: string, validateOptions?: { force: boolean }): Promise<string>;
}

/**
* Describes options that can be passed to xcprojService.verifyXcproj method.
*/
interface IVerifyXcprojOptions {
/**
* Whether to fail with error message or not
*/
shouldFail: boolean;
}

/**
* Designed for getting information about xcproj.
*/
interface IXcprojService {
/**
* Checks whether the system needs xcproj to execute ios builds successfully.
* In case the system does need xcproj but does not have it, prints an error message.
* @param {boolean} whether to fail with error message or not
* @param {IVerifyXcprojOptions} opts whether to fail with error message or not
* @return {Promise<boolean>} whether an error occurred or not.
*/
verifyXcproj(shouldFail: boolean): Promise<boolean>;
verifyXcproj(opts: IVerifyXcprojOptions): Promise<boolean>;
/**
* Collects information about xcproj.
* @return {Promise<XcprojInfo>} collected info about xcproj.
Expand Down Expand Up @@ -903,4 +913,4 @@ interface IRuntimeGradleVersions {

interface INetworkConnectivityValidator {
validate(): Promise<void>;
}
}
8 changes: 8 additions & 0 deletions lib/definitions/project.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,14 @@ interface ICocoaPodsService {
* @returns {string} Path to project's Podfile.
*/
getProjectPodfilePath(nativeProjectPath: string): string;

/**
* Executes `pod install` or `sanboxpod install` in the passed project.
* @param {string} projectRoot The root directory of the native iOS project.
* @param {string} xcodeProjPath The full path to the .xcodeproj file.
* @returns {Promise<ISpawnResult>} Information about the spawned process.
*/
executePodInstall(projectRoot: string, xcodeProjPath: string): Promise<ISpawnResult>;
}

interface IRubyFunction {
Expand Down
34 changes: 33 additions & 1 deletion lib/services/cocoapods-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ export class CocoaPodsService implements ICocoaPodsService {
private static PODFILE_POST_INSTALL_SECTION_NAME = "post_install";
private static INSTALLER_BLOCK_PARAMETER_NAME = "installer";

constructor(private $fs: IFileSystem) { }
constructor(private $fs: IFileSystem,
private $childProcess: IChildProcess,
private $errors: IErrors,
private $xcprojService: IXcprojService,
private $logger: ILogger,
private $config: IConfiguration) { }

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

public async executePodInstall(projectRoot: string, xcodeProjPath: string): Promise<ISpawnResult> {
// Check availability
try {
await this.$childProcess.exec("which pod");
await this.$childProcess.exec("which xcodeproj");
} catch (e) {
this.$errors.failWithoutHelp("CocoaPods or ruby gem 'xcodeproj' is not installed. Run `sudo gem install cocoapods` and try again.");
}

await this.$xcprojService.verifyXcproj({ shouldFail: true });

this.$logger.info("Installing pods...");
const podTool = this.$config.USE_POD_SANDBOX ? "sandbox-pod" : "pod";
// 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, ["install"], "close", { cwd: projectRoot, stdio: ['pipe', process.stdout, process.stdout] }, { throwError: false });

if (podInstallResult.exitCode !== 0) {
this.$errors.failWithoutHelp(`'${podTool} install' command failed.${podInstallResult.stderr ? " Error is: " + podInstallResult.stderr : ""}`);
}

if ((await this.$xcprojService.getXcprojInfo()).shouldUseXcproj) {
await this.$childProcess.spawnFromEvent("xcproj", ["--project", xcodeProjPath, "touch"], "close");
}

return podInstallResult;
}

public async applyPluginPodfileToProject(pluginData: IPluginData, projectData: IProjectData, nativeProjectPath: string): Promise<void> {
const pluginPodFilePath = this.getPluginPodfilePath(pluginData);
if (!this.$fs.exists(pluginPodFilePath)) {
Expand Down
72 changes: 13 additions & 59 deletions lib/services/ios-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { IOSProvisionService } from "./ios-provision-service";
import { IOSEntitlementsService } from "./ios-entitlements-service";
import { XCConfigService } from "./xcconfig-service";
import * as mobileprovision from "ios-mobileprovision-finder";
import { BUILD_XCCONFIG_FILE_NAME } from "../constants";
import { BUILD_XCCONFIG_FILE_NAME, IosProjectConstants } from "../constants";

interface INativeSourceCodeGroup {
name: string;
Expand All @@ -22,8 +22,6 @@ interface INativeSourceCodeGroup {
}

export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService {
private static XCODE_PROJECT_EXT_NAME = ".xcodeproj";
private static XCODE_SCHEME_EXT_NAME = ".xcscheme";
private static XCODEBUILD_MIN_VERSION = "6.0";
private static IOS_PROJECT_NAME_PLACEHOLDER = "__PROJECT_NAME__";
private static IOS_PLATFORM_NAME = "ios";
Expand All @@ -36,7 +34,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
private $injector: IInjector,
$projectDataService: IProjectDataService,
private $prompter: IPrompter,
private $config: IConfiguration,
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
private $devicesService: Mobile.IDevicesService,
private $mobileHelper: Mobile.IMobileHelper,
Expand Down Expand Up @@ -174,21 +171,21 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
}
this.replaceFileName("-Prefix.pch", projectRootFilePath, projectData);

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

if (this.$fs.exists(xcschemeFilePath)) {
this.$logger.debug("Found shared scheme at xcschemeFilePath, renaming to match project name.");
this.$logger.debug("Checkpoint 0");
this.replaceFileContent(xcschemeFilePath, projectData);
this.$logger.debug("Checkpoint 1");
this.replaceFileName(IOSProjectService.XCODE_SCHEME_EXT_NAME, xcschemeDirPath, projectData);
this.replaceFileName(IosProjectConstants.XcodeSchemeExtName, xcschemeDirPath, projectData);
this.$logger.debug("Checkpoint 2");
} else {
this.$logger.debug("Copying xcscheme from template not found at " + xcschemeFilePath);
}

this.replaceFileName(IOSProjectService.XCODE_PROJECT_EXT_NAME, this.getPlatformData(projectData).projectRoot, projectData);
this.replaceFileName(IosProjectConstants.XcodeProjExtName, this.getPlatformData(projectData).projectRoot, projectData);

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

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

private getPluginsDebugXcconfigFilePath(projectData: IProjectData): string {
Expand Down Expand Up @@ -941,7 +938,9 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
await this.prepareResources(pluginPlatformsFolderPath, pluginData, projectData);
await this.prepareFrameworks(pluginPlatformsFolderPath, pluginData, projectData);
await this.prepareStaticLibs(pluginPlatformsFolderPath, pluginData, projectData);
await this.prepareCocoapods(pluginPlatformsFolderPath, pluginData, projectData);

const projectRoot = this.getPlatformData(projectData).projectRoot;
await this.$cocoapodsService.applyPluginPodfileToProject(pluginData, projectData, projectRoot);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was included in prepareCocoapods method

if (opts && opts.executePodInstall && this.$fs.exists(pluginPodFilePath)) {	
			await this.executePodInstall(projectData);	
		}

but I can't see to call it in the new implementation. Maybe I'm missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opts were never passed, so I just removed this code

}

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

if (!this.$fs.exists(xcuserDataPath) && !this.$fs.exists(sharedDataPath)) {
this.$logger.info("Creating project scheme...");
Expand All @@ -969,7 +969,7 @@ 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.executePodInstall(projectData);
await this.$cocoapodsService.executePodInstall(projectRoot, xcodeProjPath);
}
}

Expand Down Expand Up @@ -1070,43 +1070,6 @@ 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 executePodInstall(projectData: IProjectData): Promise<any> {
// Check availability
try {
await this.$childProcess.exec("which pod");
await this.$childProcess.exec("which xcodeproj");
} catch (e) {
this.$errors.failWithoutHelp("CocoaPods or ruby gem 'xcodeproj' is not installed. Run `sudo gem install cocoapods` and try again.");
}

await this.$xcprojService.verifyXcproj(true);

this.$logger.info("Installing pods...");
const podTool = this.$config.USE_POD_SANDBOX ? "sandbox-pod" : "pod";
const childProcess = await this.$childProcess.spawnFromEvent(podTool, ["install"], "close", { cwd: this.getPlatformData(projectData).projectRoot, stdio: ['pipe', process.stdout, 'pipe'] });
if (childProcess.stderr) {
const warnings = childProcess.stderr.match(/(\u001b\[(?:\d*;){0,5}\d*m[\s\S]+?\u001b\[(?:\d*;){0,5}\d*m)|(\[!\].*?\n)|(.*?warning.*)/gi);
_.each(warnings, (warning: string) => {
this.$logger.warnWithLabel(warning.replace("\n", ""));
});

let errors = childProcess.stderr;
_.each(warnings, warning => {
errors = errors.replace(warning, "");
});

if (errors.trim()) {
this.$errors.failWithoutHelp(`Pod install command failed. Error output: ${errors}`);
}
}

if ((await this.$xcprojService.getXcprojInfo()).shouldUseXcproj) {
await this.$childProcess.spawnFromEvent("xcproj", ["--project", this.getXcodeprojPath(projectData), "touch"], "close");
}

return childProcess;
}

private async prepareNativeSourceCode(pluginName: string, pluginPlatformsFolderPath: string, projectData: IProjectData): Promise<void> {
const project = this.createPbxProj(projectData);
const group = this.getRootGroup(pluginName, pluginPlatformsFolderPath);
Expand Down Expand Up @@ -1153,15 +1116,6 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
}
}

private async prepareCocoapods(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData, opts?: any): Promise<void> {
const projectRoot = this.getPlatformData(projectData).projectRoot;
await this.$cocoapodsService.applyPluginPodfileToProject(pluginData, projectData, projectRoot);
const pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile");

if (opts && opts.executePodInstall && this.$fs.exists(pluginPodFilePath)) {
await this.executePodInstall(projectData);
}
}
private removeNativeSourceCode(pluginPlatformsFolderPath: string, pluginData: IPluginData, projectData: IProjectData): void {
const project = this.createPbxProj(projectData);
const group = this.getRootGroup(pluginData.name, pluginPlatformsFolderPath);
Expand Down
4 changes: 2 additions & 2 deletions lib/services/xcproj-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ class XcprojService implements IXcprojService {
private $xcodeSelectService: IXcodeSelectService) {
}

public async verifyXcproj(shouldFail: boolean): Promise<boolean> {
public async verifyXcproj(opts: IVerifyXcprojOptions): Promise<boolean> {
const xcprojInfo = await this.getXcprojInfo();
if (xcprojInfo.shouldUseXcproj && !xcprojInfo.xcprojAvailable) {
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`;
if (shouldFail) {
if (opts.shouldFail) {
this.$errors.failWithoutHelp(errorMessage);
} else {
this.$logger.warn(errorMessage);
Expand Down
Loading