Skip to content
Open
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
38 changes: 32 additions & 6 deletions cli/src/ios/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { execSync } from 'child_process';
import { readFile, readFileSync, writeFile } from 'fs-extra';
import { existsSync, readFile, readFileSync, writeFile, writeFileSync } from 'fs-extra';
import { join, resolve } from 'path';

import c from '../colors';
Expand Down Expand Up @@ -105,7 +105,7 @@ export async function editProjectSettingsIOS(config: Config): Promise<void> {
const appId = config.app.appId;
const appName = config.app.appName.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

const pbxPath = `${config.ios.nativeXcodeProjDirAbs}/project.pbxproj`;
const projectPath = getXcodeProjectFile(config);
const plistPath = resolve(config.ios.nativeTargetDirAbs, 'Info.plist');

let plistContent = await readFile(plistPath, { encoding: 'utf-8' });
Expand All @@ -115,15 +115,41 @@ export async function editProjectSettingsIOS(config: Config): Promise<void> {
`<key>CFBundleDisplayName</key>\n <string>${appName}</string>`,
);

let pbxContent = await readFile(pbxPath, { encoding: 'utf-8' });
pbxContent = pbxContent.replace(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+)/g, `PRODUCT_BUNDLE_IDENTIFIER = ${appId}`);
let projectContent = await readFile(projectPath, { encoding: 'utf-8' });
projectContent = projectPath.endsWith('.xcproj')
? projectContent.replace(xcprojSettingPattern('PRODUCT_BUNDLE_IDENTIFIER'), `$1${appId}$3`)
: projectContent.replace(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+)/g, `PRODUCT_BUNDLE_IDENTIFIER = ${appId}`);

await writeFile(plistPath, plistContent, { encoding: 'utf-8' });
await writeFile(pbxPath, pbxContent, { encoding: 'utf-8' });
await writeFile(projectPath, projectContent, { encoding: 'utf-8' });
}

export function getXcodeProjectFile(config: Config): string {
const pbxprojPath = join(config.ios.nativeXcodeProjDirAbs, 'project.pbxproj');
const xcprojPath = join(config.ios.nativeXcodeProjDirAbs, 'project.xcproj');
return !existsSync(pbxprojPath) && existsSync(xcprojPath) ? xcprojPath : pbxprojPath;
}

function xcprojSettingPattern(name: string): RegExp {
return new RegExp(`("${name}(?:\\[[^\\]]*\\])?"\\s*:\\s*")([^"]*)(")`, 'g');
}

export function setXcprojDeploymentTarget(projectFile: string, version: string): boolean {
const content = readFileSync(projectFile, 'utf-8');
const updated = content.replace(xcprojSettingPattern('IPHONEOS_DEPLOYMENT_TARGET'), `$1${version}$3`);
writeFileSync(projectFile, updated, 'utf-8');
return updated !== content;
}

