Skip to content
This repository has been archived by the owner on Jan 18, 2024. It is now read-only.

Commit

Permalink
[install-expo-modules] Replace xcode with xcparse (#4554)
Browse files Browse the repository at this point in the history
# Why

`simple-plist@1.3.1` is a transitive dependency of the xcode package. it has [an incompatible issue](wollardj/simple-plist#58) with ncc. the issue is pending for a while without publishing newer version as latest dist-tag. that blocks us from updating install-expo-modules.

# How

replace xcode with the awesome xcparse. this pr introduces a `withXCParseXcodeProject` config-plugin internally.

# Test Plan

- ✅ unit test passed
- ✅ manual test on a react-native 0.68 project
- ✅ manual test on a react-native 0.69 project
  • Loading branch information
Kudo committed Sep 23, 2022
1 parent ea2caf8 commit 804059e
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 55 deletions.
3 changes: 2 additions & 1 deletion packages/install-expo-modules/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"glob": "7.1.6",
"prompts": "^2.3.2",
"resolve-from": "^5.0.0",
"semver": "7.3.2"
"semver": "7.3.2",
"xcparse": "^0.0.3"
},
"devDependencies": {
"@types/prompts": "^2.0.6",
Expand Down
4 changes: 4 additions & 0 deletions packages/install-expo-modules/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
withIosDeploymentTarget,
} from './plugins/ios/withIosDeploymentTarget';
import { withIosModules } from './plugins/ios/withIosModules';
import { withXCParseXcodeProjectBaseMod } from './plugins/ios/withXCParseXcodeProject';
import { getDefaultSdkVersion, getVersionInfo, VersionInfo } from './utils/expoVersionMappings';
import { installExpoPackageAsync, installPodsAsync } from './utils/packageInstaller';
import { normalizeProjectRoot } from './utils/projectRoot';
Expand Down Expand Up @@ -91,6 +92,9 @@ async function runAsync(programName: string) {
deploymentTarget: iosDeploymentTarget,
});

// Keeps the base mods last
config = withXCParseXcodeProjectBaseMod(config);

