From 06ebd461f2916fed5d9462826181f52411212f2d Mon Sep 17 00:00:00 2001 From: TsvetanMilanov Date: Tue, 10 May 2016 12:20:31 +0300 Subject: [PATCH 1/2] Add check for Podfile before preparing platform When preparing iOS need to check if any plugin requires Cocoapods to be installed and configured correctly. If plugin contains podfile in the platforms/ios directory and Cocoapods is not installed or not configured correctly the platform prepare should fail. --- lib/services/platform-service.ts | 104 +++++++++++++++++-------------- test/npm-support.ts | 1 + test/platform-commands.ts | 1 + test/platform-service.ts | 1 + 4 files changed, 61 insertions(+), 46 deletions(-) diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 327e66bc0f..ed5dd49d1c 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -32,7 +32,9 @@ export class PlatformService implements IPlatformService { private $mobileHelper: Mobile.IMobileHelper, private $hostInfo: IHostInfo, private $xmlValidator: IXmlValidator, - private $npm: INodePackageManager) { } + private $npm: INodePackageManager, + private $sysInfo: ISysInfo, + private $staticConfig: Config.IStaticConfig) { } public addPlatforms(platforms: string[]): IFuture { return (() => { @@ -47,7 +49,7 @@ export class PlatformService implements IPlatformService { } private addPlatform(platformParam: string): IFuture { - return(() => { + return (() => { let [platform, version] = platformParam.split("@"); this.validatePlatform(platform); @@ -77,7 +79,7 @@ export class PlatformService implements IPlatformService { pathToSave: path.join(this.$projectData.platformsDir, platform) }; - if(this.$options.frameworkPath) { + if (this.$options.frameworkPath) { packageToInstall = this.$options.frameworkPath; } else { packageToInstall = platformData.frameworkPackageName; @@ -92,7 +94,7 @@ export class PlatformService implements IPlatformService { frameworkDir = path.resolve(frameworkDir); this.addPlatformCore(platformData, frameworkDir).wait(); - } catch(err) { + } catch (err) { this.$fs.deleteDirectory(platformPath).wait(); throw err; } finally { @@ -110,7 +112,7 @@ export class PlatformService implements IPlatformService { let isFrameworkPathDirectory = false, isFrameworkPathNotSymlinkedFile = false; - if(this.$options.frameworkPath) { + if (this.$options.frameworkPath) { let frameworkPathStats = this.$fs.getFsStats(this.$options.frameworkPath).wait(); isFrameworkPathDirectory = frameworkPathStats.isDirectory(); isFrameworkPathNotSymlinkedFile = !this.$options.symlink && frameworkPathStats.isFile(); @@ -122,7 +124,7 @@ export class PlatformService implements IPlatformService { let pathToTemplate = customTemplateOptions && customTemplateOptions.pathToTemplate; platformData.platformProjectService.createProject(path.resolve(sourceFrameworkDir), installedVersion, pathToTemplate).wait(); - if(isFrameworkPathDirectory || isFrameworkPathNotSymlinkedFile) { + if (isFrameworkPathDirectory || isFrameworkPathNotSymlinkedFile) { // Need to remove unneeded node_modules folder // One level up is the runtime module and one above is the node_modules folder. this.$fs.deleteDirectory(path.join(frameworkDir, "../../")).wait(); @@ -134,8 +136,8 @@ export class PlatformService implements IPlatformService { this.applyBaseConfigOption(platformData).wait(); - let frameworkPackageNameData: any = {version: installedVersion}; - if(customTemplateOptions) { + let frameworkPackageNameData: any = { version: installedVersion }; + if (customTemplateOptions) { frameworkPackageNameData.template = customTemplateOptions.selectedTemplate; } this.$projectDataService.setValue(platformData.frameworkPackageName, frameworkPackageNameData).wait(); @@ -145,14 +147,14 @@ export class PlatformService implements IPlatformService { private getPathToPlatformTemplate(selectedTemplate: string, frameworkPackageName: string): IFuture { return (() => { - if(!selectedTemplate) { + if (!selectedTemplate) { // read data from package.json's nativescript key // check the nativescript.tns-.template value let nativescriptPlatformData = this.$projectDataService.getValue(frameworkPackageName).wait(); selectedTemplate = nativescriptPlatformData && nativescriptPlatformData.template; } - if(selectedTemplate) { + if (selectedTemplate) { let tempDir = temp.mkdirSync("platform-template"); try { /* @@ -166,7 +168,7 @@ export class PlatformService implements IPlatformService { */ let pathToTemplate = this.$npm.install(selectedTemplate, tempDir).wait()[0][1]; return { selectedTemplate, pathToTemplate }; - } catch(err) { + } catch (err) { this.$logger.trace("Error while trying to install specified template: ", err); this.$errors.failWithoutHelp(`Unable to install platform template ${selectedTemplate}. Make sure the specified value is valid.`); } @@ -177,8 +179,8 @@ export class PlatformService implements IPlatformService { } public getInstalledPlatforms(): IFuture { - return(() => { - if(!this.$fs.exists(this.$projectData.platformsDir).wait()) { + return (() => { + if (!this.$fs.exists(this.$projectData.platformsDir).wait()) { return []; } @@ -209,11 +211,21 @@ export class PlatformService implements IPlatformService { //We need dev-dependencies here, so before-prepare hooks will be executed correctly. try { this.$pluginsService.ensureAllDependenciesAreInstalled().wait(); - } catch(err) { + } catch (err) { this.$logger.trace(err); this.$errors.failWithoutHelp(`Unable to install dependencies. Make sure your package.json is valid and all dependencies are correct. Error is: ${err.message}`); } + // Need to check if any plugin requires Cocoapods to be installed. + if (platform === "ios") { + _.each(this.$pluginsService.getAllInstalledPlugins().wait(), (pluginData: IPluginData) => { + if (this.$fs.exists(path.join(pluginData.pluginPlatformsFolderPath(platform), "Podfile")).wait() && + !this.$sysInfo.getSysInfo(this.$staticConfig.pathToPackageJson).wait().cocoapodVer) { + this.$errors.failWithoutHelp(`${pluginData.name} has Podfile and you don't have Cocoapods installed or it is not configured correctly. Please install Cocoapods and configure it correctly.`); + } + }); + } + return this.preparePlatformCore(platform).wait(); }).future()(); } @@ -250,23 +262,23 @@ export class PlatformService implements IPlatformService { } let hasTnsModulesInAppFolder = this.$fs.exists(path.join(appSourceDirectoryPath, constants.TNS_MODULES_FOLDER_NAME)).wait(); - if(hasTnsModulesInAppFolder && this.$projectData.dependencies && this.$projectData.dependencies[constants.TNS_CORE_MODULES_NAME]) { + if (hasTnsModulesInAppFolder && this.$projectData.dependencies && this.$projectData.dependencies[constants.TNS_CORE_MODULES_NAME]) { this.$logger.warn("You have tns_modules dir in your app folder and tns-core-modules in your package.json file. Tns_modules dir in your app folder will not be used and you can safely remove it."); - sourceFiles = sourceFiles.filter(source => !minimatch(source, `**/${constants.TNS_MODULES_FOLDER_NAME}/**`, {nocase: true})); + sourceFiles = sourceFiles.filter(source => !minimatch(source, `**/${constants.TNS_MODULES_FOLDER_NAME}/**`, { nocase: true })); } // verify .xml files are well-formed this.$xmlValidator.validateXmlFiles(sourceFiles).wait(); // Remove .ts and .js.map files - constants.LIVESYNC_EXCLUDED_FILE_PATTERNS.forEach(pattern => sourceFiles = sourceFiles.filter(file => !minimatch(file, pattern, {nocase: true}))); + constants.LIVESYNC_EXCLUDED_FILE_PATTERNS.forEach(pattern => sourceFiles = sourceFiles.filter(file => !minimatch(file, pattern, { nocase: true }))); let copyFileFutures = sourceFiles.map(source => { - let destinationPath = path.join(appDestinationDirectoryPath, path.relative(appSourceDirectoryPath, source)); - if (this.$fs.getFsStats(source).wait().isDirectory()) { - return this.$fs.createDirectory(destinationPath); - } - return this.$fs.copyFile(source, destinationPath); - }); + let destinationPath = path.join(appDestinationDirectoryPath, path.relative(appSourceDirectoryPath, source)); + if (this.$fs.getFsStats(source).wait().isDirectory()) { + return this.$fs.createDirectory(destinationPath); + } + return this.$fs.copyFile(source, destinationPath); + }); Future.wait(copyFileFutures); // Copy App_Resources to project root folder @@ -290,7 +302,7 @@ export class PlatformService implements IPlatformService { // Clean target node_modules folder. Not needed when bundling. this.$broccoliBuilder.cleanNodeModules(tnsModulesDestinationPath, platform); } - } catch(error) { + } catch (error) { this.$logger.debug(error); shell.rm("-rf", appDir); this.$errors.failWithoutHelp(`Processing node_modules failed. ${error}`); @@ -340,7 +352,7 @@ export class PlatformService implements IPlatformService { }).future()(); } - public copyLastOutput(platform: string, targetPath: string, settings: {isForDevice: boolean}): IFuture { + public copyLastOutput(platform: string, targetPath: string, settings: { isForDevice: boolean }): IFuture { return (() => { let packageFile: string; platform = platform.toLowerCase(); @@ -351,13 +363,13 @@ export class PlatformService implements IPlatformService { } else { packageFile = this.getLatestApplicationPackageForEmulator(platformData).wait().packageName; } - if(!packageFile || !this.$fs.exists(packageFile).wait()) { + if (!packageFile || !this.$fs.exists(packageFile).wait()) { this.$errors.failWithoutHelp("Unable to find built application. Try 'tns build %s'.", platform); } this.$fs.ensureDirectoryExists(path.dirname(targetPath)).wait(); - if(this.$fs.exists(targetPath).wait() && this.$fs.getFsStats(targetPath).wait().isDirectory()) { + if (this.$fs.exists(targetPath).wait() && this.$fs.getFsStats(targetPath).wait().isDirectory()) { let sourceFileName = path.basename(packageFile); this.$logger.trace(`Specified target path: '${targetPath}' is directory. Same filename will be used: '${sourceFileName}'.`); targetPath = path.join(targetPath, sourceFileName); @@ -389,7 +401,7 @@ export class PlatformService implements IPlatformService { return (() => { _.each(platforms, platformParam => { let [platform, version] = platformParam.split("@"); - if(this.isPlatformInstalled(platform).wait()) { + if (this.isPlatformInstalled(platform).wait()) { this.updatePlatform(platform, version).wait(); } else { this.addPlatform(platformParam).wait(); @@ -414,7 +426,7 @@ export class PlatformService implements IPlatformService { this.ensurePlatformInstalled(platform).wait(); let platformData = this.$platformsData.getPlatformData(platform); - this.$devicesService.initialize({platform: platform, deviceId: this.$options.device}).wait(); + this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }).wait(); let packageFile: string = null; let action = (device: Mobile.IDevice): IFuture => { @@ -494,7 +506,7 @@ export class PlatformService implements IPlatformService { } public validatePlatform(platform: string): void { - if(!platform) { + if (!platform) { this.$errors.fail("No platform specified."); } @@ -519,7 +531,7 @@ export class PlatformService implements IPlatformService { public ensurePlatformInstalled(platform: string): IFuture { return (() => { - if(!this.isPlatformInstalled(platform).wait()) { + if (!this.isPlatformInstalled(platform).wait()) { this.addPlatform(platform).wait(); } }).future()(); @@ -551,13 +563,13 @@ export class PlatformService implements IPlatformService { let packages = _.filter(candidates, candidate => { return _.contains(validPackageNames, candidate); }).map(currentPackage => { - currentPackage = path.join(buildOutputPath, currentPackage); + currentPackage = path.join(buildOutputPath, currentPackage); - return { - packageName: currentPackage, - time: this.$fs.getFsStats(currentPackage).wait().mtime - }; - }); + return { + packageName: currentPackage, + time: this.$fs.getFsStats(currentPackage).wait().mtime + }; + }); return packages; }).future()(); @@ -598,17 +610,17 @@ export class PlatformService implements IPlatformService { newVersion = (cachedPackageData && cachedPackageData.version) || newVersion; let canUpdate = platformData.platformProjectService.canUpdatePlatform(currentVersion, newVersion).wait(); - if(canUpdate) { - if(!semver.valid(newVersion)) { + if (canUpdate) { + if (!semver.valid(newVersion)) { this.$errors.fail("The version %s is not valid. The version should consists from 3 parts separated by dot.", newVersion); } - if(semver.gt(currentVersion, newVersion)) { // Downgrade + if (semver.gt(currentVersion, newVersion)) { // Downgrade let isUpdateConfirmed = this.$prompter.confirm(`You are going to downgrade to runtime v.${newVersion}. Are you sure?`, () => false).wait(); - if(isUpdateConfirmed) { + if (isUpdateConfirmed) { this.updatePlatformCore(platformData, currentVersion, newVersion, canUpdate).wait(); } - } else if(semver.eq(currentVersion, newVersion)) { + } else if (semver.eq(currentVersion, newVersion)) { this.$errors.fail("Current and new version are the same."); } else { this.updatePlatformCore(platformData, currentVersion, newVersion, canUpdate).wait(); @@ -623,9 +635,9 @@ export class PlatformService implements IPlatformService { private updatePlatformCore(platformData: IPlatformData, currentVersion: string, newVersion: string, canUpdate: boolean): IFuture { return (() => { let update = platformData.platformProjectService.updatePlatform(currentVersion, newVersion, canUpdate, this.addPlatform.bind(this), this.removePlatforms.bind(this)).wait(); - if(update) { + if (update) { // Remove old framework files - let oldFrameworkData = this.getFrameworkFiles(platformData, currentVersion).wait(); + let oldFrameworkData = this.getFrameworkFiles(platformData, currentVersion).wait(); _.each(oldFrameworkData.frameworkFiles, file => { let fileToDelete = path.join(platformData.projectRoot, file); @@ -659,7 +671,7 @@ export class PlatformService implements IPlatformService { // Update .tnsproject file this.$projectDataService.initialize(this.$projectData.projectDir); - this.$projectDataService.setValue(platformData.frameworkPackageName, {version: newVersion}).wait(); + this.$projectDataService.setValue(platformData.frameworkPackageName, { version: newVersion }).wait(); this.$logger.out("Successfully updated to version ", newVersion); } @@ -690,7 +702,7 @@ export class PlatformService implements IPlatformService { private applyBaseConfigOption(platformData: IPlatformData): IFuture { return (() => { - if(this.$options.baseConfig) { + if (this.$options.baseConfig) { let newConfigFile = path.resolve(this.$options.baseConfig); this.$logger.trace(`Replacing '${platformData.configurationFilePath}' with '${newConfigFile}'.`); this.$fs.copyFile(newConfigFile, platformData.configurationFilePath).wait(); diff --git a/test/npm-support.ts b/test/npm-support.ts index 6e76fd94ca..5b49c28d8d 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -51,6 +51,7 @@ function createTestInjector(): IInjector { testInjector.register("npmInstallationManager", {}); testInjector.register("lockfile", LockFile); testInjector.register("prompter", {}); + testInjector.register("sysInfo", {}); testInjector.register("androidProjectService", {}); testInjector.register("iOSProjectService", {}); testInjector.register("devicesService", {}); diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 98fd5e0bdc..36ce0841b9 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -102,6 +102,7 @@ function createTestInjector() { testInjector.register('devicesService', {}); testInjector.register('projectDataService', stubs.ProjectDataService); testInjector.register('prompter', {}); + testInjector.register('sysInfo', {}); testInjector.register('commands-service', CommandsServiceLib.CommandsService); testInjector.registerCommand("platform|add", PlatformAddCommandLib.AddPlatformCommand); testInjector.registerCommand("platform|remove", PlatformRemoveCommandLib.RemovePlatformCommand); diff --git a/test/platform-service.ts b/test/platform-service.ts index 4ed9a18a8b..09d1c4ea20 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -38,6 +38,7 @@ function createTestInjector() { testInjector.register('androidEmulatorServices', {}); testInjector.register('projectDataService', stubs.ProjectDataService); testInjector.register('prompter', {}); + testInjector.register('sysInfo', {}); testInjector.register('lockfile', stubs.LockFile); testInjector.register("commandsService", { tryExecuteCommand: () => { /* intentionally left blank */ } From 82a2bf1d8628a18e4f977877fb17341927b2fd91 Mon Sep 17 00:00:00 2001 From: TsvetanMilanov Date: Tue, 10 May 2016 12:45:24 +0300 Subject: [PATCH 2/2] Check if xcodeproj is required for project When preparing iOS and merging xcconfig files if the project requires xcodeproj to be used (Cocoapods version < 1.0.0 and Xcode version >= 7.3) need to check if xcodeproj is available and fail if it's not. --- lib/services/ios-project-service.ts | 88 +++++++++++++++++------------ lib/services/platform-service.ts | 2 +- 2 files changed, 54 insertions(+), 36 deletions(-) diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 692ce7b46c..875c9520e8 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -12,6 +12,7 @@ import * as helpers from "../common/helpers"; import * as projectServiceBaseLib from "./platform-project-service-base"; import Future = require("fibers/future"); import { PlistSession } from "plist-merge-patch"; +import {EOL} from "os"; export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService { private static XCODE_PROJECT_EXT_NAME = ".xcodeproj"; @@ -41,8 +42,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $mobileHelper: Mobile.IMobileHelper, private $pluginVariablesService: IPluginVariablesService, private $xcprojService: IXcprojService) { - super($fs, $projectData, $projectDataService); - } + super($fs, $projectData, $projectDataService); + } public get platformData(): IPlatformData { let projectRoot = path.join(this.$projectData.platformsDir, "ios"); @@ -67,7 +68,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ frameworkDirectoriesNames: ["Metadata", "metadataGenerator", "NativeScript", "internal"], targetedOS: ['darwin'], configurationFileName: "Info.plist", - configurationFilePath: path.join(projectRoot, this.$projectData.projectName, this.$projectData.projectName+"-Info.plist"), + configurationFilePath: path.join(projectRoot, this.$projectData.projectName, this.$projectData.projectName + "-Info.plist"), relativeToFrameworkConfigurationFilePath: path.join("__PROJECT_NAME__", "__PROJECT_NAME__-Info.plist"), fastLivesyncFileExtensions: [".tiff", ".tif", ".jpg", "jpeg", "gif", ".png", ".bmp", ".BMPf", ".ico", ".cur", ".xbm"] // https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/ }; @@ -77,7 +78,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return (() => { let frameworkVersion = this.getFrameworkVersion(this.platformData.frameworkPackageName).wait(); - if(semver.lt(frameworkVersion, "1.3.0")) { + if (semver.lt(frameworkVersion, "1.3.0")) { return path.join(this.platformData.projectRoot, this.$projectData.projectName, "Resources", "icons"); } @@ -89,17 +90,17 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return (() => { try { this.$childProcess.exec("which xcodebuild").wait(); - } catch(error) { + } catch (error) { this.$errors.fail("Xcode is not installed. Make sure you have Xcode installed and added to your PATH"); } let xcodeBuildVersion = this.$childProcess.exec("xcodebuild -version | head -n 1 | sed -e 's/Xcode //'").wait(); let splitedXcodeBuildVersion = xcodeBuildVersion.split("."); - if(splitedXcodeBuildVersion.length === 3) { + if (splitedXcodeBuildVersion.length === 3) { xcodeBuildVersion = util.format("%s.%s", splitedXcodeBuildVersion[0], splitedXcodeBuildVersion[1]); } - if(helpers.versionCompare(xcodeBuildVersion, IOSProjectService.XCODEBUILD_MIN_VERSION) < 0) { + if (helpers.versionCompare(xcodeBuildVersion, IOSProjectService.XCODEBUILD_MIN_VERSION) < 0) { this.$errors.fail("NativeScript can only run in Xcode version %s or greater", IOSProjectService.XCODEBUILD_MIN_VERSION); } @@ -109,13 +110,13 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ public createProject(frameworkDir: string, frameworkVersion: string, pathToTemplate?: string): IFuture { return (() => { this.$fs.ensureDirectoryExists(path.join(this.platformData.projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER)).wait(); - if(pathToTemplate) { + if (pathToTemplate) { // Copy everything except the template from the runtime this.$fs.readDirectory(frameworkDir).wait() .filter(dirName => dirName.indexOf(IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER) === -1) .forEach(dirName => shell.cp("-R", path.join(frameworkDir, dirName), this.platformData.projectRoot)); shell.cp("-rf", path.join(pathToTemplate, "*"), this.platformData.projectRoot); - } else if(this.$options.symlink) { + } else if (this.$options.symlink) { let xcodeProjectName = util.format("%s.xcodeproj", IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER); shell.cp("-R", path.join(frameworkDir, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER, "*"), path.join(this.platformData.projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER)); @@ -126,7 +127,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ _.each(frameworkFiles, (file: string) => { this.$fs.symlink(path.join(frameworkDir, file), path.join(this.platformData.projectRoot, file)).wait(); }); - } else { + } else { shell.cp("-R", path.join(frameworkDir, "*"), this.platformData.projectRoot); } }).future()(); @@ -140,7 +141,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ let projectRootFilePath = path.join(this.platformData.projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER); // Starting with NativeScript for iOS 1.6.0, the project Info.plist file resides not in the platform project, // but in the hello-world app template as a platform specific resource. - if(this.$fs.exists(path.join(projectRootFilePath, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + "-Info.plist")).wait()) { + if (this.$fs.exists(path.join(projectRootFilePath, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER + "-Info.plist")).wait()) { this.replaceFileName("-Info.plist", projectRootFilePath).wait(); } this.replaceFileName("-Prefix.pch", projectRootFilePath).wait(); @@ -173,7 +174,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ ]; let xcworkspacePath = path.join(projectRoot, this.$projectData.projectName + ".xcworkspace"); - if(this.$fs.exists(xcworkspacePath).wait()) { + if (this.$fs.exists(xcworkspacePath).wait()) { basicArgs.push("-workspace", xcworkspacePath); basicArgs.push("-scheme", this.$projectData.projectName); } else { @@ -219,7 +220,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ args.push(`PROVISIONING_PROFILE=${buildConfig.mobileProvisionIdentifier}`); } - this.$childProcess.spawnFromEvent("xcodebuild", args, "exit", {cwd: this.$options, stdio: 'inherit'}).wait(); + this.$childProcess.spawnFromEvent("xcodebuild", args, "exit", { cwd: this.$options, stdio: 'inherit' }).wait(); if (buildForDevice) { let buildOutputPath = path.join(projectRoot, "build", "device"); @@ -232,7 +233,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ "-o", path.join(buildOutputPath, this.$projectData.projectName + ".ipa") ]; - this.$childProcess.spawnFromEvent("xcrun", xcrunArgs, "exit", {cwd: this.$options, stdio: 'inherit'}).wait(); + this.$childProcess.spawnFromEvent("xcrun", xcrunArgs, "exit", { cwd: this.$options, stdio: 'inherit' }).wait(); } }).future()(); } @@ -257,7 +258,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ architectures.push('ONLY_ACTIVE_ARCH=NO'); } - buildConfig = buildConfig || { }; + buildConfig = buildConfig || {}; buildConfig.architectures = architectures; return this.buildProject(this.platformData.projectRoot, buildConfig); @@ -287,7 +288,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ let frameworkAddOptions: xcode.Options = { customFramework: true }; - if(isDynamic) { + if (isDynamic) { frameworkAddOptions["embed"] = true; project.updateBuildProperty("IPHONEOS_DEPLOYMENT_TARGET", "8.0"); this.$logger.info("The iOS Deployment Target is now 8.0 in order to support Cocoa Touch Frameworks."); @@ -338,9 +339,9 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ public updatePlatform(currentVersion: string, newVersion: string, canUpdate: boolean): IFuture { return (() => { - if(!canUpdate) { + if (!canUpdate) { let isUpdateConfirmed = this.$prompter.confirm(`We need to override xcodeproj file. The old one will be saved at ${this.$options.profileDir}. Are you sure?`, () => true).wait(); - if(isUpdateConfirmed) { + if (isUpdateConfirmed) { // Copy old file to options["profile-dir"] let sourceDir = this.xcodeprojPath; let destinationDir = path.join(this.$options.profileDir, "xcodeproj"); @@ -370,7 +371,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ let project = this.createPbxProj(); let resources = project.pbxGroupByName("Resources"); - if(resources) { + if (resources) { let references = project.pbxFileReferenceSection(); let xcodeProjectImages = _.map(resources.children, resource => this.replace(references[resource.value].name)); @@ -430,7 +431,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ this.$logger.trace("Info.plist: app/App_Resources/iOS/Info.plist is missing. Upgrading the source of the project with one from the new project template. Copy " + templateInfoPlist + " to " + infoPlistPath); try { this.$fs.copyFile(templateInfoPlist, infoPlistPath).wait(); - } catch(e) { + } catch (e) { this.$logger.trace("Copying template's Info.plist failed. " + e); } } else { @@ -481,7 +482,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ CFBundleIdentifier - ${ this.$projectData.projectId } + ${ this.$projectData.projectId} ` }); @@ -516,8 +517,8 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } private replace(name: string): string { - if(_.startsWith(name, '"')) { - name = name.substr(1, name.length-2); + if (_.startsWith(name, '"')) { + name = name.substr(1, name.length - 2); } return name.replace(/\\\"/g, "\""); @@ -541,7 +542,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } private savePbxProj(project: any): IFuture { - return this.$fs.writeFile(this.pbxProjPath, project.writeSync()); + return this.$fs.writeFile(this.pbxProjPath, project.writeSync()); } public preparePluginNativeCode(pluginData: IPluginData, opts?: any): IFuture { @@ -566,19 +567,22 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ public afterPrepareAllPlugins(): IFuture { return (() => { - if(this.$fs.exists(this.projectPodFilePath).wait()) { + if (this.$fs.exists(this.projectPodFilePath).wait()) { let projectPodfileContent = this.$fs.readText(this.projectPodFilePath).wait(); this.$logger.trace("Project Podfile content"); this.$logger.trace(projectPodfileContent); let firstPostInstallIndex = projectPodfileContent.indexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME); - if(firstPostInstallIndex !== -1 && firstPostInstallIndex !== projectPodfileContent.lastIndexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME)) { + if (firstPostInstallIndex !== -1 && firstPostInstallIndex !== projectPodfileContent.lastIndexOf(IOSProjectService.PODFILE_POST_INSTALL_SECTION_NAME)) { this.$logger.warn(`Podfile contains more than one post_install sections. You need to open ${this.projectPodFilePath} file and manually resolve this issue.`); } let xcuserDataPath = path.join(this.xcodeprojPath, "xcuserdata"); - if(!this.$fs.exists(xcuserDataPath).wait()) { + if (!this.$fs.exists(xcuserDataPath).wait()) { this.$logger.info("Creating project scheme..."); + + this.checkIfXcodeprojIsRequired().wait(); + let createSchemeRubyScript = `ruby -e "require 'xcodeproj'; xcproj = Xcodeproj::Project.open('${this.$projectData.projectName}.xcodeproj'); xcproj.recreate_user_schemes; xcproj.save"`; this.$childProcess.exec(createSchemeRubyScript, { cwd: this.platformData.projectRoot }).wait(); } @@ -651,7 +655,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ try { this.$childProcess.exec("gem which cocoapods").wait(); this.$childProcess.exec("gem which xcodeproj").wait(); - } catch(e) { + } catch (e) { this.$errors.failWithoutHelp("CocoaPods or ruby gem 'xcodeproj' is not installed. Run `sudo gem install cocoapods` and try again."); } @@ -659,7 +663,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ this.$logger.info("Installing pods..."); let podTool = this.$config.USE_POD_SANDBOX ? "sandbox-pod" : "pod"; - let childProcess = this.$childProcess.spawnFromEvent(podTool, ["install"], "close", { cwd: this.platformData.projectRoot, stdio: ['pipe', process.stdout, 'pipe'] }).wait(); + let childProcess = this.$childProcess.spawnFromEvent(podTool, ["install"], "close", { cwd: this.platformData.projectRoot, stdio: ['pipe', process.stdout, 'pipe'] }).wait(); if (childProcess.stderr) { let 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) => { @@ -671,7 +675,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ errors = errors.replace(warning, ""); }); - if(errors.trim()) { + if (errors.trim()) { this.$errors.failWithoutHelp(`Pod install command failed. Error output: ${errors}`); } } @@ -699,7 +703,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private prepareCocoapods(pluginPlatformsFolderPath: string, opts?: any): IFuture { return (() => { let pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile"); - if(this.$fs.exists(pluginPodFilePath).wait()) { + if (this.$fs.exists(pluginPodFilePath).wait()) { let pluginPodFileContent = this.$fs.readText(pluginPodFilePath).wait(), pluginPodFilePreparedContent = this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent), projectPodFileContent = this.$fs.exists(this.projectPodFilePath).wait() ? this.$fs.readText(this.projectPodFilePath).wait() : ""; @@ -726,7 +730,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } } - if(opts && opts.executePodInstall && this.$fs.exists(pluginPodFilePath).wait()) { + if (opts && opts.executePodInstall && this.$fs.exists(pluginPodFilePath).wait()) { this.executePodInstall().wait(); } }).future()(); @@ -765,12 +769,12 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private removeCocoapods(pluginPlatformsFolderPath: string): IFuture { return (() => { let pluginPodFilePath = path.join(pluginPlatformsFolderPath, "Podfile"); - if(this.$fs.exists(pluginPodFilePath).wait() && this.$fs.exists(this.projectPodFilePath).wait()) { + if (this.$fs.exists(pluginPodFilePath).wait() && this.$fs.exists(this.projectPodFilePath).wait()) { let pluginPodFileContent = this.$fs.readText(pluginPodFilePath).wait(); let projectPodFileContent = this.$fs.readText(this.projectPodFilePath).wait(); - let contentToRemove= this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent); + let contentToRemove = this.buildPodfileContent(pluginPodFilePath, pluginPodFileContent); projectPodFileContent = helpers.stringReplaceAll(projectPodFileContent, contentToRemove, ""); - if(projectPodFileContent.trim() === `use_frameworks!${os.EOL}${os.EOL}target "${this.$projectData.projectName}" do${os.EOL}${os.EOL}end`) { + if (projectPodFileContent.trim() === `use_frameworks!${os.EOL}${os.EOL}target "${this.$projectData.projectName}" do${os.EOL}${os.EOL}end`) { this.$fs.deleteFile(this.projectPodFilePath).wait(); } else { this.$fs.writeFile(this.projectPodFilePath, projectPodFileContent).wait(); @@ -806,6 +810,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ this.$fs.writeFile(projectFile, "").wait(); } + this.checkIfXcodeprojIsRequired().wait(); let escapedProjectFile = projectFile.replace(/'/g, "\\'"), escapedPluginFile = pluginFile.replace(/'/g, "\\'"), mergeScript = `require 'xcodeproj'; Xcodeproj::Config.new('${escapedProjectFile}').merge(Xcodeproj::Config.new('${escapedPluginFile}')).save_as(Pathname.new('${escapedProjectFile}'))`; @@ -842,6 +847,19 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } }).future()(); } + + private checkIfXcodeprojIsRequired(): IFuture { + return (() => { + let xcprojInfo = this.$xcprojService.getXcprojInfo().wait(); + if (xcprojInfo.shouldUseXcproj && !xcprojInfo.xcprojAvailable) { + let errorMessage = `You are using CocoaPods version ${xcprojInfo.cocoapodVer} which does not support Xcode ${xcprojInfo.xcodeVersion.major}.${xcprojInfo.xcodeVersion.minor} yet.${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`; + + this.$errors.failWithoutHelp(errorMessage); + + return true; + } + }).future()(); + } } $injector.register("iOSProjectService", IOSProjectService); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index ed5dd49d1c..481bffbd01 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -221,7 +221,7 @@ export class PlatformService implements IPlatformService { _.each(this.$pluginsService.getAllInstalledPlugins().wait(), (pluginData: IPluginData) => { if (this.$fs.exists(path.join(pluginData.pluginPlatformsFolderPath(platform), "Podfile")).wait() && !this.$sysInfo.getSysInfo(this.$staticConfig.pathToPackageJson).wait().cocoapodVer) { - this.$errors.failWithoutHelp(`${pluginData.name} has Podfile and you don't have Cocoapods installed or it is not configured correctly. Please install Cocoapods and configure it correctly.`); + this.$errors.failWithoutHelp(`${pluginData.name} has Podfile and you don't have Cocoapods installed or it is not configured correctly. Please verify Cocoapods can work on your machine.`); } }); }