diff --git a/packages/cli-platform-apple/src/__tests__/pods.test.ts b/packages/cli-platform-apple/src/__tests__/pods.test.ts index 00b9d5094..9ba0a8112 100644 --- a/packages/cli-platform-apple/src/__tests__/pods.test.ts +++ b/packages/cli-platform-apple/src/__tests__/pods.test.ts @@ -1,3 +1,4 @@ +import path from 'path'; import {writeFiles, getTempDirectory, cleanup} from '../../../../jest/helpers'; import installPods from '../tools/installPods'; import resolvePods, { @@ -51,6 +52,7 @@ const DIR = getTempDirectory('root_test'); const createTempFiles = (rest?: Record) => { writeFiles(DIR, { 'package.json': JSON.stringify(packageJson), + 'ios/Podfile': '', ...rest, }); }; @@ -83,9 +85,9 @@ describe('getPlatformDependencies', () => { describe('resolvePods', () => { it('should install pods if they are not installed', async () => { - createTempFiles({'ios/Podfile/Manifest.lock': ''}); + createTempFiles(); - await resolvePods(DIR, {}, 'ios'); + await resolvePods(DIR, path.join(DIR, 'ios'), {}, 'ios'); expect(installPods).toHaveBeenCalled(); }); @@ -93,7 +95,9 @@ describe('resolvePods', () => { it('should install pods when force option is set to true', async () => { createTempFiles(); - await resolvePods(DIR, {}, 'ios', {forceInstall: true}); + await resolvePods(DIR, path.join(DIR, 'ios'), {}, 'ios', { + forceInstall: true, + }); expect(installPods).toHaveBeenCalled(); }); @@ -101,7 +105,7 @@ describe('resolvePods', () => { it('should install pods when there is no cached hash of dependencies', async () => { createTempFiles(); - await resolvePods(DIR, {}, 'ios'); + await resolvePods(DIR, path.join(DIR, 'ios'), {}, 'ios'); expect(mockSet).toHaveBeenCalledWith( packageJson.name, @@ -111,22 +115,26 @@ describe('resolvePods', () => { }); it('should skip pods installation if the cached hash and current hash are the same', async () => { - createTempFiles({'ios/Pods/Manifest.lock': ''}); + createTempFiles({ + 'ios/Pods/Manifest.lock': '', + 'ios/Podfile.lock': `PODFILE CHECKSUM: ${dependencyHash}`, + }); mockGet.mockImplementation(() => dependencyHash); - await resolvePods(DIR, {}, 'ios'); + await resolvePods(DIR, path.join(DIR, 'ios'), {}, 'ios'); expect(installPods).not.toHaveBeenCalled(); }); it('should install pods if the cached hash and current hash are different', async () => { - createTempFiles({'ios/Pods/Manifest.lock': ''}); + createTempFiles(); mockGet.mockImplementation(() => dependencyHash); await resolvePods( DIR, + path.join(DIR, 'ios'), { dep1: { name: 'dep1', diff --git a/packages/cli-platform-apple/src/commands/buildCommand/createBuild.ts b/packages/cli-platform-apple/src/commands/buildCommand/createBuild.ts index 3e06b6c10..795c6bb07 100644 --- a/packages/cli-platform-apple/src/commands/buildCommand/createBuild.ts +++ b/packages/cli-platform-apple/src/commands/buildCommand/createBuild.ts @@ -27,10 +27,16 @@ const createBuild = ? await getArchitecture(platformConfig.sourceDir) : undefined; - await resolvePods(ctx.root, ctx.dependencies, platformName, { - forceInstall: args.forcePods, - newArchEnabled: isAppRunningNewArchitecture, - }); + await resolvePods( + ctx.root, + platformConfig.sourceDir, + ctx.dependencies, + platformName, + { + forceInstall: args.forcePods, + newArchEnabled: isAppRunningNewArchitecture, + }, + ); installedPods = true; } diff --git a/packages/cli-platform-apple/src/commands/runCommand/createRun.ts b/packages/cli-platform-apple/src/commands/runCommand/createRun.ts index 486b9ebf5..62525725b 100644 --- a/packages/cli-platform-apple/src/commands/runCommand/createRun.ts +++ b/packages/cli-platform-apple/src/commands/runCommand/createRun.ts @@ -72,10 +72,16 @@ const createRun = ? await getArchitecture(platformConfig.sourceDir) : undefined; - await resolvePods(ctx.root, ctx.dependencies, platformName, { - forceInstall: args.forcePods, - newArchEnabled: isAppRunningNewArchitecture, - }); + await resolvePods( + ctx.root, + platformConfig.sourceDir, + ctx.dependencies, + platformName, + { + forceInstall: args.forcePods, + newArchEnabled: isAppRunningNewArchitecture, + }, + ); installedPods = true; } diff --git a/packages/cli-platform-apple/src/tools/pods.ts b/packages/cli-platform-apple/src/tools/pods.ts index 2895c3d9f..117bf10b8 100644 --- a/packages/cli-platform-apple/src/tools/pods.ts +++ b/packages/cli-platform-apple/src/tools/pods.ts @@ -8,7 +8,6 @@ import { getLoader, } from '@react-native-community/cli-tools'; import installPods from './installPods'; -import findPodfilePath from '../config/findPodfilePath'; import { DependencyConfig, IOSDependencyConfig, @@ -61,10 +60,30 @@ export function generateMd5Hash(text: string) { return createHash('md5').update(text).digest('hex'); } -export function compareMd5Hashes(hash1: string, hash2: string) { +export function compareMd5Hashes(hash1?: string, hash2?: string) { return hash1 === hash2; } +async function getChecksum( + podfileLockPath: string, +): Promise { + try { + const file = fs.readFileSync(podfileLockPath, 'utf8'); + + const checksumLine = file + .split('\n') + .find((line) => line.includes('PODFILE CHECKSUM')); + + if (checksumLine) { + return checksumLine.split(': ')[1]; + } + + return undefined; + } catch { + return undefined; + } +} + async function install( packageJson: Record, cachedDependenciesHash: string | undefined, @@ -79,24 +98,28 @@ async function install( }); cacheManager.set(packageJson.name, 'dependencies', currentDependenciesHash); loader.succeed(); - } catch { + } catch (error) { loader.fail(); throw new CLIError( `Something when wrong while installing CocoaPods. Please run ${chalk.bold( 'pod install', )} manually`, + error as Error, ); } } export default async function resolvePods( root: string, + sourceDir: string, nativeDependencies: NativeDependencies, platformName: ApplePlatform, options?: ResolvePodsOptions, ) { const packageJson = getPackageJson(root); - const podfilePath = findPodfilePath(root, platformName); + const podfilePath = path.join(sourceDir, 'Podfile'); // sourceDir is calculated based on Podfile location, see getProjectConfig() + + const podfileLockPath = path.join(sourceDir, 'Podfile.lock'); const platformFolderPath = podfilePath ? podfilePath.slice(0, podfilePath.lastIndexOf('/')) : path.join(root, platformName); @@ -108,6 +131,18 @@ export default async function resolvePods( ); const dependenciesString = dependenciesToString(platformDependencies); const currentDependenciesHash = generateMd5Hash(dependenciesString); + // Users can manually add dependencies to Podfile, so we can't entirely rely on `dependencies` from `config`'s output. + const currentPodfileHash = generateMd5Hash( + fs.readFileSync(podfilePath, 'utf8'), + ); + let currentPodfileLockChecksum = await getChecksum(podfileLockPath); + + const cachedPodfileHash = cacheManager.get(packageJson.name, 'podfile'); + const cachedPodfileLockChecksum = cacheManager.get( + packageJson.name, + 'podfileLock', + ); + const cachedDependenciesHash = cacheManager.get( packageJson.name, 'dependencies', @@ -120,13 +155,20 @@ export default async function resolvePods( currentDependenciesHash, platformFolderPath, ); - } else if (arePodsInstalled && cachedDependenciesHash === undefined) { - cacheManager.set(packageJson.name, 'dependencies', currentDependenciesHash); } else if ( - !cachedDependenciesHash || - !compareMd5Hashes(currentDependenciesHash, cachedDependenciesHash) || - !arePodsInstalled + arePodsInstalled && + compareMd5Hashes(currentDependenciesHash, cachedDependenciesHash) && + compareMd5Hashes(currentPodfileHash, cachedPodfileHash) && + compareMd5Hashes(currentPodfileLockChecksum, cachedPodfileLockChecksum) ) { + cacheManager.set(packageJson.name, 'dependencies', currentDependenciesHash); + cacheManager.set(packageJson.name, 'podfile', currentPodfileHash); + cacheManager.set( + packageJson.name, + 'podfileLock', + currentPodfileLockChecksum ?? '', + ); + } else { const loader = getLoader('Installing CocoaPods...'); try { await installPods(loader, { @@ -139,13 +181,22 @@ export default async function resolvePods( 'dependencies', currentDependenciesHash, ); + cacheManager.set(packageJson.name, 'podfile', currentPodfileHash); + // We need to read again the checksum because value changed after running `pod install` + currentPodfileLockChecksum = await getChecksum(podfileLockPath); + cacheManager.set( + packageJson.name, + 'podfileLock', + currentPodfileLockChecksum ?? '', + ); loader.succeed(); - } catch { + } catch (error) { loader.fail(); throw new CLIError( `Something when wrong while installing CocoaPods. Please run ${chalk.bold( 'pod install', )} manually`, + error as Error, ); } } diff --git a/packages/cli-tools/src/cacheManager.ts b/packages/cli-tools/src/cacheManager.ts index 227eb674a..dbcf48afe 100644 --- a/packages/cli-tools/src/cacheManager.ts +++ b/packages/cli-tools/src/cacheManager.ts @@ -10,6 +10,8 @@ type CacheKey = | 'lastChecked' | 'latestVersion' | 'dependencies' + | 'podfile' + | 'podfileLock' | 'lastUsedIOSDeviceId'; type Cache = {[key in CacheKey]?: string};