console.log('\u203A Updating your project...');
await compileModsAsync(config, {
projectRoot,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import {
ConfigPlugin,
withDangerousMod,
withXcodeProject,
XcodeProject,
} from '@expo/config-plugins';
import { ConfigPlugin, withDangerousMod } from '@expo/config-plugins';
import fs from 'fs';
import path from 'path';
import semver from 'semver';
import { ISA, XCBuildConfiguration } from 'xcparse';

import { withXCParseXcodeProject, XCParseXcodeProject } from './withXCParseXcodeProject';

type IosDeploymentTargetConfigPlugin = ConfigPlugin<{ deploymentTarget: string }>;

Expand Down Expand Up @@ -62,7 +60,7 @@ export async function shouldUpdateDeployTargetPodfileAsync(
}

const withIosDeploymentTargetXcodeProject: IosDeploymentTargetConfigPlugin = (config, props) => {
return withXcodeProject(config, config => {
return withXCParseXcodeProject(config, config => {
config.modResults = updateDeploymentTargetXcodeProject(
config.modResults,
props.deploymentTarget
Expand All @@ -72,17 +70,19 @@ const withIosDeploymentTargetXcodeProject: IosDeploymentTargetConfigPlugin = (co
};

export function updateDeploymentTargetXcodeProject(
project: XcodeProject,
project: XCParseXcodeProject,
deploymentTarget: string
): XcodeProject {
const configurations = project.pbxXCBuildConfigurationSection();
for (const { buildSettings } of Object.values(configurations ?? {})) {
const currDeploymentTarget = buildSettings?.IPHONEOS_DEPLOYMENT_TARGET;
if (
currDeploymentTarget &&
semver.lt(toSemVer(currDeploymentTarget), toSemVer(deploymentTarget))
) {
buildSettings.IPHONEOS_DEPLOYMENT_TARGET = deploymentTarget;
): XCParseXcodeProject {
for (const section of Object.values(project.objects ?? {})) {
if (section.isa === ISA.XCBuildConfiguration) {
const { buildSettings } = section as XCBuildConfiguration;
const currDeploymentTarget = buildSettings?.IPHONEOS_DEPLOYMENT_TARGET;
if (
currDeploymentTarget &&
semver.lt(toSemVer(currDeploymentTarget), toSemVer(deploymentTarget))
) {
buildSettings.IPHONEOS_DEPLOYMENT_TARGET = deploymentTarget;
}
}
}
return project;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import { ConfigPlugin, withAppDelegate, withDangerousMod } from '@expo/config-plugins';
import {
getAppDelegateObjcHeaderFilePath,
getPBXProjectPath,
} from '@expo/config-plugins/build/ios/Paths';
import { getDesignatedSwiftBridgingHeaderFileReference } from '@expo/config-plugins/build/ios/Swift';
import { ConfigPlugin, IOSConfig, withAppDelegate, withDangerousMod } from '@expo/config-plugins';
import {
addObjcImports,
insertContentsInsideObjcFunctionBlock,
Expand All @@ -12,7 +7,11 @@ import {
import fs from 'fs';
import { sync as globSync } from 'glob';
import semver from 'semver';
import xcode from 'xcode';

import {
getDesignatedSwiftBridgingHeaderFileReference,
withXCParseXcodeProject,
} from './withXCParseXcodeProject';

export const withIosModulesAppDelegate: ConfigPlugin = config => {
return withAppDelegate(config, config => {
Expand All @@ -28,7 +27,7 @@ export const withIosModulesAppDelegateObjcHeader: ConfigPlugin = config => {
'ios',
async config => {
try {
const appDelegateObjcHeaderPath = getAppDelegateObjcHeaderFilePath(
const appDelegateObjcHeaderPath = IOSConfig.Paths.getAppDelegateObjcHeaderFilePath(
config.modRequest.projectRoot
);
let contents = await fs.promises.readFile(appDelegateObjcHeaderPath, 'utf8');
Expand All @@ -41,38 +40,31 @@ export const withIosModulesAppDelegateObjcHeader: ConfigPlugin = config => {
};

export const withIosModulesSwiftBridgingHeader: ConfigPlugin = config => {
return withDangerousMod(config, [
'ios',
async config => {
const { projectRoot } = config.modRequest;
const projectFilePath = getPBXProjectPath(projectRoot);
const project = xcode.project(projectFilePath);
project.parseSync();

const bridgingHeaderFileName = getDesignatedSwiftBridgingHeaderFileReference({ project });
if (!bridgingHeaderFileName) {
return config;
}
const [bridgingHeaderFilePath] = globSync(
`ios/${bridgingHeaderFileName.replace(/['"]/g, '')}`,
{
absolute: true,
cwd: projectRoot,
}
);
if (!bridgingHeaderFilePath) {
return config;
}
let contents = await fs.promises.readFile(bridgingHeaderFilePath, 'utf8');
return withXCParseXcodeProject(config, async config => {
const bridgingHeaderFileName = getDesignatedSwiftBridgingHeaderFileReference(config.modResults);
if (!bridgingHeaderFileName) {
return config;
}

if (!contents.match(/^#import\s+<Expo\/Expo\.h>\s*$/m)) {
contents = addObjcImports(contents, ['<Expo/Expo.h>']);
const [bridgingHeaderFilePath] = globSync(
`ios/${bridgingHeaderFileName.replace(/['"]/g, '')}`,
{
absolute: true,
cwd: config.modRequest.projectRoot,
}

await fs.promises.writeFile(bridgingHeaderFilePath, contents);
);
if (!bridgingHeaderFilePath) {
return config;
},
]);
}
let contents = await fs.promises.readFile(bridgingHeaderFilePath, 'utf8');

if (!contents.match(/^#import\s+<Expo\/Expo\.h>\s*$/m)) {
contents = addObjcImports(contents, ['<Expo/Expo.h>']);
}

await fs.promises.writeFile(bridgingHeaderFilePath, contents);
return config;
});
};

export function updateModulesAppDelegateObjcImpl(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { BaseMods, ConfigPlugin, IOSConfig, Mod, withMod } from '@expo/config-plugins';
import fs from 'fs';
import { ISA, build as xcbuild, parse as xcparse } from 'xcparse';
import type { BuildSettings, XCBuildConfiguration, XcodeProject } from 'xcparse';

export type XCParseXcodeProject = Partial<XcodeProject>;

export interface BuildSettingsExtended extends BuildSettings {
SWIFT_OBJC_BRIDGING_HEADER?: string;
}

const MOD_NAME = 'xcparseXcodeproj';

export const withXCParseXcodeProjectBaseMod: ConfigPlugin = config => {
return BaseMods.withGeneratedBaseMods(config, {
platform: 'ios',
skipEmptyMod: false,
providers: {
[MOD_NAME]: BaseMods.provider<XCParseXcodeProject>({
getFilePath({ modRequest: { projectRoot } }) {
return IOSConfig.Paths.getPBXProjectPath(projectRoot);
},
async read(filePath) {
const content = await fs.promises.readFile(filePath, 'utf8');
const pbxproj = xcparse(content);
return pbxproj;
},
async write(filePath: string, { modResults }) {
const content = xcbuild(modResults);
await fs.promises.writeFile(filePath, content);
},
}),
},
});
};

export const withXCParseXcodeProject: ConfigPlugin<Mod<XCParseXcodeProject>> = (config, action) => {
return withMod(config, {
platform: 'ios',
mod: MOD_NAME,
action,
});
};

export function getDesignatedSwiftBridgingHeaderFileReference(
pbxproj: XCParseXcodeProject
): string | null {
for (const section of Object.values(pbxproj.objects ?? {})) {
if (section.isa === ISA.XCBuildConfiguration) {
const buildConfigSection = section as XCBuildConfiguration;
const buildSettings = buildConfigSection.buildSettings as BuildSettingsExtended;
if (
typeof buildSettings.PRODUCT_NAME !== 'undefined' &&
typeof buildSettings.SWIFT_OBJC_BRIDGING_HEADER === 'string' &&
buildSettings.SWIFT_OBJC_BRIDGING_HEADER
) {
return buildSettings.SWIFT_OBJC_BRIDGING_HEADER;
}
}
}
return null;
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -19010,6 +19010,11 @@ xcode@^3.0.1:
simple-plist "^1.1.0"
uuid "^7.0.3"

xcparse@^0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/xcparse/-/xcparse-0.0.3.tgz#abc2dbe07e0550c14c1b670c41d05dcbe3cfd10b"
integrity sha512-/HgjZ1o81gudtNHt5a/EGEyMa991WZjZqu8ryPWJ1UtG4NRJyQ2AthR8MaqD6nN7UnCe7IcFShMm5oEA/S9nEQ==

xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
Expand Down

0 comments on commit 804059e

Please sign in to comment.