Skip to content

fix: build correctly for iOS when IPHONEOS_DEPLOYMENT_TARGET = 11.0; is specified in build.xcconfig #4206

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 5 commits into from
Dec 6, 2018
Merged
Show file tree
Hide file tree
Changes from 4 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
15 changes: 15 additions & 0 deletions lib/definitions/platform.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ interface IPlatformService extends IBuildPlatformAction, NodeJS.EventEmitter {
*/
shouldInstall(device: Mobile.IDevice, projectData: IProjectData, release: IRelease, outputPath?: string): Promise<boolean>;

/**
*
* @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<void>;

/**
* Determines whether the project should undergo the prepare process.
* @param {IShouldPrepareInfo} shouldPrepareInfo Options needed to decide whether to prepare.
Expand Down Expand Up @@ -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 {
Expand Down
21 changes: 21 additions & 0 deletions lib/definitions/project.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,27 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS
* Traverse through the production dependencies and find plugins that need build/rebuild
*/
checkIfPluginsNeedBuild(projectData: IProjectData): Promise<Array<any>>;

/**
* 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): IDeploymentTargetData;
}

interface IDeploymentTargetData {
/**
* The whole version's value
*/
version: string;
/**
* The major's version
*/
majorVersion: number;
/**
* The minor's version
*/
minorVersion: number;
}

interface IValidatePlatformOutput {
Expand Down
2 changes: 2 additions & 0 deletions lib/services/android-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
// Nothing android specific to check yet.
}

public getDeploymentTarget(projectData: IProjectData): IDeploymentTargetData { 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);
Expand Down
54 changes: 41 additions & 13 deletions lib/services/ios-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,12 +384,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
}

private async buildForDevice(projectRoot: string, args: string[], buildConfig: IBuildConfig, projectData: IProjectData): Promise<void> {
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,
Expand All @@ -402,18 +396,15 @@ 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');
}
buildConfig.architectures = architectures;
}
}

args = args.concat((buildConfig && buildConfig.architectures) || defaultArchitectures);
args = args.concat((buildConfig && buildConfig.architectures) || this.getBuildArchitectures(projectData, buildConfig, ["armv7", "arm64"]));

args = args.concat([
"-sdk", "iphoneos",
Expand Down Expand Up @@ -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.validRange(frameworkVersion) && semver.lt(semver.coerce(frameworkVersion), "5.1.0")) {
const target = this.getDeploymentTarget(projectData);
if (target && target.majorVersion >= 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);
Expand Down Expand Up @@ -555,13 +568,14 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
}

private async buildForSimulator(projectRoot: string, args: string[], projectData: IProjectData, buildConfig?: IBuildConfig): Promise<void> {
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=",
Expand Down Expand Up @@ -1031,6 +1045,20 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f
return [];
}

public getDeploymentTarget(projectData: IProjectData): IDeploymentTargetData {
const target = this.$xCConfigService.readPropertyValue(this.getBuildXCConfigFilePath(projectData), "IPHONEOS_DEPLOYMENT_TARGET");
if (!target) {
return null;
}

const parts = target.split(".");
return {
version: target,
majorVersion: parseInt(parts[0]),
minorVersion: parseInt(parts[1])
};
}

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);
Expand Down
1 change: 1 addition & 0 deletions lib/services/livesync/livesync-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,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);
Expand Down
23 changes: 21 additions & 2 deletions lib/services/platform-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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<void> {
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<void> {
this.$logger.out(`Installing on device ${device.deviceInfo.identifier}...`);

Expand Down
155 changes: 155 additions & 0 deletions test/ios-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <IOSProjectService>testInjector.resolve("iOSProjectService");
(<any>iOSProjectService).getExportOptionsMethod = () => ({});
await iOSProjectService.buildProject("myProjectRoot", projectData, <any>{ 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 = <any[]>[{
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 });
});
});
7 changes: 7 additions & 0 deletions test/stubs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,9 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor
getPluginPlatformsFolderPath(pluginData: IPluginData, platform: string): string {
return "";
}
getDeploymentTarget(projectData: IProjectData): IDeploymentTargetData {
return;
}
}

export class PlatformsDataStub extends EventEmitter implements IPlatformsData {
Expand Down Expand Up @@ -828,6 +831,10 @@ export class PlatformServiceStub extends EventEmitter implements IPlatformServic
return true;
}

public async validateInstall(device: Mobile.IDevice): Promise<void> {
return;
}

public installApplication(device: Mobile.IDevice, options: IRelease): Promise<void> {
return Promise.resolve();
}
Expand Down