diff --git a/Readme.md b/Readme.md
index e2e85e9b..1ef29ea4 100644
--- a/Readme.md
+++ b/Readme.md
@@ -146,7 +146,7 @@ If your project targets multiple platforms, keep these packages inside Apple-onl
```xml
-
+
```
@@ -172,7 +172,7 @@ The lists below distinguish the required published baseline on [nuget.org](https
Firebase and GoogleAppMeasurement `12.19.0` are the required published baseline for this update. Verify their publication through the [approved CI publishing workflow](docs/FIREBASE_RELEASES.md) before merging the 12.19.3 preparation. See the [12.19 native review and validation notes](docs/firebase-12.19-update.md), the [12.18 notes](docs/firebase-12.18-update.md), and the earlier [12.17 notes](docs/firebase-12.17-update.md).
-This checkout prepares Firebase and GoogleAppMeasurement `12.19.3`. Publication remains pending. See the [12.19.3 native review and validation notes](docs/firebase-12.19.3-update.md).
+This checkout prepares Firebase and GoogleAppMeasurement `12.19.3.1`, plus Google Sign-In `10.1.0.1`, with native-cache validation fixes. Native SDK versions are unchanged; publication remains pending. See the [12.19.3 native review and validation notes](docs/firebase-12.19.3-update.md).
### Published and prepared versions
@@ -180,22 +180,22 @@ This checkout prepares Firebase and GoogleAppMeasurement `12.19.3`. Publication
| Package | Required published baseline | Prepared version |
| --- | --- | --- |
-| `ABTesting` | `12.19.0` | `12.19.3` |
-| `Analytics` | `12.19.0` | `12.19.3` |
-| `AppCheck` | `12.19.0` | `12.19.3` |
-| `AppDistribution` | `12.19.0` | `12.19.3` |
-| `Auth` | `12.19.0` | `12.19.3` |
-| `CloudFirestore` | `12.19.0` | `12.19.3` |
-| `CloudFunctions` | `12.19.0` | `12.19.3` |
-| `CloudMessaging` | `12.19.0` | `12.19.3` |
-| `Core` | `12.19.0` | `12.19.3` |
-| `Crashlytics` | `12.19.0` | `12.19.3` |
-| `Database` | `12.19.0` | `12.19.3` |
-| `InAppMessaging` | `12.19.0` | `12.19.3` |
-| `Installations` | `12.19.0` | `12.19.3` |
-| `PerformanceMonitoring` | `12.19.0` | `12.19.3` |
-| `RemoteConfig` | `12.19.0` | `12.19.3` |
-| `Storage` | `12.19.0` | `12.19.3` |
+| `ABTesting` | `12.19.0` | `12.19.3.1` |
+| `Analytics` | `12.19.0` | `12.19.3.1` |
+| `AppCheck` | `12.19.0` | `12.19.3.1` |
+| `AppDistribution` | `12.19.0` | `12.19.3.1` |
+| `Auth` | `12.19.0` | `12.19.3.1` |
+| `CloudFirestore` | `12.19.0` | `12.19.3.1` |
+| `CloudFunctions` | `12.19.0` | `12.19.3.1` |
+| `CloudMessaging` | `12.19.0` | `12.19.3.1` |
+| `Core` | `12.19.0` | `12.19.3.1` |
+| `Crashlytics` | `12.19.0` | `12.19.3.1` |
+| `Database` | `12.19.0` | `12.19.3.1` |
+| `InAppMessaging` | `12.19.0` | `12.19.3.1` |
+| `Installations` | `12.19.0` | `12.19.3.1` |
+| `PerformanceMonitoring` | `12.19.0` | `12.19.3.1` |
+| `RemoteConfig` | `12.19.0` | `12.19.3.1` |
+| `Storage` | `12.19.0` | `12.19.3.1` |
#### Google packages (`AdamE.Google.iOS.*`)
@@ -203,7 +203,7 @@ This checkout prepares Firebase and GoogleAppMeasurement `12.19.3`. Publication
| --- | --- | --- |
| `Maps` | `9.2.0.8` | `9.2.0.10` |
| `Places` | `7.4.0.2` | `7.4.0.4` |
-| `SignIn` | `10.0.0` | `10.1.0.0` |
+| `SignIn` | `10.0.0` | `10.1.0.1` |
#### Google support packages (`AdamE.Google.iOS.*`)
@@ -211,13 +211,13 @@ These packages are usually consumed transitively rather than referenced directly
| Package | Required published baseline | Prepared version |
| --- | --- | --- |
-| `AppCheckCore` | `11.3.2` | `11.3.2` |
-| `GoogleAppMeasurement` | `12.19.0` | `12.19.3` |
-| `GoogleDataTransport` | `10.1.0.6` | `10.1.0.6` |
-| `GoogleUtilities` | `8.1.5` | `8.1.5` |
-| `GTMSessionFetcher` | `3.5.0.8` | `3.5.0.8` |
-| `Nanopb` | `3.30910.0.6` | `3.30910.0.6` |
-| `PromisesObjC` | `2.4.0.8` | `2.4.0.8` |
+| `AppCheckCore` | `11.3.2` | `11.3.2.1` |
+| `GoogleAppMeasurement` | `12.19.0` | `12.19.3.1` |
+| `GoogleDataTransport` | `10.1.0.6` | `10.1.0.7` |
+| `GoogleUtilities` | `8.1.5` | `8.1.5.1` |
+| `GTMSessionFetcher` | `3.5.0.8` | `3.5.0.9` |
+| `Nanopb` | `3.30910.0.6` | `3.30910.0.7` |
+| `PromisesObjC` | `2.4.0.8` | `2.4.0.9` |
#### ML Kit packages (`AdamE.MLKit.iOS.*`)
diff --git a/common.cake b/common.cake
index c94642ee..0f4e3a50 100644
--- a/common.cake
+++ b/common.cake
@@ -551,6 +551,87 @@ void BuildXcodeFatFramework (FilePath xcodeProject, PodSpec [] podSpecs, Platfor
}
}
+// A completion record binds the requested inputs to every output file. Rechecking the
+// inventory rejects deleted/truncated binaries and bundles, not just missing directories.
+class NativeCacheRecord
+{
+ public static string Hash (string text)
+ {
+ using (var sha = System.Security.Cryptography.SHA256.Create ())
+ return Convert.ToBase64String (sha.ComputeHash (System.Text.Encoding.UTF8.GetBytes (text)));
+ }
+
+ public static string FileHash (string path)
+ {
+ using (var stream = System.IO.File.OpenRead (path))
+ using (var sha = System.Security.Cryptography.SHA256.Create ())
+ return Convert.ToBase64String (sha.ComputeHash (stream));
+ }
+
+ public static string Inventory (string[] roots)
+ {
+ var entries = new List ();
+ foreach (var root in roots) {
+ entries.Add ("root");
+ var firstEntry = entries.Count;
+ InventoryDirectory (root, new System.IO.DirectoryInfo (root), entries);
+ if (entries.Count == firstEntry)
+ throw new System.IO.IOException ($"Native cache output is empty: {root}");
+ }
+ return Hash (string.Join ("\n", entries));
+ }
+
+ static void InventoryDirectory (string root, System.IO.DirectoryInfo directory, List entries)
+ {
+ foreach (var entry in directory.EnumerateFileSystemInfos ().OrderBy (p => p.Name, StringComparer.Ordinal)) {
+ var relative = System.IO.Path.GetRelativePath (root, entry.FullName).Replace ('\\', '/');
+ var pathKey = Convert.ToBase64String (System.Text.Encoding.UTF8.GetBytes (relative));
+ // Frameworks may contain directory aliases or dangling links in nested dependencies.
+ // Record each link without walking through it or opening its target.
+ var linkTarget = entry.LinkTarget;
+ if (linkTarget != null)
+ entries.Add (pathKey + ":link:" + Hash (linkTarget));
+ else if (entry is System.IO.DirectoryInfo child)
+ InventoryDirectory (root, child, entries);
+ else
+ entries.Add (pathKey + ":" + FileHash (entry.FullName));
+ }
+ }
+
+ public static bool Matches (string stamp, string identity, string[] roots)
+ {
+ if (!System.IO.File.Exists (stamp))
+ return false;
+ try {
+ return System.IO.File.ReadAllText (stamp) == Hash (identity) + "\n" + Inventory (roots);
+ } catch (System.IO.IOException) {
+ return false;
+ } catch (UnauthorizedAccessException) {
+ return false;
+ }
+ }
+
+ public static void Complete (string stamp, string identity, string inventory)
+ {
+ var temporary = stamp + ".tmp";
+ System.IO.File.WriteAllText (temporary, Hash (identity) + "\n" + inventory);
+ System.IO.File.Move (temporary, stamp, true);
+ }
+}
+
+string NativeXcodeIdentity ()
+{
+ var args = new ProcessArgumentBuilder ();
+ args.Append ("-version");
+ var settings = new ProcessSettings { Arguments = args, RedirectStandardOutput = true };
+ using (var process = StartAndReturnProcess ("xcodebuild", settings)) {
+ var output = string.Join ("\n", process.GetStandardOutput ());
+ process.WaitForExit ();
+ ThrowIfProcessFailed ("xcodebuild -version", process.GetExitCode ());
+ return output + "\n" + EnvironmentVariable ("DEVELOPER_DIR");
+ }
+}
+
void BuildXcodeXcframework (FilePath xcodeProject, PodSpec [] podSpecs, PlatformV2 [] platforms, DirectoryPath workingDirectory = null, Dictionary buildSettings = null)
{
if (!IsRunningOnUnix ()) {
@@ -558,6 +639,9 @@ void BuildXcodeXcframework (FilePath xcodeProject, PodSpec [] podSpecs, Platform
return;
}
+ if (podSpecs.Length == 0)
+ return;
+
Information ("Building the following frameworks:");
Information (string.Join (",\n", podSpecs.Select (ps => ps.FrameworkName).ToArray ()));
@@ -572,6 +656,7 @@ void BuildXcodeXcframework (FilePath xcodeProject, PodSpec [] podSpecs, Platform
if (!buildSettings.ContainsKey("EXPANDED_CODE_SIGN_IDENTITY"))
buildSettings["EXPANDED_CODE_SIGN_IDENTITY"] = "";
+ var xcodeIdentity = NativeXcodeIdentity ();
foreach (var podSpec in podSpecs) {
Information ($"Building the following framework: {podSpec.FrameworkName}...");
@@ -591,9 +676,6 @@ void BuildXcodeXcframework (FilePath xcodeProject, PodSpec [] podSpecs, Platform
var archiveFrameworksDirectory = archiveDirectory.Combine ("Products/Library/Frameworks");
xcodeBuildArgs.Append ($"-framework {archiveFrameworksDirectory}/{podSpec.FrameworkName}.framework");
- if (DirectoryExists (archiveDirectory))
- continue;
-
var buildSettingsCopy = new Dictionary (buildSettings);
buildSettingsCopy ["SKIP_INSTALL"] = "NO";
buildSettingsCopy ["BUILD_LIBRARY_FOR_DISTRIBUTION"] = "YES";
@@ -613,28 +695,52 @@ void BuildXcodeXcframework (FilePath xcodeProject, PodSpec [] podSpecs, Platform
if (archs != null)
xcodeBuildSettings.Arch = string.Join (',', archs.Select (a => a.Value).ToArray ());
+ var archivePath = MakeAbsolute (archiveDirectory).FullPath;
+ var stamp = archivePath + ".cache";
+ var projectPath = MakeAbsolute (workingDirectory.CombineWithFilePath (xcodeProject)).FullPath;
+ var lockPath = MakeAbsolute (workingDirectory.CombineWithFilePath ("Podfile.lock")).FullPath;
+ var manifestPath = MakeAbsolute (workingDirectory.CombineWithFilePath ("Pods/Manifest.lock")).FullPath;
+ // Unlocked and local source builds cannot safely reuse archives.
+ var supportPath = MakeAbsolute (workingDirectory.Combine ("Pods/Target Support Files")).FullPath;
+ var podfilePath = MakeAbsolute (workingDirectory.CombineWithFilePath ("Podfile")).FullPath;
+ var cacheable = DirectoryExists (supportPath) && FileExists (podfilePath) && FileExists (lockPath) && FileExists (manifestPath) &&
+ podSpecs.All (p => p.Source != PodSpec.PodSource.LocalPath);
+ var identity = string.Join ("\n", new [] {
+ "native-archive-v1", xcodeIdentity, projectPath,
+ NativeCacheRecord.Inventory (new [] { projectPath }),
+ cacheable ? NativeCacheRecord.Inventory (new [] { supportPath }) : "unlocked",
+ cacheable ? NativeCacheRecord.FileHash (podfilePath) : "unlocked",
+ cacheable ? NativeCacheRecord.FileHash (lockPath) : "unlocked",
+ cacheable ? NativeCacheRecord.FileHash (manifestPath) : "unlocked",
+ string.Join ("\n", podSpecs.SelectMany (p => p.BuildPodLines ())),
+ target, podSpec.FrameworkName, platform.Sdk.ToString (), sdk.ToString (),
+ xcodeBuildSettings.Arch ?? "default",
+ string.Join ("\n", buildSettingsCopy.OrderBy (p => p.Key, StringComparer.Ordinal).Select (p => p.Key + "=" + p.Value))
+ });
+ var roots = new [] { archivePath };
+ if (cacheable && NativeCacheRecord.Matches (stamp, identity, roots))
+ continue;
+
+ // Invalidate before any destructive work. An interrupted build never leaves a hit.
+ if (System.IO.File.Exists (stamp))
+ System.IO.File.Delete (stamp);
+ if (DirectoryExists (archiveDirectory))
+ DeleteDirectory (archiveDirectory, new DeleteDirectorySettings { Recursive = true, Force = true });
XCodeBuild (xcodeBuildSettings);
+ var frameworkPath = archiveFrameworksDirectory.Combine ($"{podSpec.FrameworkName}.framework");
+ // Catalyst uses a versioned framework with its plist under Resources.
+ var hasInfoPlist = FileExists (frameworkPath.CombineWithFilePath ("Info.plist")) ||
+ FileExists (frameworkPath.CombineWithFilePath ("Resources/Info.plist"));
+ if (!hasInfoPlist ||
+ !FileExists (frameworkPath.CombineWithFilePath (podSpec.FrameworkName)))
+ throw new Exception ($"Incomplete native archive: {archiveDirectory}");
+ if (cacheable)
+ NativeCacheRecord.Complete (stamp, identity, NativeCacheRecord.Inventory (roots));
Information ($"Done building {podSpec.FrameworkName} framework with {platform.Sdk} platform SDK...");
- var builtFrameworkDirectories = GetDirectories (archiveFrameworksDirectory.Combine ("*.framework").ToString ());
-
- Information ($"Copying the following built frameworks to its respective archives:");
- Information (string.Join (",\n", builtFrameworkDirectories.Select (b => b.ToString ()).ToArray ()));
-
- foreach (var directory in builtFrameworkDirectories) {
- var builtFrameworkName = directory.GetDirectoryName ().Replace (".framework", "");
-
- if (string.Equals (builtFrameworkName, podSpec.FrameworkName))
- continue;
-
- DirectoryPath destinationDirectory = Directory (archiveFrameworksDirectory.ToString ().Replace ($"{podSpec.FrameworkName}", $"{builtFrameworkName}"));
- destinationDirectory = destinationDirectory.Combine ($"{builtFrameworkName}.framework");
-
- Information ($"Copying {directory} to {destinationDirectory}");
-
- CopyDirectoryWithSymlinks (directory, destinationDirectory);
- }
+ // Each scheme owns its archive and completion record. Do not copy dependency
+ // frameworks into another scheme's archive behind that record's back.
}
Information ($"Building {podSpec.FrameworkName} xcframework...");
diff --git a/components.cake b/components.cake
index 7613c379..64e04da9 100644
--- a/components.cake
+++ b/components.cake
@@ -1,20 +1,20 @@
// Firebase artifacts available to be built. These artifacts generate NuGets.
-Artifact FIREBASE_AB_TESTING_ARTIFACT = new Artifact ("Firebase.ABTesting", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "ABTesting");
-Artifact FIREBASE_ANALYTICS_ARTIFACT = new Artifact ("Firebase.Analytics", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "Analytics");
-Artifact FIREBASE_AUTH_ARTIFACT = new Artifact ("Firebase.Auth", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "Auth");
-Artifact FIREBASE_CLOUD_FIRESTORE_ARTIFACT = new Artifact ("Firebase.CloudFirestore", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "CloudFirestore");
-Artifact FIREBASE_CLOUD_FUNCTIONS_ARTIFACT = new Artifact ("Firebase.CloudFunctions", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "CloudFunctions");
-Artifact FIREBASE_CLOUD_MESSAGING_ARTIFACT = new Artifact ("Firebase.CloudMessaging", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "CloudMessaging");
-Artifact FIREBASE_CORE_ARTIFACT = new Artifact ("Firebase.Core", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "Core");
-Artifact FIREBASE_CRASHLYTICS_ARTIFACT = new Artifact ("Firebase.Crashlytics", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "Crashlytics");
-Artifact FIREBASE_DATABASE_ARTIFACT = new Artifact ("Firebase.Database", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "Database");
-Artifact FIREBASE_IN_APP_MESSAGING_ARTIFACT = new Artifact ("Firebase.InAppMessaging", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "InAppMessaging");
-Artifact FIREBASE_INSTALLATIONS_ARTIFACT = new Artifact ("Firebase.Installations", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "Installations");
-Artifact FIREBASE_PERFORMANCE_MONITORING_ARTIFACT = new Artifact ("Firebase.PerformanceMonitoring", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "PerformanceMonitoring");
-Artifact FIREBASE_REMOTE_CONFIG_ARTIFACT = new Artifact ("Firebase.RemoteConfig", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "RemoteConfig");
-Artifact FIREBASE_STORAGE_ARTIFACT = new Artifact ("Firebase.Storage", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "Storage");
-Artifact FIREBASE_APP_DISTRIBUTION_ARTIFACT = new Artifact ("Firebase.AppDistribution", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "AppDistribution");
-Artifact FIREBASE_APP_CHECK_ARTIFACT = new Artifact ("Firebase.AppCheck", "12.19.3", "15.0", ComponentGroup.Firebase, csprojName: "AppCheck");
+Artifact FIREBASE_AB_TESTING_ARTIFACT = new Artifact ("Firebase.ABTesting", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "ABTesting");
+Artifact FIREBASE_ANALYTICS_ARTIFACT = new Artifact ("Firebase.Analytics", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "Analytics");
+Artifact FIREBASE_AUTH_ARTIFACT = new Artifact ("Firebase.Auth", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "Auth");
+Artifact FIREBASE_CLOUD_FIRESTORE_ARTIFACT = new Artifact ("Firebase.CloudFirestore", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "CloudFirestore");
+Artifact FIREBASE_CLOUD_FUNCTIONS_ARTIFACT = new Artifact ("Firebase.CloudFunctions", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "CloudFunctions");
+Artifact FIREBASE_CLOUD_MESSAGING_ARTIFACT = new Artifact ("Firebase.CloudMessaging", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "CloudMessaging");
+Artifact FIREBASE_CORE_ARTIFACT = new Artifact ("Firebase.Core", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "Core");
+Artifact FIREBASE_CRASHLYTICS_ARTIFACT = new Artifact ("Firebase.Crashlytics", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "Crashlytics");
+Artifact FIREBASE_DATABASE_ARTIFACT = new Artifact ("Firebase.Database", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "Database");
+Artifact FIREBASE_IN_APP_MESSAGING_ARTIFACT = new Artifact ("Firebase.InAppMessaging", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "InAppMessaging");
+Artifact FIREBASE_INSTALLATIONS_ARTIFACT = new Artifact ("Firebase.Installations", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "Installations");
+Artifact FIREBASE_PERFORMANCE_MONITORING_ARTIFACT = new Artifact ("Firebase.PerformanceMonitoring", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "PerformanceMonitoring");
+Artifact FIREBASE_REMOTE_CONFIG_ARTIFACT = new Artifact ("Firebase.RemoteConfig", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "RemoteConfig");
+Artifact FIREBASE_STORAGE_ARTIFACT = new Artifact ("Firebase.Storage", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "Storage");
+Artifact FIREBASE_APP_DISTRIBUTION_ARTIFACT = new Artifact ("Firebase.AppDistribution", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "AppDistribution");
+Artifact FIREBASE_APP_CHECK_ARTIFACT = new Artifact ("Firebase.AppCheck", "12.19.3.1", "15.0", ComponentGroup.Firebase, csprojName: "AppCheck");
// Google artifacts available to be built. These artifacts generate NuGets.
Artifact GOOGLE_ANALYTICS_ARTIFACT = new Artifact ("Google.Analytics", "3.20.0.2", "15.0", ComponentGroup.Google, csprojName: "Analytics");
@@ -22,16 +22,16 @@ Artifact GOOGLE_CAST_ARTIFACT = new Artifact ("Google.Cast"
Artifact GOOGLE_MAPS_ARTIFACT = new Artifact ("Google.Maps", "9.2.0.10", "15.0", ComponentGroup.Google, csprojName: "Maps");
Artifact GOOGLE_UMP_ARTIFACT = new Artifact ("Google.UserMessagingPlatform", "1.1.0.1", "15.0", ComponentGroup.Google, csprojName: "UserMessagingPlatform");
Artifact GOOGLE_PLACES_ARTIFACT = new Artifact ("Google.Places", "7.4.0.4", "15.0", ComponentGroup.Google, csprojName: "Places");
-Artifact GOOGLE_APP_CHECK_CORE_ARTIFACT = new Artifact ("Google.AppCheckCore", "11.3.2", "15.0", ComponentGroup.Google, csprojName: "AppCheckCore");
-Artifact GOOGLE_SIGN_IN_ARTIFACT = new Artifact ("Google.SignIn", "10.1.0.0", "15.0", ComponentGroup.Google, csprojName: "SignIn");
+Artifact GOOGLE_APP_CHECK_CORE_ARTIFACT = new Artifact ("Google.AppCheckCore", "11.3.2.1", "15.0", ComponentGroup.Google, csprojName: "AppCheckCore");
+Artifact GOOGLE_SIGN_IN_ARTIFACT = new Artifact ("Google.SignIn", "10.1.0.1", "15.0", ComponentGroup.Google, csprojName: "SignIn");
Artifact GOOGLE_TAG_MANAGER_ARTIFACT = new Artifact ("Google.TagManager", "9.2.0.0", "15.0", ComponentGroup.Google, csprojName: "TagManager");
-Artifact GOOGLE_GOOGLE_APP_MEASUREMENT_ARTIFACT = new Artifact ("Google.AppMeasurement", "12.19.3", "15.0", ComponentGroup.Google, csprojName: "GoogleAppMeasurement");
-Artifact GOOGLE_PROMISES_OBJC_ARTIFACT = new Artifact ("Google.PromisesObjC", "2.4.0.8", "15.0", ComponentGroup.Google, csprojName: "PromisesObjC");
-Artifact GOOGLE_GTM_SESSION_FETCHER_ARTIFACT = new Artifact ("Google.GTMSessionFetcher", "3.5.0.8", "15.0", ComponentGroup.Google, csprojName: "GTMSessionFetcher");
-Artifact GOOGLE_NANOPB_ARTIFACT = new Artifact ("Google.Nanopb", "3.30910.0.6", "15.0", ComponentGroup.Google, csprojName: "Nanopb");
-Artifact GOOGLE_GOOGLE_UTILITIES_ARTIFACT = new Artifact ("Google.GoogleUtilities", "8.1.5", "15.0", ComponentGroup.Google, csprojName: "GoogleUtilities");
-Artifact GOOGLE_GOOGLE_DATA_TRANSPORT_ARTIFACT = new Artifact ("Google.GoogleDataTransport", "10.1.0.6", "15.0", ComponentGroup.Google, csprojName: "GoogleDataTransport");
+Artifact GOOGLE_GOOGLE_APP_MEASUREMENT_ARTIFACT = new Artifact ("Google.AppMeasurement", "12.19.3.1", "15.0", ComponentGroup.Google, csprojName: "GoogleAppMeasurement");
+Artifact GOOGLE_PROMISES_OBJC_ARTIFACT = new Artifact ("Google.PromisesObjC", "2.4.0.9", "15.0", ComponentGroup.Google, csprojName: "PromisesObjC");
+Artifact GOOGLE_GTM_SESSION_FETCHER_ARTIFACT = new Artifact ("Google.GTMSessionFetcher", "3.5.0.9", "15.0", ComponentGroup.Google, csprojName: "GTMSessionFetcher");
+Artifact GOOGLE_NANOPB_ARTIFACT = new Artifact ("Google.Nanopb", "3.30910.0.7", "15.0", ComponentGroup.Google, csprojName: "Nanopb");
+Artifact GOOGLE_GOOGLE_UTILITIES_ARTIFACT = new Artifact ("Google.GoogleUtilities", "8.1.5.1", "15.0", ComponentGroup.Google, csprojName: "GoogleUtilities");
+Artifact GOOGLE_GOOGLE_DATA_TRANSPORT_ARTIFACT = new Artifact ("Google.GoogleDataTransport", "10.1.0.7", "15.0", ComponentGroup.Google, csprojName: "GoogleDataTransport");
// MLKit artifacts available to be built. These artifacts generate NuGets.
Artifact MLKIT_CORE_ARTIFACT = new Artifact ("MLKit.Core", "12.0.0.3", "16.0", ComponentGroup.MLKit, csprojName: "Core");
Artifact MLKIT_TEXT_RECOGNITION = new Artifact ("MLKit.TextRecognition", "1.0.0.3", "15.0", ComponentGroup.MLKit, csprojName: "TextRecognition");
diff --git a/custom_externals_download.cake b/custom_externals_download.cake
index d0be868f..054fa8c4 100644
--- a/custom_externals_download.cake
+++ b/custom_externals_download.cake
@@ -53,16 +53,22 @@ FilePath GetArchivePath (ExternalDownloadSource source, DirectoryPath externalsP
DirectoryPath GetExtractionRoot (ExternalDownloadSource source, DirectoryPath externalsPath) =>
externalsPath.Combine (source.ExtractionRootName);
-void DownloadAndExtract (ExternalDownloadSource source, Func artifactsAlreadyPresent, Action copyArtifacts)
+void DownloadAndExtract (ExternalDownloadSource source, string[] outputNames, Action copyArtifacts)
{
- var externalsPath = new DirectoryPath ("./externals");
-
- if (artifactsAlreadyPresent ()) {
+ var externalsPath = MakeAbsolute (new DirectoryPath ("./externals"));
+
+ var outputs = outputNames.Select (name => MakeAbsolute (externalsPath.Combine (name)).FullPath).ToArray ();
+ var stamp = MakeAbsolute (externalsPath.CombineWithFilePath (source.Id + ".download-cache")).FullPath;
+ var identity = string.Join ("\n", new [] { "native-download-v1", source.Id, source.Version,
+ source.ArchiveKey, source.Url, source.ExpectedSha256, source.ExtractionRootName,
+ string.Join ("\n", outputNames) });
+ if (NativeCacheRecord.Matches (stamp, identity, outputs)) {
Information ($"{source.Id} artifacts already available in externals. Skipping download.");
return;
}
EnsureDirectoryExists (externalsPath);
+ System.IO.File.Delete (stamp);
var archivePath = GetArchivePath (source, externalsPath);
var extractionRoot = GetExtractionRoot (source, externalsPath);
@@ -82,10 +88,17 @@ void DownloadAndExtract (ExternalDownloadSource source, Func artifactsAlre
if (exitCode != 0)
throw new Exception ($"tar failed with exit code {exitCode} while extracting {archivePath.GetFilename ()}.");
+ // Compare the entire verified extraction with the copy, including all framework
+ // slices and resource bundles. A partial copy must never publish a completion record.
+ var expected = NativeCacheRecord.Inventory (outputNames.Select (name =>
+ MakeAbsolute (extractionRoot.Combine ("Frameworks").Combine (name)).FullPath).ToArray ());
copyArtifacts (extractionRoot, externalsPath, deleteSettings);
+ if (NativeCacheRecord.Inventory (outputs) != expected)
+ throw new Exception ($"Incomplete native copy for {source.Id}.");
DeleteDirectory (extractionRoot, deleteSettings);
DeleteFile (archivePath);
+ NativeCacheRecord.Complete (stamp, identity, expected);
}
int ExtractArchive (ExternalDownloadSource source, DirectoryPath externalsPath, FilePath archivePath)
@@ -122,7 +135,7 @@ void FirebaseAnalyticsDownload ()
DownloadAndExtract (
source,
- () => DirectoryExists (new DirectoryPath ("./externals/FirebaseAnalytics.xcframework")),
+ new [] { "FirebaseAnalytics.xcframework" },
(extractionRoot, externalsPath, deleteSettings) => {
var frameworkSource = extractionRoot.Combine ("Frameworks").Combine ("FirebaseAnalytics.xcframework");
var frameworkDestination = externalsPath.Combine ("FirebaseAnalytics.xcframework");
@@ -143,8 +156,7 @@ void GoogleAppMeasurementDownload ()
DownloadAndExtract (
source,
- () => DirectoryExists (new DirectoryPath ("./externals/GoogleAppMeasurementIdentitySupport.xcframework")) &&
- DirectoryExists (new DirectoryPath ("./externals/GoogleAppMeasurement.xcframework")),
+ new [] { "GoogleAppMeasurementIdentitySupport.xcframework", "GoogleAppMeasurement.xcframework" },
(extractionRoot, externalsPath, deleteSettings) => {
var frameworkSource = extractionRoot.Combine ("Frameworks");
var identitySupportSource = frameworkSource.Combine ("GoogleAppMeasurementIdentitySupport.xcframework");
diff --git a/docs/CI.md b/docs/CI.md
index 71361624..6774b4b7 100644
--- a/docs/CI.md
+++ b/docs/CI.md
@@ -43,3 +43,6 @@ Run the planner and fingerprint regressions with:
python3 -m unittest discover -s tests/ci -v
dotnet test scripts/FirebaseBindingAudit.Tests/FirebaseBindingAudit.Tests.csproj
```
+
+Native cache behavior and fixture regression coverage are documented in
+[NATIVE_CACHES.md](NATIVE_CACHES.md).
diff --git a/docs/NATIVE_CACHES.md b/docs/NATIVE_CACHES.md
new file mode 100644
index 00000000..0c49cbfd
--- /dev/null
+++ b/docs/NATIVE_CACHES.md
@@ -0,0 +1,58 @@
+# Native build caches
+
+The CocoaPods/Xcode archive helper and the direct native archive downloader verify
+cache inputs and outputs before reuse. Existing directories without completion
+records are rebuilt or downloaded once; deleting a record is also a way to force
+that operation without clearing unrelated native outputs.
+
+## Xcode archives
+
+`BuildXcodeXcframework` records completion beside each platform archive as
+`.xcarchive.cache`. Its identity includes the requested pod definitions,
+Podfile, both CocoaPods lockfiles, generated Xcode project and target-support files,
+Xcode version, developer-directory override, target, platform, architectures and
+build settings. Unlocked builds and explicit local-path pod sources always rebuild.
+
+Before rebuilding, the helper removes the old completion record and archive. A
+successful build must contain the requested framework's Info.plist and binary.
+The record then binds the input identity to an inventory of all archive files,
+including resources. Each scheme builds and records its own archive; it does not
+populate another scheme's cache by copying dependency frameworks.
+
+## Direct downloads
+
+`DownloadAndExtract` records completion in `externals/.download-cache`. Its
+identity includes the SDK version, archive key, full URL, expected SHA-256,
+extraction root and required outputs. GoogleAppMeasurement requires both its
+measurement and IdentitySupport frameworks.
+
+A miss removes the old completion record before downloading. The helper verifies
+the archive hash, extracts it, and compares every copied file against the verified
+extraction before writing a record. This comparison includes framework slices and
+embedded resource bundles. A failed extraction, interrupted copy, or incomplete
+copy cannot leave a valid record.
+
+Both cache hits rehash the recorded outputs. A missing directory, deleted binary
+or resource, empty output, or changed file invalidates the cache. This adds local
+file reads to a hit in exchange for detecting incomplete and modified outputs.
+Completion records are written through a temporary file and renamed only after
+success.
+
+## Regression coverage and release policy
+
+`NativeCacheTests` compiles the production Cake cache helpers and executes them
+against temporary fixtures. Only Cake filesystem/process aliases and external
+build/download operations are simulated. The tests require no Apple SDKs, vendor
+network access or application credentials, and run with the managed audit suite:
+
+```sh
+dotnet test scripts/FirebaseBindingAudit.Tests/FirebaseBindingAudit.Tests.csproj
+```
+
+These shared helper changes affect the package fingerprints of both the Firebase
+and Sign-In closures. The cache correction prepares Firebase and
+GoogleAppMeasurement 12.19.3.1, Sign-In 10.1.0.1, and revisions of all seven Firebase
+support packages (four of which are shared with Sign-In). Native SDK pins remain
+unchanged. The checked-in initial release baselines and version guards are unchanged.
+Publish the Firebase revision before the Sign-In revision so the latter can reuse
+its four shared support packages, following the normal approved release workflow.
diff --git a/eng/firebase-release.json b/eng/firebase-release.json
index c937b915..2985ae16 100644
--- a/eng/firebase-release.json
+++ b/eng/firebase-release.json
@@ -1,5 +1,5 @@
{
- "releaseVersion": "12.19.3",
+ "releaseVersion": "12.19.3.1",
"minimumFirebaseVersion": "12.12.0",
"nativeBuild": {
"runner": "xcode-27.1",
diff --git a/eng/signin-release.json b/eng/signin-release.json
index cf324a20..368dae8f 100644
--- a/eng/signin-release.json
+++ b/eng/signin-release.json
@@ -1,5 +1,5 @@
{
- "releaseVersion": "10.1.0",
+ "releaseVersion": "10.1.0.1",
"nativeBuild": {
"runner": "xcode-27.1",
"xcode": "27.1",
diff --git a/scripts/FirebaseBindingAudit.Tests/Fixtures/NativeCacheHarness.cs.txt b/scripts/FirebaseBindingAudit.Tests/Fixtures/NativeCacheHarness.cs.txt
new file mode 100644
index 00000000..b2c718c2
--- /dev/null
+++ b/scripts/FirebaseBindingAudit.Tests/Fixtures/NativeCacheHarness.cs.txt
@@ -0,0 +1,108 @@
+// Only Cake aliases and external download/Xcode operations are simulated. The cache
+// decisions, identity generation, copy callbacks and completion writes come from production.
+class FilePath {
+ public string FullPath; public FilePath(string path) { FullPath = path; }
+ public string GetFilename() => Path.GetFileName(FullPath);
+ public static implicit operator FilePath(string path) => new FilePath(path);
+ public override string ToString() => FullPath;
+}
+class DirectoryPath {
+ public string FullPath; public DirectoryPath(string path) { FullPath = path; }
+ public DirectoryPath Combine(string path) => new DirectoryPath(Path.Combine(FullPath, path));
+ public FilePath CombineWithFilePath(FilePath path) => new FilePath(Path.Combine(FullPath, path.FullPath));
+ public static implicit operator DirectoryPath(string path) => new DirectoryPath(path);
+ public override string ToString() => FullPath;
+}
+class DeleteDirectorySettings { public bool Recursive; public bool Force; }
+class ProcessArgumentBuilder { public string Text = ""; public void Append(string text) { Text += text; } }
+class ProcessSettings { public ProcessArgumentBuilder Arguments; }
+class PodSpec {
+ public enum PodSource { PodVersion, LocalPath }
+ public string TargetName = "Widget", FrameworkName = "Widget", Version;
+ public PodSource Source;
+ public string[] BuildPodLines() => new[] { "pod 'Widget', '" + Version + "'" };
+}
+public class Harness {
+ public string Version = "1.0", Key = "key", Hash = "", UrlPrefix = "https://example.test";
+ public string Settings = "default", Architectures = "arm64", XcodeId = "Xcode 1", CopyFailure = "", BuildFailure = "";
+ public bool Local, Catalyst, FrameworkLinks;
+ public int Downloads, Copies, Builds, Creates;
+ string Root;
+ Dictionary ExternalDownloads = new Dictionary();
+ public Harness(string root) {
+ Root = root;
+ Write(Path.Combine(root, "build/Podfile.lock"), "lock");
+ Write(Path.Combine(root, "build/Podfile"), "podfile");
+ Write(Path.Combine(root, "build/Pods/Target Support Files/Widget/Widget.xcconfig"), "settings");
+ Write(Path.Combine(root, "build/Pods/Manifest.lock"), "lock");
+ Write(Path.Combine(root, "build/Pods/Pods.xcodeproj/project.pbxproj"), "project");
+ }
+ static void Write(string path, string text) { System.IO.Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllText(path, text); }
+ static void Framework(string root, string name) {
+ Write(Path.Combine(root, "Info.plist"), "plist"); Write(Path.Combine(root, name), "binary");
+ Write(Path.Combine(root, "Resources/Privacy.bundle/Info.plist"), "bundle");
+ }
+ DirectoryPath Directory(string path) => new DirectoryPath(path);
+ FilePath MakeAbsolute(FilePath path) => new FilePath(Path.GetFullPath(path.FullPath, Root));
+ DirectoryPath MakeAbsolute(DirectoryPath path) => new DirectoryPath(Path.GetFullPath(path.FullPath, Root));
+ bool FileExists(FilePath path) => File.Exists(MakeAbsolute(path).FullPath);
+ bool DirectoryExists(DirectoryPath path) => System.IO.Directory.Exists(MakeAbsolute(path).FullPath);
+ void EnsureDirectoryExists(DirectoryPath path) => System.IO.Directory.CreateDirectory(MakeAbsolute(path).FullPath);
+ void DeleteDirectory(DirectoryPath path, DeleteDirectorySettings settings) => System.IO.Directory.Delete(MakeAbsolute(path).FullPath, true);
+ void DeleteFile(FilePath path) => File.Delete(MakeAbsolute(path).FullPath);
+ bool IsRunningOnUnix() => true;
+ void Information(string message) { }
+ void Warning(string message, string value) { }
+ string NativeXcodeIdentity() => XcodeId;
+ void ThrowIfProcessFailed(string tool, int exit) { if (exit != 0) throw new Exception(tool); }
+ int StartProcess(string tool, ProcessSettings settings) { Creates++; return 0; }
+ void XCodeBuild(XCodeBuildSettings settings) {
+ Builds++; var path = Path.Combine(settings.ArchivePath, "Products/Library/Frameworks/Widget.framework");
+ // A retry must start clean rather than accepting remnants of a failed build.
+ if (System.IO.Directory.Exists(settings.ArchivePath)) throw new Exception("Archive was not cleaned");
+ Write(Path.Combine(path, "Info.plist"), "plist");
+ if (BuildFailure == "throw") throw new IOException("Interrupted build");
+ if (BuildFailure == "partial") return;
+ Framework(path, "Widget");
+ if (Catalyst)
+ File.Move(Path.Combine(path, "Info.plist"), Path.Combine(path, "Resources/Info.plist"));
+ if (FrameworkLinks) {
+ System.IO.Directory.CreateSymbolicLink(Path.Combine(path, "ResourceAlias"), "Resources");
+ File.CreateSymbolicLink(Path.Combine(path, "Headers"), "MissingHeaders");
+ }
+ Write(Path.Combine(path, "Widget"), Version);
+ }
+ void DownloadArchive(ExternalDownloadSource source, FilePath path) { Downloads++; Write(MakeAbsolute(path).FullPath, "archive"); }
+ int ExtractArchive(ExternalDownloadSource source, DirectoryPath externals, FilePath archive) {
+ var root = MakeAbsolute(GetExtractionRoot(source, externals)).Combine("Frameworks").FullPath;
+ var names = source.Id == "FirebaseAnalytics" ? new[] { "FirebaseAnalytics" } : new[] { "GoogleAppMeasurementIdentitySupport", "GoogleAppMeasurement" };
+ foreach (var name in names) {
+ var output = Path.Combine(root, name + ".xcframework"); Write(Path.Combine(output, "Info.plist"), "xcframework");
+ var framework = name == "FirebaseAnalytics" ? "Analytics" : "Measurement";
+ Framework(Path.Combine(output, "ios", framework + ".framework"), framework);
+ }
+ return 0;
+ }
+ void CopyDirectory(DirectoryPath source, DirectoryPath dest) {
+ Copies++; var from = MakeAbsolute(source).FullPath; var to = MakeAbsolute(dest).FullPath;
+ System.IO.Directory.CreateDirectory(to);
+ foreach (var file in System.IO.Directory.GetFiles(from, "*", SearchOption.AllDirectories).OrderBy(p => p, StringComparer.Ordinal)) {
+ var target = Path.Combine(to, Path.GetRelativePath(from, file));
+ System.IO.Directory.CreateDirectory(Path.GetDirectoryName(target)); File.Copy(file, target, true);
+ if (CopyFailure == "throw" || (CopyFailure == "second" && Copies % 2 == 0)) throw new IOException("Interrupted copy");
+ if (CopyFailure == "partial") return;
+ }
+ }
+ void SetDownload(string id) {
+ var hash = NativeCacheRecord.FileHash(CreateArchiveFixture());
+ ExternalDownloads[id] = new ExternalDownloadSource(id, Version, Key, UrlPrefix,
+ expectedSha256: Hash == "uppercase" ? Convert.ToHexString(Convert.FromBase64String(hash)) :
+ Hash == "wrong" ? new string('0', 64) : Convert.ToHexString(Convert.FromBase64String(hash)).ToLowerInvariant());
+ }
+ string CreateArchiveFixture() { var path = Path.Combine(Root, "archive-fixture"); Write(path, "archive"); return path; }
+ public void Download() { SetDownload("GoogleAppMeasurement"); GoogleAppMeasurementDownload(); }
+ public void Analytics() { SetDownload("FirebaseAnalytics"); FirebaseAnalyticsDownload(); }
+ public void Build() => BuildXcodeXcframework("Pods/Pods.xcodeproj",
+ new[] { new PodSpec { Version = Version, Source = Local ? PodSpec.PodSource.LocalPath : PodSpec.PodSource.PodVersion } },
+ new[] { PlatformV2.Create(Catalyst ? Sdk.macCatalyst : Sdk.iPhoneOS, new[] { Architectures == "arm64" ? Arch.Arm64 : Arch.x86_64 }) },
+ new DirectoryPath(Path.Combine(Root, "build")), new Dictionary { ["CUSTOM"] = Settings });
diff --git a/scripts/FirebaseBindingAudit.Tests/NativeCacheTests.cs b/scripts/FirebaseBindingAudit.Tests/NativeCacheTests.cs
new file mode 100644
index 00000000..f38bf27c
--- /dev/null
+++ b/scripts/FirebaseBindingAudit.Tests/NativeCacheTests.cs
@@ -0,0 +1,241 @@
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Xunit;
+
+public class NativeCacheTests : IDisposable
+{
+ static readonly Lazy HarnessType = new(Compile);
+ readonly string root = Path.Combine(Path.GetTempPath(), "native-cache-" + Guid.NewGuid().ToString("N"));
+ readonly object harness;
+
+ public NativeCacheTests()
+ {
+ Directory.CreateDirectory(root);
+ harness = Activator.CreateInstance(HarnessType.Value, root)!;
+ }
+ public void Dispose() => Directory.Delete(root, true);
+ void Set(string name, object value) => HarnessType.Value.GetField(name)!.SetValue(harness, value);
+ int Count(string name) => (int)HarnessType.Value.GetField(name)!.GetValue(harness)!;
+ void Run(string method)
+ {
+ try { HarnessType.Value.GetMethod(method)!.Invoke(harness, null); }
+ catch (TargetInvocationException e) when (e.InnerException is not null) { throw e.InnerException; }
+ }
+ string Output(string name) => Path.Combine(root, "externals", name);
+ string Archive => Path.Combine(root, "build/Widget/iphoneos.xcarchive");
+
+ [Fact]
+ public void Download_UnchangedInputReusesVerifiedOutputs()
+ {
+ Run("Download"); Run("Download");
+ Assert.Equal(1, Count("Downloads"));
+ Assert.Equal(2, Count("Copies"));
+ }
+
+ [Theory]
+ [InlineData("Version", "2.0")]
+ [InlineData("Key", "other-key")]
+ [InlineData("Hash", "uppercase")]
+ [InlineData("UrlPrefix", "https://other.example")]
+ public void Download_ChangedSourceInvalidates(string field, string value)
+ {
+ Run("Download");
+ // SHA spelling is part of source identity even when its verified value is identical.
+ Set(field, value); Run("Download");
+ Assert.Equal(2, Count("Downloads"));
+ }
+
+ [Theory]
+ [InlineData("stamp")]
+ [InlineData("framework")]
+ [InlineData("binary")]
+ [InlineData("bundle")]
+ [InlineData("corruption")]
+ [InlineData("empty")]
+ public void Download_MissingOrIncompleteOutputIsNotAHit(string damage)
+ {
+ Run("Download");
+ if (damage == "stamp") File.Delete(Output("GoogleAppMeasurement.download-cache"));
+ if (damage == "framework") Directory.Delete(Output("GoogleAppMeasurementIdentitySupport.xcframework"), true);
+ if (damage == "binary") File.Delete(Output("GoogleAppMeasurement.xcframework/ios/Measurement.framework/Measurement"));
+ if (damage == "bundle") File.Delete(Output("GoogleAppMeasurement.xcframework/ios/Measurement.framework/Resources/Privacy.bundle/Info.plist"));
+ if (damage == "corruption") File.WriteAllText(Output("GoogleAppMeasurementIdentitySupport.xcframework/Info.plist"), "truncated");
+ if (damage == "empty") { Directory.Delete(Output("GoogleAppMeasurement.xcframework"), true); Directory.CreateDirectory(Output("GoogleAppMeasurement.xcframework")); }
+ Run("Download");
+ Assert.Equal(2, Count("Downloads"));
+ Run("Download"); Assert.Equal(2, Count("Downloads"));
+ }
+
+ [Theory]
+ [InlineData("throw")]
+ [InlineData("partial")]
+ [InlineData("second")]
+ public void Download_InterruptedOrSilentPartialCopyCannotBecomeAHit(string failure)
+ {
+ Run("Download"); Set("Version", "2.0"); Set("CopyFailure", failure);
+ Assert.ThrowsAny(() => Run("Download"));
+ Assert.False(File.Exists(Output("GoogleAppMeasurement.download-cache")));
+ Set("CopyFailure", ""); Run("Download"); Run("Download");
+ Assert.Equal(3, Count("Downloads"));
+ }
+
+ [Fact]
+ public void Download_HashFailureLeavesNoCompletionRecord()
+ {
+ Set("Hash", "wrong"); Assert.ThrowsAny(() => Run("Download"));
+ Assert.Equal(0, Count("Copies"));
+ Assert.False(File.Exists(Output("GoogleAppMeasurement.download-cache")));
+ Set("Hash", ""); Run("Download"); Run("Download");
+ Assert.Equal(2, Count("Downloads"));
+ }
+
+ [Fact]
+ public void Download_ChangedHashCannotAcceptPreviouslyVerifiedOutputs()
+ {
+ Run("Download"); Set("Hash", "wrong");
+ Assert.ThrowsAny(() => Run("Download"));
+ Assert.Equal(2, Count("Downloads"));
+ Assert.False(File.Exists(Output("GoogleAppMeasurement.download-cache")));
+ Set("Hash", ""); Run("Download"); Run("Download");
+ Assert.Equal(3, Count("Downloads"));
+ }
+
+ [Fact]
+ public void Analytics_UsesSameCompletionContract()
+ {
+ Run("Analytics"); Run("Analytics"); Assert.Equal(1, Count("Downloads"));
+ File.Delete(Output("FirebaseAnalytics.xcframework/ios/Analytics.framework/Analytics"));
+ Run("Analytics"); Assert.Equal(2, Count("Downloads"));
+ }
+
+ [Fact]
+ public void Archive_UnchangedInputReusesButStillCreatesXcframework()
+ {
+ Run("Build"); Run("Build");
+ Assert.Equal(1, Count("Builds")); Assert.Equal(2, Count("Creates"));
+ }
+
+ [Fact]
+ public void Archive_CatalystResourcesPlistIsRequiredAndCanBeCached()
+ {
+ Set("Catalyst", true);
+ Run("Build");
+ Run("Build");
+ Assert.Equal(1, Count("Builds"));
+
+ File.Delete(Path.Combine(root, "build/Widget/maccatalyst.xcarchive/Products/Library/Frameworks/Widget.framework/Resources/Info.plist"));
+ Run("Build");
+ Assert.Equal(2, Count("Builds"));
+ }
+
+ [Fact]
+ public void Archive_RecordsFrameworkLinksWithoutFollowingThem()
+ {
+ if (OperatingSystem.IsWindows()) return;
+ Set("FrameworkLinks", true);
+ Run("Build");
+ Run("Build");
+ Assert.Equal(1, Count("Builds"));
+
+ var link = Path.Combine(Archive, "Products/Library/Frameworks/Widget.framework/Headers");
+ File.Delete(link);
+ File.CreateSymbolicLink(link, "ChangedHeaders");
+ Run("Build");
+ Assert.Equal(2, Count("Builds"));
+ }
+
+ [Theory]
+ [InlineData("Version")]
+ [InlineData("Settings")]
+ [InlineData("Architectures")]
+ [InlineData("XcodeId")]
+ [InlineData("Lock")]
+ [InlineData("Manifest")]
+ [InlineData("Project")]
+ [InlineData("Podfile")]
+ [InlineData("Support")]
+ public void Archive_ChangedBuildInputInvalidates(string change)
+ {
+ Run("Build");
+ if (change == "Lock") File.AppendAllText(Path.Combine(root, "build/Podfile.lock"), "changed");
+ else if (change == "Manifest") File.AppendAllText(Path.Combine(root, "build/Pods/Manifest.lock"), "changed");
+ else if (change == "Podfile") File.AppendAllText(Path.Combine(root, "build/Podfile"), "changed");
+ else if (change == "Support") File.AppendAllText(Path.Combine(root, "build/Pods/Target Support Files/Widget/Widget.xcconfig"), "changed");
+ else if (change == "Project") File.AppendAllText(Path.Combine(root, "build/Pods/Pods.xcodeproj/project.pbxproj"), "changed");
+ else Set(change, "changed");
+ Run("Build"); Assert.Equal(2, Count("Builds"));
+ Assert.Equal(change == "Version" ? "changed" : "1.0", File.ReadAllText(Path.Combine(Archive, "Products/Library/Frameworks/Widget.framework/Widget")));
+ Run("Build"); Assert.Equal(2, Count("Builds"));
+ }
+
+ [Theory]
+ [InlineData("stamp")]
+ [InlineData("binary")]
+ [InlineData("plist")]
+ [InlineData("bundle")]
+ [InlineData("corruption")]
+ public void Archive_UnstampedOrIncompleteArchiveRebuilds(string damage)
+ {
+ Run("Build");
+ var framework = Path.Combine(Archive, "Products/Library/Frameworks/Widget.framework");
+ if (damage == "stamp") File.Delete(Archive + ".cache");
+ if (damage == "binary") File.Delete(Path.Combine(framework, "Widget"));
+ if (damage == "plist") File.Delete(Path.Combine(framework, "Info.plist"));
+ if (damage == "bundle") File.Delete(Path.Combine(framework, "Resources/Privacy.bundle/Info.plist"));
+ if (damage == "corruption") File.WriteAllText(Path.Combine(framework, "Widget"), "bad");
+ Run("Build"); Assert.Equal(2, Count("Builds"));
+ }
+
+ [Theory]
+ [InlineData("throw")]
+ [InlineData("partial")]
+ public void Archive_FailedRebuildCannotReuseOldStamp(string failure)
+ {
+ Run("Build"); Set("Version", "2.0"); Set("BuildFailure", failure);
+ Assert.ThrowsAny(() => Run("Build"));
+ Assert.False(File.Exists(Archive + ".cache"));
+ Set("BuildFailure", ""); Run("Build"); Run("Build");
+ Assert.Equal(3, Count("Builds"));
+ }
+
+ [Theory]
+ [InlineData("unlocked")]
+ [InlineData("local")]
+ public void Archive_InputsWithoutStableSourceIdentityAlwaysRebuild(string mode)
+ {
+ if (mode == "unlocked") File.Delete(Path.Combine(root, "build/Podfile.lock"));
+ else Set("Local", true);
+ Run("Build"); Run("Build"); Assert.Equal(2, Count("Builds"));
+ Assert.False(File.Exists(Archive + ".cache"));
+ }
+
+ static Type Compile()
+ {
+ var dir = new DirectoryInfo(AppContext.BaseDirectory);
+ while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "common.cake"))) dir = dir.Parent;
+ Assert.NotNull(dir);
+ CompilationUnitSyntax Parse(string path) => CSharpSyntaxTree.ParseText(File.ReadAllText(Path.Combine(dir.FullName, path)),
+ new CSharpParseOptions(kind: SourceCodeKind.Script)).GetCompilationUnitRoot();
+ var common = Parse("common.cake"); var downloads = Parse("custom_externals_download.cake"); var poco = Parse("poco.cake");
+ string Class(CompilationUnitSyntax source, string name) => source.Members.OfType().Single(c => c.Identifier.ValueText == name).ToFullString();
+ string Method(CompilationUnitSyntax source, string name) => source.Members.OfType().Single(m => m.Identifier.ValueText == name).ToFullString();
+ var code = """
+ using System;
+ using System.IO;
+ using System.Linq;
+ using System.Collections.Generic;
+ """ + string.Join("\n", poco.Members.OfType().Where(c => new[] { "Arch", "Sdk", "PlatformV2" }.Contains(c.Identifier.ValueText)).Select(c => c.ToFullString()))
+ + Class(common, "NativeCacheRecord") + Class(common, "XCodeBuildSettings") + Class(downloads, "ExternalDownloadSource")
+ + File.ReadAllText(Path.Combine(dir.FullName, "scripts/FirebaseBindingAudit.Tests/Fixtures/NativeCacheHarness.cs.txt"))
+ + Method(common, "BuildXcodeXcframework")
+ + string.Join("\n", new[] { "DownloadAndExtract", "GetArchivePath", "GetExtractionRoot", "VerifyArchiveHash", "FirebaseAnalyticsDownload", "GoogleAppMeasurementDownload" }.Select(n => Method(downloads, n))) + "\n}";
+ var refs = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!).Split(Path.PathSeparator).Select(p => MetadataReference.CreateFromFile(p));
+ var compilation = CSharpCompilation.Create("NativeCache" + Guid.NewGuid().ToString("N"), [CSharpSyntaxTree.ParseText(code)], refs,
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+ using var output = new MemoryStream(); var result = compilation.Emit(output);
+ Assert.True(result.Success, string.Join("\n", result.Diagnostics));
+ return Assembly.Load(output.ToArray()).GetType("Harness")!;
+ }
+}
diff --git a/scripts/FirebaseBindingAudit/BindingSurfaceCoverage.cs b/scripts/FirebaseBindingAudit/BindingSurfaceCoverage.cs
index 6c802f34..3fc1a00a 100644
--- a/scripts/FirebaseBindingAudit/BindingSurfaceCoverage.cs
+++ b/scripts/FirebaseBindingAudit/BindingSurfaceCoverage.cs
@@ -10,7 +10,7 @@ namespace FirebaseBindingAudit;
internal static class FirebasePackageVersions
{
- public const string DefaultFirebasePackageVersion = "12.19.3";
+ public const string DefaultFirebasePackageVersion = "12.19.3.1";
}
internal sealed class BindingSurfaceCoverageManifest
diff --git a/source/Firebase/ABTesting/ABTesting.csproj b/source/Firebase/ABTesting/ABTesting.csproj
index d7fbd1b1..3adf568a 100644
--- a/source/Firebase/ABTesting/ABTesting.csproj
+++ b/source/Firebase/ABTesting/ABTesting.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.ABTesting
Firebase.ABTesting
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/Analytics/Analytics.csproj b/source/Firebase/Analytics/Analytics.csproj
index 609b2334..fa7b0ee0 100644
--- a/source/Firebase/Analytics/Analytics.csproj
+++ b/source/Firebase/Analytics/Analytics.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.Analytics
Firebase.Analytics
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -28,7 +28,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/AppCheck/AppCheck.csproj b/source/Firebase/AppCheck/AppCheck.csproj
index d9a6d882..b437e245 100644
--- a/source/Firebase/AppCheck/AppCheck.csproj
+++ b/source/Firebase/AppCheck/AppCheck.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.AppCheck
Firebase.AppCheck
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/AppDistribution/AppDistribution.csproj b/source/Firebase/AppDistribution/AppDistribution.csproj
index 4a9c2499..a8da7d41 100644
--- a/source/Firebase/AppDistribution/AppDistribution.csproj
+++ b/source/Firebase/AppDistribution/AppDistribution.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.AppDistribution
Firebase.AppDistribution
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/Auth/Auth.csproj b/source/Firebase/Auth/Auth.csproj
index 6a2d3151..87a5e343 100644
--- a/source/Firebase/Auth/Auth.csproj
+++ b/source/Firebase/Auth/Auth.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.Auth
Firebase.Auth
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/CloudFirestore/CloudFirestore.csproj b/source/Firebase/CloudFirestore/CloudFirestore.csproj
index f8aac3d4..cb6a7c14 100644
--- a/source/Firebase/CloudFirestore/CloudFirestore.csproj
+++ b/source/Firebase/CloudFirestore/CloudFirestore.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.CloudFirestore
Firebase.CloudFirestore
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/CloudFunctions/CloudFunctions.csproj b/source/Firebase/CloudFunctions/CloudFunctions.csproj
index d8bb2e2e..e765fec6 100644
--- a/source/Firebase/CloudFunctions/CloudFunctions.csproj
+++ b/source/Firebase/CloudFunctions/CloudFunctions.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.CloudFunctions
Firebase.CloudFunctions
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/CloudMessaging/CloudMessaging.csproj b/source/Firebase/CloudMessaging/CloudMessaging.csproj
index fbd46c75..05793568 100644
--- a/source/Firebase/CloudMessaging/CloudMessaging.csproj
+++ b/source/Firebase/CloudMessaging/CloudMessaging.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.CloudMessaging
Firebase.CloudMessaging
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/Core/Core.csproj b/source/Firebase/Core/Core.csproj
index 9006b5c4..5d46ca98 100644
--- a/source/Firebase/Core/Core.csproj
+++ b/source/Firebase/Core/Core.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.Core
Firebase.Core
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/Crashlytics/Crashlytics.csproj b/source/Firebase/Crashlytics/Crashlytics.csproj
index eb624bea..1f7dfae2 100644
--- a/source/Firebase/Crashlytics/Crashlytics.csproj
+++ b/source/Firebase/Crashlytics/Crashlytics.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.Crashlytics
Firebase.Crashlytics
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/Crashlytics/Crashlytics.targets b/source/Firebase/Crashlytics/Crashlytics.targets
index 6621db5a..5d4bf025 100644
--- a/source/Firebase/Crashlytics/Crashlytics.targets
+++ b/source/Firebase/Crashlytics/Crashlytics.targets
@@ -1,7 +1,7 @@
- <_FirebaseCrashlyticsAssemblyName>Firebase.Crashlytics, Version=12.19.3, Culture=neutral, PublicKeyToken=null
- <_FirebaseCrashlyticsItemsFolder>FCrshlytcs-12.19.3
+ <_FirebaseCrashlyticsAssemblyName>Firebase.Crashlytics, Version=12.19.3.1, Culture=neutral, PublicKeyToken=null
+ <_FirebaseCrashlyticsItemsFolder>FCrshlytcs-12.19.3.1
<_FirebaseCrashlyticsSDKBaseFolder>$(XamarinBuildDownloadDir)$(_FirebaseCrashlyticsItemsFolder)\
<_FirebaseScriptName>upload-symbols.sh
diff --git a/source/Firebase/Database/Database.csproj b/source/Firebase/Database/Database.csproj
index 38f0d37b..4ad70148 100644
--- a/source/Firebase/Database/Database.csproj
+++ b/source/Firebase/Database/Database.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.Database
Firebase.Database
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/InAppMessaging/InAppMessaging.csproj b/source/Firebase/InAppMessaging/InAppMessaging.csproj
index 94fcee16..9d997e5b 100644
--- a/source/Firebase/InAppMessaging/InAppMessaging.csproj
+++ b/source/Firebase/InAppMessaging/InAppMessaging.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.InAppMessaging
Firebase.InAppMessaging
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/Installations/Installations.csproj b/source/Firebase/Installations/Installations.csproj
index ffab754e..a163d610 100644
--- a/source/Firebase/Installations/Installations.csproj
+++ b/source/Firebase/Installations/Installations.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.Installations
Firebase.Installations
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/PerformanceMonitoring/PerformanceMonitoring.csproj b/source/Firebase/PerformanceMonitoring/PerformanceMonitoring.csproj
index 65bece1e..f60b57fd 100644
--- a/source/Firebase/PerformanceMonitoring/PerformanceMonitoring.csproj
+++ b/source/Firebase/PerformanceMonitoring/PerformanceMonitoring.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.PerformanceMonitoring
Firebase.PerformanceMonitoring
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/RemoteConfig/RemoteConfig.csproj b/source/Firebase/RemoteConfig/RemoteConfig.csproj
index 40064338..0379ee90 100644
--- a/source/Firebase/RemoteConfig/RemoteConfig.csproj
+++ b/source/Firebase/RemoteConfig/RemoteConfig.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.RemoteConfig
Firebase.RemoteConfig
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Firebase/Storage/Storage.csproj b/source/Firebase/Storage/Storage.csproj
index ffc6291d..90296b44 100644
--- a/source/Firebase/Storage/Storage.csproj
+++ b/source/Firebase/Storage/Storage.csproj
@@ -8,8 +8,8 @@
15.0
Firebase.Storage
Firebase.Storage
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -27,7 +27,7 @@
License.md
NUGET_README.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Google/AppCheckCore/AppCheckCore.csproj b/source/Google/AppCheckCore/AppCheckCore.csproj
index 46fb1fcc..7496f2dc 100644
--- a/source/Google/AppCheckCore/AppCheckCore.csproj
+++ b/source/Google/AppCheckCore/AppCheckCore.csproj
@@ -8,8 +8,8 @@
15.0
Google.AppCheckCore
Google.AppCheckCore
- 11.3.2
- 11.3.2
+ 11.3.2.1
+ 11.3.2.1
Resources
true
true
@@ -26,7 +26,7 @@
https://github.com/AdamEssenmacher/GoogleApisForiOSComponents
License.md
true
- 11.3.2
+ 11.3.2.1
diff --git a/source/Google/GTMSessionFetcher/GTMSessionFetcher.csproj b/source/Google/GTMSessionFetcher/GTMSessionFetcher.csproj
index 48fd2825..d3403557 100644
--- a/source/Google/GTMSessionFetcher/GTMSessionFetcher.csproj
+++ b/source/Google/GTMSessionFetcher/GTMSessionFetcher.csproj
@@ -8,8 +8,8 @@
15.0
Google.GTMSessionFetcher
Google.GTMSessionFetcher
- 3.5.0.8
- 3.5.0.8
+ 3.5.0.9
+ 3.5.0.9
Resources
true
true
@@ -26,7 +26,7 @@
License.md
NUGET_README.md
true
- 3.5.0.8
+ 3.5.0.9
diff --git a/source/Google/GoogleAppMeasurement/GoogleAppMeasurement.csproj b/source/Google/GoogleAppMeasurement/GoogleAppMeasurement.csproj
index 9318a46d..46819e0a 100644
--- a/source/Google/GoogleAppMeasurement/GoogleAppMeasurement.csproj
+++ b/source/Google/GoogleAppMeasurement/GoogleAppMeasurement.csproj
@@ -8,8 +8,8 @@
15.0
Google.GoogleAppMeasurement
Google.GoogleAppMeasurement
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
Resources
true
true
@@ -25,7 +25,7 @@
https://github.com/AdamEssenmacher/GoogleApisForiOSComponents
License.md
true
- 12.19.3
+ 12.19.3.1
diff --git a/source/Google/GoogleDataTransport/GoogleDataTransport.csproj b/source/Google/GoogleDataTransport/GoogleDataTransport.csproj
index 71ff8481..a865a43e 100644
--- a/source/Google/GoogleDataTransport/GoogleDataTransport.csproj
+++ b/source/Google/GoogleDataTransport/GoogleDataTransport.csproj
@@ -8,8 +8,8 @@
15.0
Google.GoogleDataTransport
Google.GoogleDataTransport
- 10.1.0.6
- 10.1.0.6
+ 10.1.0.7
+ 10.1.0.7
Resources
true
true
@@ -25,7 +25,7 @@
https://github.com/AdamEssenmacher/GoogleApisForiOSComponents
License.md
true
- 10.1.0.6
+ 10.1.0.7
diff --git a/source/Google/GoogleUtilities/GoogleUtilities.csproj b/source/Google/GoogleUtilities/GoogleUtilities.csproj
index fd3380ae..e376bbb0 100644
--- a/source/Google/GoogleUtilities/GoogleUtilities.csproj
+++ b/source/Google/GoogleUtilities/GoogleUtilities.csproj
@@ -8,8 +8,8 @@
15.0
Google.GoogleUtilities
Google.GoogleUtilities
- 8.1.5
- 8.1.5
+ 8.1.5.1
+ 8.1.5.1
Resources
true
true
@@ -26,7 +26,7 @@
License.md
NUGET_README.md
true
- 8.1.5
+ 8.1.5.1
diff --git a/source/Google/Nanopb/Nanopb.csproj b/source/Google/Nanopb/Nanopb.csproj
index fd98b92d..44d45935 100644
--- a/source/Google/Nanopb/Nanopb.csproj
+++ b/source/Google/Nanopb/Nanopb.csproj
@@ -8,8 +8,8 @@
15.0
Google.Nanopb
Google.Nanopb
- 3.30910.0.6
- 3.30910.0.6
+ 3.30910.0.7
+ 3.30910.0.7
Resources
true
true
@@ -25,7 +25,7 @@
https://github.com/AdamEssenmacher/GoogleApisForiOSComponents
License.md
true
- 3.30910.0.6
+ 3.30910.0.7
diff --git a/source/Google/PromisesObjC/PromisesObjC.csproj b/source/Google/PromisesObjC/PromisesObjC.csproj
index 84734584..7b3fd893 100644
--- a/source/Google/PromisesObjC/PromisesObjC.csproj
+++ b/source/Google/PromisesObjC/PromisesObjC.csproj
@@ -8,8 +8,8 @@
15.0
Google.PromisesObjC
Google.PromisesObjC
- 2.4.0.8
- 2.4.0.8
+ 2.4.0.9
+ 2.4.0.9
Resources
true
true
@@ -26,7 +26,7 @@
License.md
NUGET_README.md
true
- 2.4.0.8
+ 2.4.0.9
diff --git a/source/Google/SignIn/SignIn.csproj b/source/Google/SignIn/SignIn.csproj
index 3b9f4bd8..132f586b 100644
--- a/source/Google/SignIn/SignIn.csproj
+++ b/source/Google/SignIn/SignIn.csproj
@@ -8,8 +8,8 @@
15.0
Google.SignIn
Google.SignIn
- 10.1.0.0
- 10.1.0.0
+ 10.1.0.1
+ 10.1.0.1
Resources
true
@@ -26,7 +26,7 @@
License.md
NUGET_README.md
true
- 10.1.0.0
+ 10.1.0.1
diff --git a/tests/E2E/Firebase.Foundation/FirebaseFoundationE2E/FirebaseFoundationE2E.csproj b/tests/E2E/Firebase.Foundation/FirebaseFoundationE2E/FirebaseFoundationE2E.csproj
index 6f09251d..fb9e70db 100644
--- a/tests/E2E/Firebase.Foundation/FirebaseFoundationE2E/FirebaseFoundationE2E.csproj
+++ b/tests/E2E/Firebase.Foundation/FirebaseFoundationE2E/FirebaseFoundationE2E.csproj
@@ -21,8 +21,8 @@
- 12.19.3
- 12.19.3
+ 12.19.3.1
+ 12.19.3.1
$(NoWarn);NU1603
diff --git a/tests/E2E/Firebase.Foundation/binding-surface-coverage.json b/tests/E2E/Firebase.Foundation/binding-surface-coverage.json
index 35082c3b..48116a61 100644
--- a/tests/E2E/Firebase.Foundation/binding-surface-coverage.json
+++ b/tests/E2E/Firebase.Foundation/binding-surface-coverage.json
@@ -53,7 +53,7 @@
"requiredExtraPackages": [
{
"id": "AdamE.Firebase.iOS.ABTesting",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -166,7 +166,7 @@
"requiredExtraPackages": [
{
"id": "AdamE.Firebase.iOS.ABTesting",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
diff --git a/tests/E2E/Firebase.Foundation/runtime-drift-cases.json b/tests/E2E/Firebase.Foundation/runtime-drift-cases.json
index 447bbfd6..3a3812e1 100644
--- a/tests/E2E/Firebase.Foundation/runtime-drift-cases.json
+++ b/tests/E2E/Firebase.Foundation/runtime-drift-cases.json
@@ -25,7 +25,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.AppCheck",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -36,11 +36,11 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.RemoteConfig",
- "version": "12.19.3"
+ "version": "12.19.3.1"
},
{
"id": "AdamE.Firebase.iOS.ABTesting",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -51,7 +51,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.Database",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -62,7 +62,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.Database",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -73,7 +73,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.ABTesting",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -84,7 +84,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.ABTesting",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -95,7 +95,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.ABTesting",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -106,7 +106,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.CloudFirestore",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -117,7 +117,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.CloudFirestore",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -128,7 +128,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.CloudFirestore",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -139,7 +139,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.CloudFirestore",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -150,7 +150,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.CloudFirestore",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -161,7 +161,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.CloudFirestore",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -172,7 +172,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.CloudFirestore",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -183,7 +183,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.CloudFirestore",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -194,7 +194,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.CloudFirestore",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -205,7 +205,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.CloudFunctions",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -216,7 +216,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.Crashlytics",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
},
@@ -227,7 +227,7 @@
"packages": [
{
"id": "AdamE.Firebase.iOS.Crashlytics",
- "version": "12.19.3"
+ "version": "12.19.3.1"
}
]
}