Skip to content

Commit

Permalink
fix(create): Ensure unix paths in Xcode project file
Browse files Browse the repository at this point in the history
Cordova-iOS requires macOS for building and deploying, but it seems to
be a fairly common problem that people generate the iOS project files on
Windows (which isn't technically supported, but _appears_ to work) and
then Xcode can't open it on macOS because of a bad path separator.

The path separator problem is fixable, so let's at least generate a
valid Xcode project.

This does *not* claim to add support for using Cordova-iOS on Windows.

Closes apache#971.
Closes apache#1016.
Closes apache#1084.
  • Loading branch information
dpogue committed Apr 15, 2023
1 parent 3d6c71a commit cfbdc3e
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 2 deletions.
9 changes: 8 additions & 1 deletion lib/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,14 @@ class ProjectCreator {
const cdvLibRealPath = fs.realpathSync(this.projectPath('CordovaLib'));

const cdvLibXcodeAbsPath = path.join(cdvLibRealPath, 'CordovaLib.xcodeproj');
const cdvLibXcodePath = path.relative(this.project.path, cdvLibXcodeAbsPath);
let cdvLibXcodePath = path.relative(this.project.path, cdvLibXcodeAbsPath);

if (path.sep !== path.posix.sep) {
// If the Cordova project is being created on Windows, we need to
// make sure the Xcode project file uses POSIX-style paths or else
// Xcode considers it invalid
cdvLibXcodePath = cdvLibXcodePath.replace(path.sep, path.posix.sep);
}

// Replace magic line in project.pbxproj
const pbxprojPath = this.projectPath('__PROJECT_NAME__.xcodeproj/project.pbxproj');
Expand Down
2 changes: 1 addition & 1 deletion tests/spec/coverage.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"spec_dir": "tests/spec",
"spec_files": [
"unit/**/*[sS]pec.js",
"create.spec.js"
"createAndBuild.spec.js"
],
"stopSpecOnExpectationFailure": false,
"random": false
Expand Down
File renamed without changes.
103 changes: 103 additions & 0 deletions tests/spec/unit/create.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/

const fs = require('fs-extra');
const os = require('os');
const path = require('path');
const xcode = require('xcode');
const create = require('../../../lib/create');

const makeTempDir = () => path.join(
fs.realpathSync(os.tmpdir()),
`cordova-ios-create-test-${Date.now()}`
);

/**
* Verifies that some of the project file exists. Not all will be tested.
* E.g. App's resource directory, xcodeproj, xcworkspace, and CordovaLib.
*
* @param {String} tmpDir
* @param {String} projectName
*/
function verifyProjectFiles (tmpDir, projectName) {
expect(fs.existsSync(path.join(tmpDir, projectName))).toBe(true);
expect(fs.existsSync(path.join(tmpDir, `${projectName}.xcodeproj`))).toBe(true);
expect(fs.existsSync(path.join(tmpDir, `${projectName}.xcworkspace`))).toBe(true);
expect(fs.existsSync(path.join(tmpDir, 'CordovaLib'))).toBe(true);

const pbxproj = path.join(tmpDir, `${projectName}.xcodeproj`, 'project.pbxproj');
const pbxcontents = fs.readFileSync(pbxproj, 'utf-8');
const regex = /(.+CordovaLib.xcodeproj.+PBXFileReference.+wrapper.pb-project.+)(path = .+?;)(.*)(sourceTree.+;)(.+)/;
const line = pbxcontents.split(/\r?\n/).find(l => regex.test(l));

expect(line).toMatch(/path = CordovaLib\/CordovaLib.xcodeproj;/);
}

/**
* Verifies that the set bundle id matches with the expected.
*
* @param {String} tmpDir
* @param {String} projectName
* @param {String} expectedBundleIdentifier
*/
function verifyProjectBundleIdentifier (tmpDir, projectName, expectedBundleIdentifier) {
const pbxproj = path.join(tmpDir, `${projectName}.xcodeproj`, 'project.pbxproj');
const xcodeproj = xcode.project(pbxproj);
xcodeproj.parseSync();
const actualBundleIdentifier = xcodeproj.getBuildProperty('PRODUCT_BUNDLE_IDENTIFIER');
expect(actualBundleIdentifier).toBe(`"${expectedBundleIdentifier}"`);
}

/**
* Runs various project creation checks.
*
* @param {String} tmpDir
* @param {String} packageName
* @param {String} projectName
* @returns {Promise}
*/
async function verifyCreatedProject (tmpDir, packageName, projectName) {
await create.createProject(tmpDir, packageName, projectName, {}, undefined)
.then(() => verifyProjectFiles(tmpDir, projectName))
.then(() => verifyProjectBundleIdentifier(tmpDir, projectName, packageName));
}

describe('create', () => {
let tmpDir;

beforeEach(function () {
tmpDir = makeTempDir();
});

afterEach(() => {
fs.removeSync(tmpDir);
});

it('should create project with ascii name, no spaces', () => {
const packageName = 'com.test.app1';
const projectName = 'testcreate';
return verifyCreatedProject(tmpDir, packageName, projectName);
});

it('should create project with complicated name', () => {
const packageName = 'com.test.app2';
const projectName = '応応応応 hello & إثرا 用用用用';
return verifyCreatedProject(tmpDir, packageName, projectName);
});
});

0 comments on commit cfbdc3e

Please sign in to comment.