Skip to content

Commit

Permalink
refactor: move pods installation from init to ios-specific commands (
Browse files Browse the repository at this point in the history
…#2077)

* remove pod installation from init process

* create dependency hashes and store it in cache

* move installPods from doctor to platform-ios

* install pods on run-ios and build-ios

* add --force-pods flag to run-ios

* add tests

* add tests for pods installation

* use crypto and rename releaseCacheManager to cacheManager

* move run sudo to cli-tools

* use findPodfilePath

* resolve only native dependencies for pod check

* update pod installation tests

* add version property to dependency config & use it to hash dependencies

* cr updates

* add pod installation prompt to init

* better error handling when package.json not found

* add --install-pods flag to init

* update tests

* fix config tests

* set hash in cache when initializing new project

* try/catch require package.json

* update type for installCocoaPods
  • Loading branch information
TMisiukiewicz authored Sep 21, 2023
1 parent 520c90f commit d011058
Show file tree
Hide file tree
Showing 30 changed files with 399 additions and 47 deletions.
2 changes: 1 addition & 1 deletion __e2e__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ beforeAll(() => {

// Initialise React Native project

runCLI(DIR, ['init', 'TestProject']);
runCLI(DIR, ['init', 'TestProject', '--install-pods']);

// Link CLI to the project
const pkgs = [
Expand Down
15 changes: 14 additions & 1 deletion __e2e__/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,14 @@ test('init fails if the directory already exists', () => {

test('init should prompt for the project name', () => {
createCustomTemplateFiles();
const {stdout} = runCLI(DIR, ['init', 'test', '--template', templatePath]);
const {stdout} = runCLI(DIR, [
'init',
'test',
'--template',
templatePath,
'--install-pods',
'false',
]);

(prompts as jest.MockedFunction<typeof prompts>).mockReturnValue(
Promise.resolve({
Expand All @@ -79,6 +86,8 @@ test('init --template filepath', () => {
'--template',
templatePath,
'TestInit',
'--install-pods',
'false',
]);

expect(stdout).toContain('Run instructions');
Expand All @@ -103,6 +112,8 @@ test('init --template file with custom directory', () => {
projectName,
'--directory',
'custom-path',
'--install-pods',
'false',
]);

// make sure --directory option is used in run instructions
Expand Down Expand Up @@ -149,6 +160,8 @@ test('init uses npm as the package manager with --npm', () => {
templatePath,
'TestInit',
'--npm',
'--install-pods',
'false',
]);

expect(stdout).toContain('Run instructions');
Expand Down
3 changes: 1 addition & 2 deletions __e2e__/root.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ beforeAll(() => {
writeFiles(cwd, {});

// Initialise React Native project
runCLI(cwd, ['init', 'TestProject']);

runCLI(cwd, ['init', 'TestProject', '--install-pods']);
// Link CLI to the project
const pkgs = [
'@react-native-community/cli-platform-ios',
Expand Down
4 changes: 4 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ module.exports = {

Skip dependencies installation

#### `--install-pods [boolean]`

Determine if CocoaPods should be installed when initializing a project. If set to `true` it will install pods, if set to `false`, it will skip the step entirely. If not used, prompt will be displayed

#### `--npm`
> [!WARNING]
> `--npm` is deprecated and will be removed in the future. Please use `--pm npm` instead.
Expand Down
1 change: 1 addition & 0 deletions docs/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ On Android and iOS, this function returns a dependency configuration for:
```ts
type IOSDependencyConfig = {
podspecPath: string;
version: string;
scriptPhases: Array<IOSScriptPhase>;
configurations: string[];
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Object {
],
"podspecPath": "<<REPLACED>>/node_modules/react-native-test/ReactNativeTest.podspec",
"scriptPhases": Array [],
"version": "unresolved",
},
},
"root": "<<REPLACED>>/node_modules/react-native-test",
Expand Down Expand Up @@ -57,6 +58,7 @@ Object {
"path": "./phase.sh",
},
],
"version": "unresolved",
},
},
"root": "<<REPLACED>>/node_modules/react-native-test",
Expand Down Expand Up @@ -107,6 +109,7 @@ Object {
"show_env_vars_in_log": false,
},
],
"version": "unresolved",
},
},
"root": "<<REPLACED>>/node_modules/react-native-test",
Expand All @@ -131,6 +134,7 @@ Object {
"configurations": Array [],
"podspecPath": "<<REPLACED>>/node_modules/react-native-test/ReactNativeTest.podspec",
"scriptPhases": Array [],
"version": "unresolved",
},
},
"root": "<<REPLACED>>/node_modules/react-native-test",
Expand All @@ -149,6 +153,7 @@ Object {
],
"podspecPath": "<<REPLACED>>/node_modules/react-native-test/ReactNativeTest.podspec",
"scriptPhases": Array [],
"version": "unresolved",
},
},
"root": "<<REPLACED>>/node_modules/react-native-test",
Expand Down
9 changes: 9 additions & 0 deletions packages/cli-config/src/__tests__/index-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,14 @@ test('supports dependencies from user configuration with custom root and propert
writeFiles(DIR, {
...REACT_NATIVE_MOCK,
'native-libs/local-lib/LocalRNLibrary.podspec': '',
'native-libs/local-lib/package.json': `
{
"name": "local-lib",
"version": "0.0.1",
"dependencies": {
"react-native": "0.0.1"
}
}`,
'react-native.config.js': `
const path = require('path');
const root = path.resolve('${escapePathSeparator(
Expand Down Expand Up @@ -276,6 +284,7 @@ module.exports = {
"configurations": Array [],
"podspecPath": "custom-path",
"scriptPhases": Array [],
"version": "0.0.1",
},
},
"root": "<<REPLACED>>/native-libs/local-lib",
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 @@ -122,6 +122,7 @@ export const projectConfig = t
// IOSDependencyConfig
.object({
podspecPath: t.string(),
version: t.string(),
configurations: t.array().items(t.string()).default([]),
scriptPhases: t.array().items(t.object()).default([]),
})
Expand Down
1 change: 0 additions & 1 deletion packages/cli-doctor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"prompts": "^2.4.2",
"semver": "^7.5.2",
"strip-ansi": "^5.2.0",
"sudo-prompt": "^9.0.0",
"wcwidth": "^1.0.1",
"yaml": "^2.2.1"
},
Expand Down
1 change: 0 additions & 1 deletion packages/cli-doctor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@ export const commands = {info, doctor};
* refactor the init in order to remove that connection.
*/
export {default as versionRanges} from './tools/versionRanges';
export {default as installPods} from './tools/installPods';
2 changes: 1 addition & 1 deletion packages/cli-doctor/src/tools/healthchecks/cocoaPods.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import execa from 'execa';
import {runSudo} from '@react-native-community/cli-tools';
import {doesSoftwareNeedToBeFixed} from '../checkInstallation';
import {runSudo} from '../installPods';
import {logError} from './common';
import {HealthCheckInterface} from '../../types';
import versionRanges from '../versionRanges';
Expand Down
6 changes: 6 additions & 0 deletions packages/cli-platform-ios/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ Installs passed binary instead of building a fresh one.
> default: false
List all available iOS devices and simulators and let you choose one to run the app.
#### `--force-pods`,

Force running `pod install` before running an app

### `build-ios`

Expand Down Expand Up @@ -165,6 +168,9 @@ Example:
npx react-native build-ios --extra-params "-jobs 4"
```

#### `--force-pods`,

Force running `pod install` before building an app
### `log-ios`

Usage:
Expand Down
134 changes: 134 additions & 0 deletions packages/cli-platform-ios/src/__tests__/pods.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import {writeFiles, getTempDirectory, cleanup} from '../../../../jest/helpers';
import installPods from '../tools/installPods';
import resolvePods, {compareMd5Hashes, getIosDependencies} from '../tools/pods';

const mockGet = jest.fn();
const mockSet = jest.fn();
jest.mock('@react-native-community/cli-tools', () => ({
...Object.assign(jest.requireActual('@react-native-community/cli-tools')),
cacheManager: {
get: mockGet,
set: mockSet,
},
}));
jest.mock('../tools/installPods', () => jest.fn());
const dependencyHash = 'd41d8cd98f00b204e9800998ecf8427e';

const packageJson = {
name: 'test-package',
dependencies: {dep1: '1.0.0'},
devDependencies: {dep2: '1.0.0'},
};

const commonDepConfig = {
root: '',
platforms: {
ios: {
podspecPath: '',
version: '1.0.0',
scriptPhases: [],
configurations: [],
},
},
};

const dependenciesConfig = {
dep1: {
name: 'dep1',
...commonDepConfig,
},
dep2: {
name: 'dep2',
...commonDepConfig,
},
};

const DIR = getTempDirectory('root_test');

const createTempFiles = (rest?: Record<string, string>) => {
writeFiles(DIR, {
'package.json': JSON.stringify(packageJson),
...rest,
});
};

beforeEach(async () => {
await cleanup(DIR);
jest.resetAllMocks();
});

describe('compareMd5Hashes', () => {
it('should return false if hashes are different', () => {
const result = compareMd5Hashes('hash1', 'hash2');

expect(result).toBe(false);
});

it('should return true if hashes are the same', () => {
const result = compareMd5Hashes('hash', 'hash');

expect(result).toBe(true);
});
});

describe('getIosDependencies', () => {
it('should return only dependencies with native code', () => {
const result = getIosDependencies(dependenciesConfig);
expect(result).toEqual(['[email protected]', '[email protected]']);
});
});

describe('resolvePods', () => {
it('should install pods if they are not installed', async () => {
createTempFiles({'ios/Podfile/Manifest.lock': ''});

await resolvePods(DIR, {});

expect(installPods).toHaveBeenCalled();
});

it('should install pods when force option is set to true', async () => {
createTempFiles();

await resolvePods(DIR, {}, {forceInstall: true});

expect(installPods).toHaveBeenCalled();
});

it('should install pods when there is no cached hash of dependencies', async () => {
createTempFiles();

await resolvePods(DIR, {});

expect(mockSet).toHaveBeenCalledWith(
packageJson.name,
'dependencies',
dependencyHash,
);
});

it('should skip pods installation if the cached hash and current hash are the same', async () => {
createTempFiles({'ios/Pods/Manifest.lock': ''});

mockGet.mockImplementation(() => dependencyHash);

await resolvePods(DIR, {});

expect(installPods).not.toHaveBeenCalled();
});

it('should install pods if the cached hash and current hash are different', async () => {
createTempFiles({'ios/Pods/Manifest.lock': ''});

mockGet.mockImplementation(() => dependencyHash);

await resolvePods(DIR, {
dep1: {
name: 'dep1',
...commonDepConfig,
},
});

expect(installPods).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type BuildFlags = {
interactive?: boolean;
destination?: string;
extraParams?: string[];
forcePods?: boolean;
};

export const buildOptions = [
Expand Down
4 changes: 4 additions & 0 deletions packages/cli-platform-ios/src/commands/buildIOS/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ import {buildProject} from './buildProject';
import {BuildFlags, buildOptions} from './buildOptions';
import {getConfiguration} from './getConfiguration';
import {getXcodeProjectAndDir} from './getXcodeProjectAndDir';
import resolvePods from '../../tools/pods';

async function buildIOS(_: Array<string>, ctx: Config, args: BuildFlags) {
const {xcodeProject, sourceDir} = getXcodeProjectAndDir(ctx.project.ios);

// check if pods need to be installed
await resolvePods(ctx.root, ctx.dependencies, {forceInstall: args.forcePods});

process.chdir(sourceDir);

const {scheme, mode} = await getConfiguration(xcodeProject, sourceDir, args);
Expand Down
4 changes: 4 additions & 0 deletions packages/cli-platform-ios/src/commands/runIOS/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import listIOSDevices from '../../tools/listIOSDevices';
import {promptForDeviceSelection} from '../../tools/prompts';
import getSimulators from '../../tools/getSimulators';
import {getXcodeProjectAndDir} from '../buildIOS/getXcodeProjectAndDir';
import resolvePods from '../../tools/pods';

export interface FlagsT extends BuildFlags {
simulator?: string;
Expand All @@ -47,6 +48,9 @@ async function runIOS(_: Array<string>, ctx: Config, args: FlagsT) {

let {packager, port} = args;

// check if pods need to be installed
await resolvePods(ctx.root, ctx.dependencies, {forceInstall: args.forcePods});

const packagerStatus = await isPackagerRunning(port);

if (
Expand Down
Loading

0 comments on commit d011058

Please sign in to comment.