diff --git a/.travis.yml b/.travis.yml index ea391870ad..109ed1d663 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ branches: only: - master - release + - release-patch env: global: - DATE=$(date +%Y-%m-%d) @@ -58,3 +59,10 @@ deploy: branch: release api_key: secure: "FM9QLOkFq6JpHlfHkT1i2Ht1ZlttZLq7K3kQNLabw7Z5+BPMcy/f3LRJkAkYMezrKLKRkq1uXmhY0BapoTnR9AVEO/t4g6dtbZ1TZ3xBH/HHnFofTFubOrc7+61DJzKduYtnQ/sn3EEOkN8jrXSY9uas4qZh7PSm1hcfjPI8gdI=" +- provider: npm + skip_cleanup: true + email: nativescript@telerik.com + on: + branch: release-patch + api_key: + secure: "FM9QLOkFq6JpHlfHkT1i2Ht1ZlttZLq7K3kQNLabw7Z5+BPMcy/f3LRJkAkYMezrKLKRkq1uXmhY0BapoTnR9AVEO/t4g6dtbZ1TZ3xBH/HHnFofTFubOrc7+61DJzKduYtnQ/sn3EEOkN8jrXSY9uas4qZh7PSm1hcfjPI8gdI=" diff --git a/.travis/add-publishConfig.js b/.travis/add-publishConfig.js index 306595c06a..7d03f227f1 100644 --- a/.travis/add-publishConfig.js +++ b/.travis/add-publishConfig.js @@ -2,20 +2,33 @@ const fsModule = require("fs"); const path = "./package.json"; -const fileOptions = {encoding: "utf-8"}; +const fileOptions = { encoding: "utf-8" }; const content = fsModule.readFileSync(path, fileOptions); const packageDef = JSON.parse(content); if (!packageDef.publishConfig) { - packageDef.publishConfig = {}; + packageDef.publishConfig = {}; } const branch = process.argv[2]; if (!branch) { - console.log("Please pass the branch name as an argument!"); - process.exit(1); + console.log("Please pass the branch name as an argument!"); + process.exit(1); +} + +switch (branch) { + case "release": + packageDef.publishConfig.tag = "rc"; + break; + case "release-patch": + packageDef.publishConfig.tag = "patch"; + break; + case "master": + packageDef.publishConfig.tag = "next"; + break; + default: + throw new Error(`Unable to publish as the branch ${branch} does not have corresponding tag. Supported branches are master (next tag), release (rc tag) and release-patch (patch tag)`); } -packageDef.publishConfig.tag = branch === "release" ? "rc" : "next"; const newContent = JSON.stringify(packageDef, null, " "); fsModule.writeFileSync(path, newContent, fileOptions); diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index cd6dd4a31e..2fe9bdd2f9 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -83,6 +83,14 @@ interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter { */ shouldInstall(device: Mobile.IDevice, projectData: IProjectData, release: IRelease, outputPath?: string): Promise; + /** + * + * @param {Mobile.IDevice} device The device where the application should be installed. + * @param {IProjectData} projectData DTO with information about the project. + * @param {string} @optional outputPath Directory containing build information and artifacts. + */ + validateInstall(device: Mobile.IDevice, projectData: IProjectData, release: IRelease, outputPath?: string): Promise; + /** * Determines whether the project should undergo the prepare process. * @param {IShouldPrepareInfo} shouldPrepareInfo Options needed to decide whether to prepare. @@ -307,6 +315,13 @@ interface INodeModulesDependenciesBuilder { interface IBuildInfo { prepareTime: string; buildTime: string; + /** + * Currently it is used only for iOS. + * As `xcrun` command does not throw an error when IPHONEOS_DEPLOYMENT_TARGET is provided in `xcconfig` file and + * the simulator's version does not match IPHONEOS_DEPLOYMENT_TARGET's value, we need to save it to buildInfo file + * in order check it on livesync and throw an error to the user. + */ + deploymentTarget?: string; } interface IPlatformDataComposition { diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 859fd352e3..c2d89f1611 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -449,6 +449,12 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS * Traverse through the production dependencies and find plugins that need build/rebuild */ checkIfPluginsNeedBuild(projectData: IProjectData): Promise>; + + /** + * Get the deployment target's version + * Currently implemented only for iOS -> returns the value of IPHONEOS_DEPLOYMENT_TARGET property from xcconfig file + */ + getDeploymentTarget(projectData: IProjectData): any; } interface IValidatePlatformOutput { diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 5c7a3f1ea3..c8cfd0062a 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -665,6 +665,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject // Nothing android specific to check yet. } + public getDeploymentTarget(projectData: IProjectData): semver.SemVer { return; } + private copy(projectRoot: string, frameworkDir: string, files: string, cpArg: string): void { const paths = files.split(' ').map(p => path.join(frameworkDir, p)); shell.cp(cpArg, paths, projectRoot); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 2335342b8e..deeb4552e8 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -384,12 +384,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } private async buildForDevice(projectRoot: string, args: string[], buildConfig: IBuildConfig, projectData: IProjectData): Promise { - const defaultArchitectures = [ - 'ARCHS=armv7 arm64', - 'VALID_ARCHS=armv7 arm64' - ]; - - // build only for device specific architecture if (!buildConfig.release && !buildConfig.architectures) { await this.$devicesService.initialize({ platform: this.$devicePlatformsConstants.iOS.toLowerCase(), deviceId: buildConfig.device, @@ -402,10 +396,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ .uniq() .value(); if (devicesArchitectures.length > 0) { - const architectures = [ - `ARCHS=${devicesArchitectures.join(" ")}`, - `VALID_ARCHS=${devicesArchitectures.join(" ")}` - ]; + const architectures = this.getBuildArchitectures(projectData, buildConfig, devicesArchitectures); if (devicesArchitectures.length > 1) { architectures.push('ONLY_ACTIVE_ARCH=NO'); } @@ -413,7 +404,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } } - args = args.concat((buildConfig && buildConfig.architectures) || defaultArchitectures); + args = args.concat((buildConfig && buildConfig.architectures) || this.getBuildArchitectures(projectData, buildConfig, ["armv7", "arm64"])); args = args.concat([ "-sdk", "iphoneos", @@ -457,6 +448,28 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return commandResult; } + private getBuildArchitectures(projectData: IProjectData, buildConfig: IBuildConfig, architectures: string[]): string[] { + let result: string[] = []; + + const frameworkVersion = this.getFrameworkVersion(projectData); + if (semver.valid(frameworkVersion) && semver.lt(semver.coerce(frameworkVersion), "5.1.0")) { + const target = this.getDeploymentTarget(projectData); + if (target && target.major >= 11) { + // We need to strip 32bit architectures as of deployment target >= 11 it is not allowed to have such + architectures = _.filter(architectures, arch => { + const is64BitArchitecture = arch === "x86_64" || arch === "arm64"; + if (!is64BitArchitecture) { + this.$logger.warn(`The architecture ${arch} will be stripped as it is not supported for deployment target ${target.version}.`); + } + return is64BitArchitecture; + }); + } + result = [`ARCHS=${architectures.join(" ")}`, `VALID_ARCHS=${architectures.join(" ")}`]; + } + + return result; + } + private async setupSigningFromTeam(projectRoot: string, projectData: IProjectData, teamId: string) { const xcode = this.$pbxprojDomXcode.Xcode.open(this.getPbxProjPath(projectData)); const signing = xcode.getSigning(projectData.projectName); @@ -555,13 +568,14 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } private async buildForSimulator(projectRoot: string, args: string[], projectData: IProjectData, buildConfig?: IBuildConfig): Promise { + const architectures = this.getBuildArchitectures(projectData, buildConfig, ["i386", "x86_64"]); + args = args + .concat(architectures) .concat([ "build", "-configuration", buildConfig.release ? "Release" : "Debug", "-sdk", "iphonesimulator", - "ARCHS=i386 x86_64", - "VALID_ARCHS=i386 x86_64", "ONLY_ACTIVE_ARCH=NO", "CONFIGURATION_BUILD_DIR=" + path.join(projectRoot, "build", "emulator"), "CODE_SIGN_IDENTITY=", @@ -1031,6 +1045,15 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f return []; } + public getDeploymentTarget(projectData: IProjectData): semver.SemVer { + const target = this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "IPHONEOS_DEPLOYMENT_TARGET"); + if (!target) { + return null; + } + + return semver.coerce(target); + } + private getAllLibsForPluginWithFileExtension(pluginData: IPluginData, fileExtension: string): string[] { const filterCallback = (fileName: string, pluginPlatformsFolderPath: string) => path.extname(fileName) === fileExtension; return this.getAllNativeLibrariesForPlugin(pluginData, IOSProjectService.IOS_PLATFORM_NAME, filterCallback); diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 1875af6aed..69654a1264 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -475,6 +475,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi }); } + await this.$platformService.validateInstall(options.device, options.projectData, options, options.deviceBuildInfoDescriptor.outputPath); const shouldInstall = await this.$platformService.shouldInstall(options.device, options.projectData, options, options.deviceBuildInfoDescriptor.outputPath); if (shouldInstall) { await this.$platformService.installApplication(options.device, { release: false }, options.projectData, pathToBuildItem, options.deviceBuildInfoDescriptor.outputPath); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index d46fc27e6f..2a433e55c0 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -436,13 +436,20 @@ export class PlatformService extends EventEmitter implements IPlatformService { public saveBuildInfoFile(platform: string, projectDir: string, buildInfoFileDirname: string): void { const buildInfoFile = path.join(buildInfoFileDirname, buildInfoFileName); + const projectData = this.$projectDataService.getProjectData(projectDir); + const platformData = this.$platformsData.getPlatformData(platform, projectData); - const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, this.$projectDataService.getProjectData(projectDir)); - const buildInfo = { + const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); + const buildInfo: IBuildInfo = { prepareTime: prepareInfo.changesRequireBuildTime, buildTime: new Date().toString() }; + const deploymentTarget = platformData.platformProjectService.getDeploymentTarget(projectData); + if (deploymentTarget) { + buildInfo.deploymentTarget = deploymentTarget.version; + } + this.$fs.writeJson(buildInfoFile, buildInfo); } @@ -455,9 +462,21 @@ export class PlatformService extends EventEmitter implements IPlatformService { const platformData = this.$platformsData.getPlatformData(platform, projectData); const deviceBuildInfo: IBuildInfo = await this.getDeviceBuildInfo(device, projectData); const localBuildInfo = this.getBuildInfo(platform, platformData, { buildForDevice: !device.isEmulator, release: release.release }, outputPath); + return !localBuildInfo || !deviceBuildInfo || deviceBuildInfo.buildTime !== localBuildInfo.buildTime; } + public async validateInstall(device: Mobile.IDevice, projectData: IProjectData, release: IRelease, outputPath?: string): Promise { + const platform = device.deviceInfo.platform; + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const localBuildInfo = this.getBuildInfo(device.deviceInfo.platform, platformData, { buildForDevice: !device.isEmulator, release: release.release }, outputPath); + if (localBuildInfo.deploymentTarget) { + if (semver.lt(semver.coerce(device.deviceInfo.version), semver.coerce(localBuildInfo.deploymentTarget))) { + this.$errors.fail(`Unable to install on device with version ${device.deviceInfo.version} as deployment target is ${localBuildInfo.deploymentTarget}`); + } + } + } + public async installApplication(device: Mobile.IDevice, buildConfig: IBuildConfig, projectData: IProjectData, packageFile?: string, outputFilePath?: string): Promise { this.$logger.out(`Installing on device ${device.deviceInfo.identifier}...`); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index be79c7160a..57ab93eba3 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nativescript", - "version": "5.1.0", + "version": "5.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index fe06f30a1b..7648f24e6a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "5.1.0", + "version": "5.1.1", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index f6431d517a..93c9ab6a5c 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -1055,3 +1055,158 @@ describe("Merge Project XCConfig files", () => { } }); }); + +describe("buildProject", () => { + let xcodeBuildCommandArgs: string[] = []; + + function setup(data: { frameworkVersion: string, deploymentTarget: string, devices?: Mobile.IDevice[] }): IInjector { + const projectPath = "myTestProjectPath"; + const projectName = "myTestProjectName"; + const testInjector = createTestInjector(projectPath, projectName); + + const childProcess = testInjector.resolve("childProcess"); + childProcess.spawnFromEvent = (command: string, args: string[]) => { + if (command === "xcodebuild" && args[0] !== "-exportArchive") { + xcodeBuildCommandArgs = args; + } + }; + + const projectDataService = testInjector.resolve("projectDataService"); + projectDataService.getNSValue = (projectDir: string, propertyName: string) => { + if (propertyName === "tns-ios") { + return { + name: "tns-ios", + version: data.frameworkVersion + }; + } + }; + + const projectData = testInjector.resolve("projectData"); + projectData.appResourcesDirectoryPath = path.join(projectPath, "app", "App_Resources"); + + const devicesService = testInjector.resolve("devicesService"); + devicesService.initialize = () => ({}); + devicesService.getDeviceInstances = () => data.devices || []; + + const xCConfigService = testInjector.resolve("xCConfigService"); + xCConfigService.readPropertyValue = (projectDir: string, propertyName: string) => { + if (propertyName === "IPHONEOS_DEPLOYMENT_TARGET") { + return data.deploymentTarget; + } + }; + + const pbxprojDomXcode = testInjector.resolve("pbxprojDomXcode"); + pbxprojDomXcode.Xcode = { + open: () => ({ + getSigning: () => ({}), + setAutomaticSigningStyle: () => ({}), + save: () => ({}) + }) + }; + + const iOSProvisionService = testInjector.resolve("iOSProvisionService"); + iOSProvisionService.getDevelopmentTeams = () => ({}); + iOSProvisionService.getTeamIdsWithName = () => ({}); + + return testInjector; + } + + function executeTests(testCases: any[], data: { buildForDevice: boolean }) { + _.each(testCases, testCase => { + it(`${testCase.name}`, async () => { + const testInjector = setup({ frameworkVersion: testCase.frameworkVersion, deploymentTarget: testCase.deploymentTarget }); + const projectData: IProjectData = testInjector.resolve("projectData"); + + const iOSProjectService = testInjector.resolve("iOSProjectService"); + (iOSProjectService).getExportOptionsMethod = () => ({}); + await iOSProjectService.buildProject("myProjectRoot", projectData, { buildForDevice: data.buildForDevice }); + + const archsItem = xcodeBuildCommandArgs.find(item => item.startsWith("ARCHS=")); + if (testCase.expectedArchs) { + const archsValue = archsItem.split("=")[1]; + assert.deepEqual(archsValue, testCase.expectedArchs); + } else { + assert.deepEqual(undefined, archsItem); + } + }); + }); + } + + describe("for device", () => { + afterEach(() => { + xcodeBuildCommandArgs = []; + }); + + const testCases = [{ + name: "shouldn't exclude armv7 architecture when deployment target 10", + frameworkVersion: "5.0.0", + deploymentTarget: "10.0", + expectedArchs: "armv7 arm64" + }, { + name: "should exclude armv7 architecture when deployment target is 11", + frameworkVersion: "5.0.0", + deploymentTarget: "11.0", + expectedArchs: "arm64" + }, { + name: "shouldn't pass architecture to xcodebuild command when frameworkVersion is 5.1.0", + frameworkVersion: "5.1.0", + deploymentTarget: "11.0" + }, { + name: "should pass only 64bit architecture to xcodebuild command when frameworkVersion is 5.0.0 and deployment target is 11.0", + frameworkVersion: "5.0.0", + deploymentTarget: "11.0", + expectedArchs: "arm64" + }, { + name: "should pass both architectures to xcodebuild command when frameworkVersion is 5.0.0 and deployment target is 10.0", + frameworkVersion: "5.0.0", + deploymentTarget: "10.0", + expectedArchs: "armv7 arm64" + }, { + name: "should pass both architectures to xcodebuild command when frameworkVersion is 5.0.0 and no deployment target", + frameworkVersion: "5.0.0", + deploymentTarget: null, + expectedArchs: "armv7 arm64" + }]; + + executeTests(testCases, { buildForDevice: true }); + }); + + describe("for simulator", () => { + afterEach(() => { + xcodeBuildCommandArgs = []; + }); + + const testCases = [{ + name: "shouldn't exclude i386 architecture when deployment target is 10", + frameworkVersion: "5.0.0", + deploymentTarget: "10.0", + expectedArchs: "i386 x86_64" + }, { + name: "should exclude i386 architecture when deployment target is 11", + frameworkVersion: "5.0.0", + deploymentTarget: "11.0", + expectedArchs: "x86_64" + }, { + name: "shouldn't pass architecture to xcodebuild command when frameworkVersion is 5.1.0", + frameworkVersion: "5.1.0", + deploymentTarget: "11.0" + }, { + name: "should pass only 64bit architecture to xcodebuild command when frameworkVersion is 5.0.0 and deployment target is 11.0", + frameworkVersion: "5.0.0", + deploymentTarget: "11.0", + expectedArchs: "x86_64" + }, { + name: "should pass both architectures to xcodebuild command when frameworkVersion is 5.0.0 and deployment target is 10.0", + frameworkVersion: "5.0.0", + deploymentTarget: "10.0", + expectedArchs: "i386 x86_64" + }, { + name: "should pass both architectures to xcodebuild command when frameworkVersion is 5.0.0 and no deployment target", + frameworkVersion: "5.0.0", + deploymentTarget: null, + expectedArchs: "i386 x86_64" + }]; + + executeTests(testCases, { buildForDevice: false }); + }); +}); diff --git a/test/stubs.ts b/test/stubs.ts index dc02ec8d73..1a47e49c09 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -468,6 +468,9 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor getPluginPlatformsFolderPath(pluginData: IPluginData, platform: string): string { return ""; } + getDeploymentTarget(projectData: IProjectData): any { + return; + } } export class PlatformsDataStub extends EventEmitter implements IPlatformsData { @@ -836,6 +839,10 @@ export class PlatformServiceStub extends EventEmitter implements IPlatformServic return true; } + public async validateInstall(device: Mobile.IDevice): Promise { + return; + } + public installApplication(device: Mobile.IDevice, options: IRelease): Promise { return Promise.resolve(); }