Skip to content

Commit

Permalink
feat: opt-in pod install with ios commands (#2116)
Browse files Browse the repository at this point in the history
* add automaticPodsInstallation param to config & create config on init

* add note in the docs

* CR updates

* update tests

* update build-ios

* throw error if Gemfile is not found

* add try/catch to creating custom config file

* update logic for merging config files

* test: update snapshot

* code review updates

---------

Co-authored-by: szymonrybczak <[email protected]>
  • Loading branch information
TMisiukiewicz and szymonrybczak authored Oct 31, 2023
1 parent 4d149bc commit 9c52009
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 20 deletions.
3 changes: 2 additions & 1 deletion __e2e__/__snapshots__/config.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ exports[`shows up current config without unnecessary output 1`] = `
},
"project": {
"ios": {
"sourceDir": "<<REPLACED_ROOT>>/TestProject/ios"
"sourceDir": "<<REPLACED_ROOT>>/TestProject/ios",
"automaticPodsInstallation": true
},
"android": {
"sourceDir": "<<REPLACED_ROOT>>/TestProject/android",
Expand Down
1 change: 1 addition & 0 deletions __e2e__/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const customTemplateCopiedFiles = [
'file',
'node_modules',
'package.json',
'react-native.config.js',
'yarn.lock',
];

Expand Down
12 changes: 12 additions & 0 deletions docs/projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type IOSProjectParams = {
sourceDir?: string;
watchModeCommandParams?: string[];
unstable_reactLegacyComponentNames?: string[] | null;
automaticPodsInstallation?: boolean;
};

type AndroidProjectParams = {
Expand Down Expand Up @@ -100,6 +101,17 @@ This will allow you to use libraries that haven't been migrated yet on the New A

The list should contain the name of the components, as they're registered in the ViewManagers (i.e. just `"Button"`).

#### project.ios.automaticPodsInstallation

A boolean value to determine if you want to automatically install CocoaPods when running `run-ios` or `build-ios` command when:
- they are not yet installed
- a new dependency visible for autolinking is installed
- a version of existing native dependency has changed

If set to `true`, you can skip running `pod install` manually whenever it's needed.

> Note: Starting from React Native 0.73, CLI's `init` command scaffolds the project with `react-native.config.js` file with this value set to `true` by default. Older projects can opt-in after migrating to 0.73. Please note that if your setup does not follow the standard React Native template, e.g. you are not using Gems to install CocoaPods, this might not work properly for you.
#### project.android.appName

A name of the app in the Android `sourceDir`, equivalent to Gradle project name. By default it's `app`.
Expand Down
1 change: 1 addition & 0 deletions packages/cli-config/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export const projectConfig = t
.array()
.items(t.string())
.default([]),
automaticPodsInstallation: t.bool().default(false),
})
.default({}),
android: t
Expand Down
19 changes: 10 additions & 9 deletions packages/cli-platform-ios/src/commands/buildIOS/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ import getArchitecture from '../../tools/getArchitecture';
async function buildIOS(_: Array<string>, ctx: Config, args: BuildFlags) {
const {xcodeProject, sourceDir} = getXcodeProjectAndDir(ctx.project.ios);

const isAppRunningNewArchitecture = ctx.project.ios?.sourceDir
? await getArchitecture(ctx.project.ios?.sourceDir)
: undefined;

// check if pods need to be installed
await resolvePods(ctx.root, ctx.dependencies, {
forceInstall: args.forcePods,
newArchEnabled: isAppRunningNewArchitecture,
});
if (ctx.project.ios?.automaticPodsInstallation || args.forcePods) {
const isAppRunningNewArchitecture = ctx.project.ios?.sourceDir
? await getArchitecture(ctx.project.ios?.sourceDir)
: undefined;

await resolvePods(ctx.root, ctx.dependencies, {
forceInstall: args.forcePods,
newArchEnabled: isAppRunningNewArchitecture,
});
}

process.chdir(sourceDir);

Expand Down
18 changes: 10 additions & 8 deletions packages/cli-platform-ios/src/commands/runIOS/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,17 @@ async function runIOS(_: Array<string>, ctx: Config, args: FlagsT) {

let {packager, port} = args;

const isAppRunningNewArchitecture = ctx.project.ios?.sourceDir
? await getArchitecture(ctx.project.ios?.sourceDir)
: undefined;

// check if pods need to be installed
await resolvePods(ctx.root, ctx.dependencies, {
forceInstall: args.forcePods,
newArchEnabled: isAppRunningNewArchitecture,
});
if (ctx.project.ios?.automaticPodsInstallation || args.forcePods) {
const isAppRunningNewArchitecture = ctx.project.ios?.sourceDir
? await getArchitecture(ctx.project.ios?.sourceDir)
: undefined;

await resolvePods(ctx.root, ctx.dependencies, {
forceInstall: args.forcePods,
newArchEnabled: isAppRunningNewArchitecture,
});
}

if (packager) {
const {port: newPort, startPackager} = await findDevServerPort(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe('ios::getProjectConfig', () => {
});
expect(projectConfig('/', {})).toMatchInlineSnapshot(`
Object {
"automaticPodsInstallation": undefined,
"sourceDir": "/ios",
"watchModeCommandParams": undefined,
"xcodeProject": null,
Expand All @@ -45,6 +46,7 @@ describe('ios::getProjectConfig', () => {
});
expect(projectConfig('/', {})).toMatchInlineSnapshot(`
Object {
"automaticPodsInstallation": undefined,
"sourceDir": "/ios",
"watchModeCommandParams": undefined,
"xcodeProject": null,
Expand Down
1 change: 1 addition & 0 deletions packages/cli-platform-ios/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export function projectConfig(
sourceDir,
watchModeCommandParams: userConfig.watchModeCommandParams,
xcodeProject,
automaticPodsInstallation: userConfig.automaticPodsInstallation,
};
}

Expand Down
4 changes: 4 additions & 0 deletions packages/cli-platform-ios/src/tools/installPods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ async function installPods(loader?: Ora, options?: PodInstallOptions) {

if (fs.existsSync('../Gemfile') && !options?.skipBundleInstall) {
await runBundleInstall(loader);
} else if (!fs.existsSync('../Gemfile')) {
throw new CLIError(
'Could not find the Gemfile. Currently the CLI requires to have this file in the root directory of the project to install CocoaPods. If your configuration is different, please install the CocoaPods manually.',
);
}

try {
Expand Down
2 changes: 2 additions & 0 deletions packages/cli-types/src/ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
export interface IOSProjectParams {
sourceDir?: string;
watchModeCommandParams?: string[];
automaticPodsInstallation?: boolean;
}

export type IOSProjectInfo = {
Expand All @@ -17,6 +18,7 @@ export interface IOSProjectConfig {
sourceDir: string;
xcodeProject: IOSProjectInfo | null;
watchModeCommandParams?: string[];
automaticPodsInstallation?: boolean;
}

export interface IOSDependencyConfig {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@react-native-community/cli-types": "12.0.0-alpha.18",
"chalk": "^4.1.2",
"commander": "^9.4.1",
"deepmerge": "^4.3.0",
"execa": "^5.0.0",
"find-up": "^4.1.0",
"fs-extra": "^8.1.0",
Expand All @@ -48,7 +49,6 @@
"@types/hapi__joi": "^17.1.6",
"@types/prompts": "^2.4.4",
"@types/semver": "^6.0.2",
"deepmerge": "^4.3.0",
"slash": "^3.0.0",
"snapshot-diff": "^0.7.0"
},
Expand Down
60 changes: 59 additions & 1 deletion packages/cli/src/commands/init/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {getNpmVersionIfAvailable} from '../../tools/npm';
import {getYarnVersionIfAvailable} from '../../tools/yarn';
import {createHash} from 'crypto';
import createGitRepository from './createGitRepository';
import deepmerge from 'deepmerge';

const DEFAULT_VERSION = 'latest';

Expand Down Expand Up @@ -169,7 +170,8 @@ async function createFromTemplate({
packageName,
});

loader.succeed();
createDefaultConfigFile(projectDirectory, loader);

const {postInitScript} = templateConfig;
if (postInitScript) {
loader.info('Executing post init script ');
Expand Down Expand Up @@ -269,6 +271,62 @@ function createTemplateUri(options: Options, version: string): string {
return options.template || `react-native@${version}`;
}

//remove quotes from object keys to match the linter rules of the template
function sanitizeConfigFile(fileContent: string) {
return fileContent.replace(/"([^"]+)":/g, '$1:');
}

/*
Starting from 0.73, react-native.config.js is created by CLI during the init process.
It contains automaticPodsInstallation flag set to true by default.
This flag is used by CLI to determine whether to install CocoaPods dependencies when running ios commands or not.
It's created by CLI rather than being a part of a template to avoid displaying this file in the Upgrade Helper,
as it might bring confusion for existing projects where this change might not be applicable.
For more details, see https://github.com/react-native-community/cli/blob/main/docs/projects.md#projectiosautomaticpodsinstallation
*/
function createDefaultConfigFile(directory: string, loader: Loader) {
const cliConfigContent = {
project: {
ios: {
automaticPodsInstallation: true,
},
},
};
const configFileContent = `module.exports = ${JSON.stringify(
cliConfigContent,
null,
2,
)}`;
const filepath = 'react-native.config.js';
try {
if (!doesDirectoryExist(path.join(directory, filepath))) {
fs.writeFileSync(filepath, sanitizeConfigFile(configFileContent), {
encoding: 'utf-8',
});
} else {
const existingConfigFile = require(path.join(directory, filepath));

const mergedConfig = deepmerge(existingConfigFile, cliConfigContent);
const output = `module.exports = ${JSON.stringify(
mergedConfig,
null,
2,
)};`;

fs.writeFileSync(filepath, sanitizeConfigFile(output), {
encoding: 'utf-8',
});
}
loader.succeed();
} catch {
loader.warn(
`Could not create custom ${chalk.bold(
'react-native.config.js',
)} file. You can create it manually in your project's root folder with the following content: \n\n${configFileContent}`,
);
}
}

async function createProject(
projectName: string,
directory: string,
Expand Down

0 comments on commit 9c52009

Please sign in to comment.