Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 26 additions & 26 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ If your project targets multiple platforms, keep these packages inside Apple-onl

```xml
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
<PackageReference Include="AdamE.Firebase.iOS.Core" Version="12.19.3" />
<PackageReference Include="AdamE.Firebase.iOS.Core" Version="12.19.3.1" />
</ItemGroup>
```

Expand All @@ -172,52 +172,52 @@ 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

#### Firebase packages (`AdamE.Firebase.iOS.*`)

| 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.*`)

| Package | Published version | Prepared version |
| --- | --- | --- |
| `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.*`)

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.*`)

Expand Down
148 changes: 127 additions & 21 deletions common.cake
Original file line number Diff line number Diff line change
Expand Up @@ -551,13 +551,97 @@ 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<string> ();
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<string> 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<string, string> buildSettings = null)
{
if (!IsRunningOnUnix ()) {
Warning("{0} is not available on the current platform.", "xcodebuild");
return;
}

if (podSpecs.Length == 0)
return;

Information ("Building the following frameworks:");
Information (string.Join (",\n", podSpecs.Select (ps => ps.FrameworkName).ToArray ()));

Expand All @@ -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}...");

Expand All @@ -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<string, string> (buildSettings);
buildSettingsCopy ["SKIP_INSTALL"] = "NO";
buildSettingsCopy ["BUILD_LIBRARY_FOR_DISTRIBUTION"] = "YES";
Expand All @@ -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...");
Expand Down
Loading
Loading