From 093db89467d5b94db925b81051bd58bc9bdfd826 Mon Sep 17 00:00:00 2001
From: szymonrybczak <szymon.rybczak@gmail.com>
Date: Wed, 3 Jul 2024 15:32:00 +0200
Subject: [PATCH 1/6] fix(apple): compare also `Podfile` and `Podfile.lock`
 when deciding to install Cocoapods

---
 packages/cli-config-apple/src/tools/pods.ts   | 38 +++++++++++++++----
 .../src/commands/buildCommand/createBuild.ts  | 14 +++++--
 .../src/commands/runCommand/createRun.ts      | 14 +++++--
 packages/cli-tools/src/cacheManager.ts        |  2 +
 4 files changed, 52 insertions(+), 16 deletions(-)

diff --git a/packages/cli-config-apple/src/tools/pods.ts b/packages/cli-config-apple/src/tools/pods.ts
index f2ea7da9a..404793bbc 100644
--- a/packages/cli-config-apple/src/tools/pods.ts
+++ b/packages/cli-config-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,7 +60,7 @@ 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;
 }
 
@@ -91,12 +90,15 @@ async function install(
 
 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 +110,20 @@ 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'),
+  );
+  const currentPodfileLockHash = generateMd5Hash(
+    fs.readFileSync(podfileLockPath, 'utf8'),
+  );
+
+  const cachedPodfileHash = cacheManager.get(packageJson.name, 'podfile');
+  const cachedPodfileLockHash = cacheManager.get(
+    packageJson.name,
+    'podfileLock',
+  );
+
   const cachedDependenciesHash = cacheManager.get(
     packageJson.name,
     'dependencies',
@@ -120,13 +136,17 @@ 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(currentPodfileLockHash, cachedPodfileLockHash)
   ) {
+    cacheManager.set(packageJson.name, 'dependencies', currentDependenciesHash);
+    cacheManager.set(packageJson.name, 'podfile', currentPodfileHash);
+    cacheManager.set(packageJson.name, 'podfileLock', currentPodfileLockHash);
+  } else {
+    console.log('eh');
     const loader = getLoader('Installing CocoaPods...');
     try {
       await installPods(loader, {
@@ -139,6 +159,8 @@ export default async function resolvePods(
         'dependencies',
         currentDependenciesHash,
       );
+      cacheManager.set(packageJson.name, 'podfile', currentPodfileHash);
+      cacheManager.set(packageJson.name, 'podfileLock', currentPodfileLockHash);
       loader.succeed();
     } catch {
       loader.fail();
diff --git a/packages/cli-platform-apple/src/commands/buildCommand/createBuild.ts b/packages/cli-platform-apple/src/commands/buildCommand/createBuild.ts
index 4a2e480a7..efedd2288 100644
--- a/packages/cli-platform-apple/src/commands/buildCommand/createBuild.ts
+++ b/packages/cli-platform-apple/src/commands/buildCommand/createBuild.ts
@@ -29,10 +29,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 53b573df7..4c19fcc41 100644
--- a/packages/cli-platform-apple/src/commands/runCommand/createRun.ts
+++ b/packages/cli-platform-apple/src/commands/runCommand/createRun.ts
@@ -84,10 +84,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-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};
 

From ef17f9ca14936c89834ecf92bf77ca5ac4023278 Mon Sep 17 00:00:00 2001
From: szymonrybczak <szymon.rybczak@gmail.com>
Date: Thu, 4 Jul 2024 14:25:18 +0200
Subject: [PATCH 2/6] fix: save lock checksum instead of hash

---
 packages/cli-config-apple/src/tools/pods.ts | 54 +++++++++++++++++----
 1 file changed, 44 insertions(+), 10 deletions(-)

diff --git a/packages/cli-config-apple/src/tools/pods.ts b/packages/cli-config-apple/src/tools/pods.ts
index 404793bbc..2c35cdd3f 100644
--- a/packages/cli-config-apple/src/tools/pods.ts
+++ b/packages/cli-config-apple/src/tools/pods.ts
@@ -13,6 +13,7 @@ import {
   IOSDependencyConfig,
 } from '@react-native-community/cli-types';
 import {ApplePlatform} from '../types';
+import readline from 'readline';
 
 interface ResolvePodsOptions {
   forceInstall?: boolean;
@@ -64,6 +65,30 @@ export function compareMd5Hashes(hash1?: string, hash2?: string) {
   return hash1 === hash2;
 }
 
+async function getChecksum(podfileLockPath: string) {
+  const fileStream = fs.createReadStream(podfileLockPath);
+
+  const rl = readline.createInterface({
+    input: fileStream,
+    crlfDelay: Infinity,
+  });
+
+  let lines = [];
+  for await (const line of rl) {
+    lines.push(line);
+  }
+
+  lines = lines.reverse();
+
+  for (const line of lines) {
+    if (line.includes('PODFILE CHECKSUM')) {
+      return line.split(': ')[1];
+    }
+  }
+
+  return undefined;
+}
+
 async function install(
   packageJson: Record<string, any>,
   cachedDependenciesHash: string | undefined,
@@ -78,12 +103,13 @@ 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,
     );
   }
 }
@@ -114,12 +140,10 @@ export default async function resolvePods(
   const currentPodfileHash = generateMd5Hash(
     fs.readFileSync(podfilePath, 'utf8'),
   );
-  const currentPodfileLockHash = generateMd5Hash(
-    fs.readFileSync(podfileLockPath, 'utf8'),
-  );
+  let currentPodfileLockChecksum = await getChecksum(podfileLockPath);
 
   const cachedPodfileHash = cacheManager.get(packageJson.name, 'podfile');
-  const cachedPodfileLockHash = cacheManager.get(
+  const cachedPodfileLockChecksum = cacheManager.get(
     packageJson.name,
     'podfileLock',
   );
@@ -140,13 +164,16 @@ export default async function resolvePods(
     arePodsInstalled &&
     compareMd5Hashes(currentDependenciesHash, cachedDependenciesHash) &&
     compareMd5Hashes(currentPodfileHash, cachedPodfileHash) &&
-    compareMd5Hashes(currentPodfileLockHash, cachedPodfileLockHash)
+    compareMd5Hashes(currentPodfileLockChecksum, cachedPodfileLockChecksum)
   ) {
     cacheManager.set(packageJson.name, 'dependencies', currentDependenciesHash);
     cacheManager.set(packageJson.name, 'podfile', currentPodfileHash);
-    cacheManager.set(packageJson.name, 'podfileLock', currentPodfileLockHash);
+    cacheManager.set(
+      packageJson.name,
+      'podfileLock',
+      currentPodfileLockChecksum ?? '',
+    );
   } else {
-    console.log('eh');
     const loader = getLoader('Installing CocoaPods...');
     try {
       await installPods(loader, {
@@ -160,14 +187,21 @@ export default async function resolvePods(
         currentDependenciesHash,
       );
       cacheManager.set(packageJson.name, 'podfile', currentPodfileHash);
-      cacheManager.set(packageJson.name, 'podfileLock', currentPodfileLockHash);
+      // 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,
       );
     }
   }

From 73b67f27bbfb949ec75e29e370f4ca712d019546 Mon Sep 17 00:00:00 2001
From: Szymon Rybczak <szymon.rybczak@gmail.com>
Date: Tue, 9 Jul 2024 14:23:48 +0200
Subject: [PATCH 3/6] Update packages/cli-platform-apple/src/tools/pods.ts

Co-authored-by: Riccardo Cipolleschi <riccardo.cipolleschi@gmail.com>
---
 packages/cli-config-apple/src/tools/pods.ts | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/packages/cli-config-apple/src/tools/pods.ts b/packages/cli-config-apple/src/tools/pods.ts
index 2c35cdd3f..db234d788 100644
--- a/packages/cli-config-apple/src/tools/pods.ts
+++ b/packages/cli-config-apple/src/tools/pods.ts
@@ -80,11 +80,9 @@ async function getChecksum(podfileLockPath: string) {
 
   lines = lines.reverse();
 
-  for (const line of lines) {
-    if (line.includes('PODFILE CHECKSUM')) {
-      return line.split(': ')[1];
-    }
-  }
+  return lines
+    .filter((line) => line.includes('PODFILE CHECKSUM'))[0]
+    .split(': ')[1]
 
   return undefined;
 }

From 1c53b2b321b02f85ab33ad67564b38afedeb5983 Mon Sep 17 00:00:00 2001
From: szymonrybczak <szymon.rybczak@gmail.com>
Date: Tue, 9 Jul 2024 14:40:28 +0200
Subject: [PATCH 4/6] fix: simplify receiving checksum from `Podfile`

---
 packages/cli-config-apple/src/tools/pods.ts | 29 ++++++++-------------
 1 file changed, 11 insertions(+), 18 deletions(-)

diff --git a/packages/cli-config-apple/src/tools/pods.ts b/packages/cli-config-apple/src/tools/pods.ts
index db234d788..25e0f1526 100644
--- a/packages/cli-config-apple/src/tools/pods.ts
+++ b/packages/cli-config-apple/src/tools/pods.ts
@@ -13,7 +13,6 @@ import {
   IOSDependencyConfig,
 } from '@react-native-community/cli-types';
 import {ApplePlatform} from '../types';
-import readline from 'readline';
 
 interface ResolvePodsOptions {
   forceInstall?: boolean;
@@ -65,26 +64,20 @@ export function compareMd5Hashes(hash1?: string, hash2?: string) {
   return hash1 === hash2;
 }
 
-async function getChecksum(podfileLockPath: string) {
-  const fileStream = fs.createReadStream(podfileLockPath);
+async function getChecksum(
+  podfileLockPath: string,
+): Promise<string | undefined> {
+  const file = fs.readFileSync(podfileLockPath, 'utf8');
 
-  const rl = readline.createInterface({
-    input: fileStream,
-    crlfDelay: Infinity,
-  });
+  const checksumLine = file
+    .split('\n')
+    .find((line) => line.includes('PODFILE CHECKSUM'));
 
-  let lines = [];
-  for await (const line of rl) {
-    lines.push(line);
+  if (checksumLine) {
+    return checksumLine.split(': ')[1];
+  } else {
+    return undefined;
   }
-
-  lines = lines.reverse();
-
-  return lines
-    .filter((line) => line.includes('PODFILE CHECKSUM'))[0]
-    .split(': ')[1]
-
-  return undefined;
 }
 
 async function install(

From 374e57f6116d47951feb15c9f6b9ca8eec04a5c7 Mon Sep 17 00:00:00 2001
From: szymonrybczak <szymon.rybczak@gmail.com>
Date: Sat, 31 Aug 2024 18:41:45 +0200
Subject: [PATCH 5/6] fix: return undefined if `Podfile.lock` not found

---
 packages/cli-config-apple/src/tools/pods.ts | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/packages/cli-config-apple/src/tools/pods.ts b/packages/cli-config-apple/src/tools/pods.ts
index 25e0f1526..b3edf7327 100644
--- a/packages/cli-config-apple/src/tools/pods.ts
+++ b/packages/cli-config-apple/src/tools/pods.ts
@@ -67,15 +67,19 @@ export function compareMd5Hashes(hash1?: string, hash2?: string) {
 async function getChecksum(
   podfileLockPath: string,
 ): Promise<string | undefined> {
-  const file = fs.readFileSync(podfileLockPath, 'utf8');
+  try {
+    const file = fs.readFileSync(podfileLockPath, 'utf8');
 
-  const checksumLine = file
-    .split('\n')
-    .find((line) => line.includes('PODFILE CHECKSUM'));
+    const checksumLine = file
+      .split('\n')
+      .find((line) => line.includes('PODFILE CHECKSUM'));
 
-  if (checksumLine) {
-    return checksumLine.split(': ')[1];
-  } else {
+    if (checksumLine) {
+      return checksumLine.split(': ')[1];
+    }
+
+    return undefined;
+  } catch {
     return undefined;
   }
 }

From 262836e36e788714c905a95d3734a6a77ee06644 Mon Sep 17 00:00:00 2001
From: szymonrybczak <szymon.rybczak@gmail.com>
Date: Sat, 31 Aug 2024 18:42:17 +0200
Subject: [PATCH 6/6] fix: sync unit tests with current impl

---
 .../src/__tests__/pods.test.ts                | 22 +++++++++++++------
 1 file changed, 15 insertions(+), 7 deletions(-)

diff --git a/packages/cli-config-apple/src/__tests__/pods.test.ts b/packages/cli-config-apple/src/__tests__/pods.test.ts
index 00b9d5094..9ba0a8112 100644
--- a/packages/cli-config-apple/src/__tests__/pods.test.ts
+++ b/packages/cli-config-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<string, string>) => {
   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',