export function getMajoriOSVersion(config: Config): string {
const pbx = readFileSync(join(config.ios.nativeXcodeProjDirAbs, 'project.pbxproj'), 'utf-8');
const projectFile = getXcodeProjectFile(config);
const pbx = readFileSync(projectFile, 'utf-8');
if (projectFile.endsWith('.xcproj')) {
const majors = [...pbx.matchAll(xcprojSettingPattern('IPHONEOS_DEPLOYMENT_TARGET'))].map((match) =>
parseInt(match[2], 10),
);
return majors.length > 0 ? String(Math.min(...majors)) : '';
}
const searchString = 'IPHONEOS_DEPLOYMENT_TARGET = ';
const iosVersion = pbx.substring(
pbx.indexOf(searchString) + searchString.length,
Expand Down
16 changes: 14 additions & 2 deletions cli/src/tasks/migrate-uiscene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { join, sep } from 'path';

import { runTask } from '../common';
import type { Config } from '../definitions';
import { getXcodeProjectFile } from '../ios/common';
import { logger } from '../log';
import { deleteFolderRecursive, readdirp } from '../util/fs';
import { addSceneManifestIfNeeded, hasSceneManifest } from '../util/spm';
Expand Down Expand Up @@ -63,9 +64,20 @@ export async function migrateToUIScene(config: Config): Promise<void> {
});

await runTask('Registering SceneDelegate.swift with the Xcode App target.', async () => {
const pbxprojPath = join(config.ios.nativeXcodeProjDirAbs, 'project.pbxproj');
const projectFile = getXcodeProjectFile(config);
if (projectFile.endsWith('.xcproj')) {
if (readFileSync(projectFile, 'utf-8').includes('"SceneDelegate.swift"')) {
logger.warn('SceneDelegate.swift is already registered in the App target, skipping.');
} else {
logger.warn(
'Could not register SceneDelegate.swift automatically in a project.xcproj project. ' +
'Add SceneDelegate.swift to the App target in Xcode manually.',
);
}
return;
}
try {
const { added } = addSwiftFileToAppTarget(pbxprojPath, 'App', 'SceneDelegate.swift');
const { added } = addSwiftFileToAppTarget(projectFile, 'App', 'SceneDelegate.swift');
if (!added) {
logger.warn('SceneDelegate.swift is already registered in the App target, skipping.');
}
Expand Down
16 changes: 7 additions & 9 deletions cli/src/tasks/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import c from '../colors';
import { getCoreVersion, runTask, checkJDKMajorVersion } from '../common';
import type { Config } from '../definitions';
import { fatal } from '../errors';
import { getMajoriOSVersion } from '../ios/common';
import { getMajoriOSVersion, getXcodeProjectFile, setXcprojDeploymentTarget } from '../ios/common';
import { logger, logPrompt, logSuccess } from '../log';
import { deleteFolderRecursive } from '../util/fs';
import { runCommand } from '../util/subprocess';
Expand Down Expand Up @@ -152,14 +152,12 @@ export async function migrateCommand(config: Config, noprompt: boolean, packagem
const currentiOSVersion = getMajoriOSVersion(config);
if (parseInt(currentiOSVersion) < parseInt(iOSVersion)) {
// ios template changes
await runTask(`Migrating deployment target to ${iOSVersion}.0.`, () => {
return updateFile(
config,
join(config.ios.nativeXcodeProjDirAbs, 'project.pbxproj'),
'IPHONEOS_DEPLOYMENT_TARGET = ',
';',
`${iOSVersion}.0`,
);
await runTask(`Migrating deployment target to ${iOSVersion}.0.`, async () => {
const projectFile = getXcodeProjectFile(config);
if (projectFile.endsWith('.xcproj')) {
return setXcprojDeploymentTarget(projectFile, `${iOSVersion}.0`);
}
return updateFile(config, projectFile, 'IPHONEOS_DEPLOYMENT_TARGET = ', ';', `${iOSVersion}.0`);
});

if ((await config.ios.packageManager) !== 'SPM') {
Expand Down
171 changes: 171 additions & 0 deletions cli/test/fixtures/xcproj/default/project.xcproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
{
"build-independent-targets-in-parallel": false,
"default-configuration": "Release",
"configurations": [
{ "name": "Debug", "file": "debug.xcconfig" },
"Release",
],
"localizations": {
"development": "en",
"supported": [
"Base",
],
},
"packages": [
{
"kind": "local",
"path": "CapApp-SPM",
},
],
"files": [
{ "path": "<PROJECT>/../debug.xcconfig" },
{
"kind": "group",
"path": "App",
"children": [
{ "path": "SceneDelegate.swift", "target-membership": [ "App/compile-sources" ] },
{ "path": "capacitor.config.json", "encoding": "utf8", "target-membership": [ "App/resources" ] },
{ "path": "AppDelegate.swift", "target-membership": [ "App/compile-sources" ] },
{
"kind": "variant-group",
"name": "Main.storyboard",
"target-membership": [
"App/resources",
],
"children": [
{
"path": "Base.lproj/Main.storyboard",
},
],
},
{ "path": "Assets.xcassets", "target-membership": [ "App/resources" ] },
{
"kind": "variant-group",
"name": "LaunchScreen.storyboard",
"target-membership": [
"App/resources",
],
"children": [
{
"path": "Base.lproj/LaunchScreen.storyboard",
},
],
},
{ "path": "Info.plist" },
{ "path": "config.xml", "target-membership": [ "App/resources" ] },
{ "path": "public", "target-membership": [ "App/resources" ] },
],
}, {
"kind": "group",
"name": "Products",
"children": [
{ "path": "<PRODUCTS>/App.app", "id": "504EC3041FED79650016851F", "type": "wrapper.application", "index": false },
],
},
],
"targets": [
{
"name": "App",
"id": "504EC3031FED79650016851F",
"product": "Products/App.app",
"product-type": "application",
"last-swift-migration": "11.0",
"legacy-provisioning-style": "automatic",
"specialized-configurations": [
{ "name": "Debug", "file": "debug.xcconfig" },
],
"build-phases": [
"compile-sources",
"frameworks",
"resources",
],
"package-product-members": [
{
"package": "CapApp-SPM",
"product-name": "CapApp-SPM",
"build-phase": { "build-phase": "frameworks" },
},
],
"build-settings": {
"ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon",
"CODE_SIGN_STYLE": "Automatic",
"CURRENT_PROJECT_VERSION": "1",
"INFOPLIST_FILE": "App/Info.plist",
"IPHONEOS_DEPLOYMENT_TARGET": "15.0",
"LD_RUNPATH_SEARCH_PATHS": [
"$(inherited)",
"@executable_path/Frameworks",
],
"MARKETING_VERSION": "1.0",
"OTHER_SWIFT_FLAGS[config=Debug]": "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"",
"PRODUCT_BUNDLE_IDENTIFIER": "com.getcapacitor.App",
"PRODUCT_NAME": "$(TARGET_NAME)",
"SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Debug]": "DEBUG",
"SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Release]": "",
"SWIFT_VERSION": "5.0",
"TARGETED_DEVICE_FAMILY": "1,2",
},
},
],
"build-settings": {
"ALWAYS_SEARCH_USER_PATHS": "NO",
"CLANG_ANALYZER_NONNULL": "YES",
"CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION": "YES_AGGRESSIVE",
"CLANG_CXX_LANGUAGE_STANDARD": "gnu++14",
"CLANG_CXX_LIBRARY": "libc++",
"CLANG_ENABLE_MODULES": "YES",
"CLANG_ENABLE_OBJC_ARC": "YES",
"CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING": "YES",
"CLANG_WARN_BOOL_CONVERSION": "YES",
"CLANG_WARN_COMMA": "YES",
"CLANG_WARN_CONSTANT_CONVERSION": "YES",
"CLANG_WARN_DIRECT_OBJC_ISA_USAGE": "YES_ERROR",
"CLANG_WARN_DOCUMENTATION_COMMENTS": "YES",
"CLANG_WARN_EMPTY_BODY": "YES",
"CLANG_WARN_ENUM_CONVERSION": "YES",
"CLANG_WARN_INFINITE_RECURSION": "YES",
"CLANG_WARN_INT_CONVERSION": "YES",
"CLANG_WARN_NON_LITERAL_NULL_CONVERSION": "YES",
"CLANG_WARN_OBJC_LITERAL_CONVERSION": "YES",
"CLANG_WARN_OBJC_ROOT_CLASS": "YES_ERROR",
"CLANG_WARN_RANGE_LOOP_ANALYSIS": "YES",
"CLANG_WARN_STRICT_PROTOTYPES": "YES",
"CLANG_WARN_SUSPICIOUS_MOVE": "YES",
"CLANG_WARN_UNGUARDED_AVAILABILITY": "YES_AGGRESSIVE",
"CLANG_WARN_UNREACHABLE_CODE": "YES",
"CLANG_WARN__DUPLICATE_METHOD_MATCH": "YES",
"CODE_SIGN_IDENTITY": "iPhone Developer",
"COPY_PHASE_STRIP": "NO",
"DEBUG_INFORMATION_FORMAT[config=Debug]": "dwarf",
"DEBUG_INFORMATION_FORMAT[config=Release]": "dwarf-with-dsym",
"ENABLE_NS_ASSERTIONS[config=Release]": "NO",
"ENABLE_STRICT_OBJC_MSGSEND": "YES",
"ENABLE_TESTABILITY[config=Debug]": "YES",
"GCC_C_LANGUAGE_STANDARD": "gnu11",
"GCC_DYNAMIC_NO_PIC[config=Debug]": "NO",
"GCC_NO_COMMON_BLOCKS": "YES",
"GCC_OPTIMIZATION_LEVEL[config=Debug]": "0",
"GCC_PREPROCESSOR_DEFINITIONS[config=Debug]": [
"DEBUG=1",
"$(inherited)",
],
"GCC_WARN_64_TO_32_BIT_CONVERSION": "YES",
"GCC_WARN_ABOUT_RETURN_TYPE": "YES_ERROR",
"GCC_WARN_UNDECLARED_SELECTOR": "YES",
"GCC_WARN_UNINITIALIZED_AUTOS": "YES_AGGRESSIVE",
"GCC_WARN_UNUSED_FUNCTION": "YES",
"GCC_WARN_UNUSED_VARIABLE": "YES",
"IPHONEOS_DEPLOYMENT_TARGET": "15.0",
"MTL_ENABLE_DEBUG_INFO[config=Debug]": "YES",
"MTL_ENABLE_DEBUG_INFO[config=Release]": "NO",
"ONLY_ACTIVE_ARCH[config=Debug]": "YES",
"SDKROOT": "iphoneos",
"SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Debug]": "DEBUG",
"SWIFT_COMPILATION_MODE[config=Release]": "wholemodule",
"SWIFT_OPTIMIZATION_LEVEL[config=Debug]": "-Onone",
"SWIFT_OPTIMIZATION_LEVEL[config=Release]": "-O",
"VALIDATE_PRODUCT[config=Release]": "YES",
},
"last-upgrade": "9.2",
"last-swift-update": "9.2",
}
Loading