diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 000000000..650869bae --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "cake.tool": { + "version": "0.38.5", + "commands": [ + "dotnet-cake" + ] + } + } +} + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..ace86e6bb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + inputs: + names: + description: "Cake --names (comma-separated)" + required: false + default: "Google.SignIn" + +permissions: + contents: read + +jobs: + build: + runs-on: macos-14 + env: + CAKE_NAMES: ${{ inputs.names || 'Google.SignIn' }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET (global.json) + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Restore .NET workloads + run: dotnet workload restore Xamarin.Google.sln + + - name: Install CocoaPods + run: | + if ! command -v pod >/dev/null 2>&1; then + sudo gem install cocoapods + fi + pod --version + + - name: Restore tools + run: dotnet tool restore + + - name: Build + pack + run: dotnet tool run dotnet-cake -- --target=nuget --names="${CAKE_NAMES}" + + - name: Upload packages + uses: actions/upload-artifact@v4 + with: + name: nuget + path: output/*.nupkg diff --git a/.github/workflows/publish-github-packages.yml b/.github/workflows/publish-github-packages.yml new file mode 100644 index 000000000..3c6d437ee --- /dev/null +++ b/.github/workflows/publish-github-packages.yml @@ -0,0 +1,53 @@ +name: Publish (GitHub Packages) + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + names: + description: "Cake --names (comma-separated)" + required: false + default: "Google.SignIn" + +permissions: + contents: read + packages: write + +jobs: + publish: + runs-on: macos-14 + env: + CAKE_NAMES: ${{ inputs.names || 'Google.SignIn' }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET (global.json) + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Restore .NET workloads + run: dotnet workload restore Xamarin.Google.sln + + - name: Install CocoaPods + run: | + if ! command -v pod >/dev/null 2>&1; then + sudo gem install cocoapods + fi + pod --version + + - name: Restore tools + run: dotnet tool restore + + - name: Build + pack + run: dotnet tool run dotnet-cake -- --target=nuget --names="${CAKE_NAMES}" + + - name: Push packages + run: | + dotnet nuget push "output/*.nupkg" \ + --source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \ + --api-key "${{ secrets.GITHUB_TOKEN }}" \ + --skip-duplicate diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..980051eac --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,18 @@ +# Agent Instructions (public OSS) + +## Non-negotiables +- Documentation and samples must be generic (not app-specific). +- Do not reference private business context. +- Do not commit secrets; samples must rely on local config templates. + +## Working agreements +- Keep changes reviewable and releaseable (small PR-sized diffs). +- Prefer minimal disruption to existing package IDs and build scripts. + +## Quick commands +- Build and pack a single component (macOS): + - `dotnet tool restore` + - `dotnet tool run dotnet-cake -- --target=nuget --names=Google.SignIn` + +## CI +- GitHub Actions should run build/pack validation without requiring any app secrets (publishing uses the built-in `GITHUB_TOKEN`). diff --git a/Readme.md b/Readme.md index f2883083c..bfe302bbe 100644 --- a/Readme.md +++ b/Readme.md @@ -5,6 +5,11 @@ This was a really unfortunate decision from Microsoft. Having these extremely co Microsoft's current recommendation to iOS.NET developers is that they should write their own bindings using this [slim binding demo project](https://github.com/Redth/DotNet.Platform.SlimBindings) as a guide. However, developers following this approach can find their 'slim bindings' to be incompatible with any other published libraries with dependency conflicts, such as Plugin.Firebase. +## CI and publishing + +- Local build instructions: `docs/BUILDING.md` +- GitHub Packages publishing workflow (NuGet): `docs/PUBLISHING_GITHUB_PACKAGES.md` + # Contributing & Sponsoring As a community-supported continuation, I'm happy to review any PRs to help move this project forward. These libraries almost certainly have some missing or stale bindings. Work needs to happen to keep these bindings current with native SDKs. Whole artifacts still need to be converted from where Microsoft left them. Sample projects need to be updated (or rewritten). Tests would be nice. diff --git a/build.cake b/build.cake index 093fca5a0..7f6163990 100644 --- a/build.cake +++ b/build.cake @@ -36,6 +36,14 @@ FilePath GetCakeToolPath () return new FilePath (p.Modules[0].FileName); } +string GetDefaultiOSSimulatorRuntimeIdentifier () +{ + var arch = System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture; + return arch == System.Runtime.InteropServices.Architecture.Arm64 + ? "iossimulator-arm64" + : "iossimulator-x64"; +} + void BuildCake (string target) { var cakeSettings = new CakeSettings { @@ -184,6 +192,10 @@ Task ("samples") .Does(() => { var msBuildSettings = new DotNetCoreMSBuildSettings (); + msBuildSettings.Properties ["Platform"] = new [] { "iPhoneSimulator" }; + msBuildSettings.Properties ["RuntimeIdentifier"] = new [] { GetDefaultiOSSimulatorRuntimeIdentifier () }; + msBuildSettings.Properties ["EnableCodeSigning"] = new [] { "false" }; + var dotNetCoreBuildSettings = new DotNetCoreBuildSettings { Configuration = "Release", Verbosity = DotNetCoreVerbosity.Diagnostic, diff --git a/components.cake b/components.cake index 0aa5377d8..1e7295b64 100644 --- a/components.cake +++ b/components.cake @@ -23,7 +23,7 @@ Artifact GOOGLE_MAPS_ARTIFACT = new Artifact ("Google.Maps" Artifact GOOGLE_MOBILE_ADS_ARTIFACT = new Artifact ("Google.MobileAds", "8.13.0.3", "15.0", ComponentGroup.Google, csprojName: "MobileAds"); 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.2", "15.0", ComponentGroup.Google, csprojName: "Places"); -Artifact GOOGLE_SIGN_IN_ARTIFACT = new Artifact ("Google.SignIn", "8.0.0.4", "15.0", ComponentGroup.Google, csprojName: "SignIn"); +Artifact GOOGLE_SIGN_IN_ARTIFACT = new Artifact ("Google.SignIn", "9.0.0.0", "15.0", ComponentGroup.Google, csprojName: "SignIn"); Artifact GOOGLE_TAG_MANAGER_ARTIFACT = new Artifact ("Google.TagManager", "7.4.0.2", "15.0", ComponentGroup.Google, csprojName: "TagManager"); Artifact GOOGLE_GOOGLE_APP_MEASUREMENT_ARTIFACT = new Artifact ("Google.AppMeasurement", "12.5.0.4", "15.0", ComponentGroup.Google, csprojName: "GoogleAppMeasurement"); @@ -233,10 +233,10 @@ void SetArtifactsPodSpecs () PodSpec.Create ("GooglePlaces", "7.4.0") }; GOOGLE_SIGN_IN_ARTIFACT.PodSpecs = new [] { - PodSpec.Create ("GoogleSignIn", "8.0.0", frameworkSource: FrameworkSource.Pods), - PodSpec.Create ("AppAuth", "1.7.6", frameworkSource: FrameworkSource.Pods), + PodSpec.Create ("GoogleSignIn", "~> 9.0.0", frameworkSource: FrameworkSource.Pods), + PodSpec.Create ("AppAuth", "2.0.0", frameworkSource: FrameworkSource.Pods), PodSpec.Create ("AppCheckCore", "11.2.0", frameworkSource: FrameworkSource.Pods), - PodSpec.Create ("GTMAppAuth", "4.1.1", frameworkSource: FrameworkSource.Pods), + PodSpec.Create ("GTMAppAuth", "5.0.0", frameworkSource: FrameworkSource.Pods), }; GOOGLE_TAG_MANAGER_ARTIFACT.PodSpecs = new [] { PodSpec.Create ("GoogleTagManager", "7.4.0") diff --git a/docs/BUILDING.md b/docs/BUILDING.md new file mode 100644 index 000000000..b1cf5aa76 --- /dev/null +++ b/docs/BUILDING.md @@ -0,0 +1,37 @@ +# Building locally (macOS) + +This repo builds .NET iOS/macOS bindings and NuGet packages using Cake. + +## Prerequisites + +- .NET SDK (see `global.json`) +- Xcode (matching the installed iOS workload requirements) +- CocoaPods (`pod`) + +Restore the local Cake tool (any version `< 1.0` should work): + +```sh +dotnet tool restore +``` + +## Build + pack a component + +Build and produce `.nupkg` files into `./output`: + +```sh +dotnet tool run dotnet-cake -- --target=nuget --names=Google.SignIn +``` + +## Build samples (using source) + +Build sample projects (without publishing packages): + +```sh +dotnet tool run dotnet-cake -- --target=samples --names=Google.SignIn +``` + +Clean generated folders: + +```sh +dotnet tool run dotnet-cake -- --target=clean +``` diff --git a/docs/NUGET_README.md b/docs/NUGET_README.md new file mode 100644 index 000000000..24916b790 --- /dev/null +++ b/docs/NUGET_README.md @@ -0,0 +1,20 @@ +# Overview + +This package is part of a set of .NET bindings for Google and Firebase native iOS SDKs. + +## Target frameworks + +These packages are intended for iOS and Mac Catalyst TFMs (for example `net8.0-ios`, `net9.0-ios`, `net10.0-ios`). + +When multi-targeting, condition the reference so it only restores for iOS: + +```xml + + + +``` + +## Notes on native dependency conflicts + +Google/Firebase iOS SDKs share native dependencies (xcframeworks). Avoid mixing multiple independent bindings that ship overlapping Google/Firebase native SDK binaries in the same app, as it can lead to duplicate symbols or runtime issues. + diff --git a/docs/PUBLISHING_GITHUB_PACKAGES.md b/docs/PUBLISHING_GITHUB_PACKAGES.md new file mode 100644 index 000000000..db7b3c1ea --- /dev/null +++ b/docs/PUBLISHING_GITHUB_PACKAGES.md @@ -0,0 +1,31 @@ +# Publishing to GitHub Packages (NuGet) + +This repository can publish `.nupkg` files to GitHub Packages (NuGet feed) via GitHub Actions. + +## How publishing works + +- A tag push triggers the publish workflow. +- The workflow builds and packs NuGet packages. +- Optionally, you can run the workflow manually (`workflow_dispatch`) and pass a custom Cake `--names` value. +- The workflow pushes packages to: + - `https://nuget.pkg.github.com//index.json` + +## Consuming packages + +Add GitHub Packages as a NuGet source and authenticate using a GitHub PAT (or workflow token in CI). + +Notes: +- The publish workflow uses the built-in `GITHUB_TOKEN` (no extra repository secret is required). +- Local development typically uses a GitHub PAT with `read:packages` (and `repo` for private repos). + +Create a local `NuGet.Config` (or update your existing one) and add: + +```xml + + + + + +``` + +Then restore using credentials for the `` account. diff --git a/samples/Google/SignIn/SignInExample/AppDelegate.cs b/samples/Google/SignIn/SignInExample/AppDelegate.cs index 9f7529d4c..e5c406e50 100644 --- a/samples/Google/SignIn/SignInExample/AppDelegate.cs +++ b/samples/Google/SignIn/SignInExample/AppDelegate.cs @@ -12,16 +12,23 @@ public class AppDelegate : UIApplicationDelegate { // class-level declarations - public override UIWindow Window { + public override UIWindow? Window { get; set; } - public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) + public override bool FinishedLaunching (UIApplication application, NSDictionary? launchOptions) { - // You can get the GoogleService-Info.plist file at https://developers.google.com/mobile/add - var googleServiceDictionary = NSDictionary.FromFile ("GoogleService-Info.plist"); - SignIn.SharedInstance.ClientId = googleServiceDictionary ["CLIENT_ID"].ToString (); + // For a runnable sample, add your own GoogleService-Info.plist (not committed) and set CLIENT_ID. + var googleServiceInfoPath = NSBundle.MainBundle.PathForResource ("GoogleService-Info", "plist"); + var googleServiceDictionary = !string.IsNullOrWhiteSpace (googleServiceInfoPath) + ? NSMutableDictionary.FromFile (googleServiceInfoPath) + : null; + + var clientId = googleServiceDictionary?["CLIENT_ID"]?.ToString (); + + if (!string.IsNullOrWhiteSpace (clientId)) + SignIn.SharedInstance.Configuration = new Configuration (clientId); return true; } @@ -40,5 +47,3 @@ public override bool OpenUrl (UIApplication application, NSUrl url, string sourc } } } - - diff --git a/samples/Google/SignIn/SignInExample/GoogleService-Info.plist b/samples/Google/SignIn/SignInExample/GoogleService-Info.plist deleted file mode 100644 index 25e27f5ed..000000000 --- a/samples/Google/SignIn/SignInExample/GoogleService-Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CLIENT_ID - 1028405910307-am33jrsno99vde2ff7aa8bm29eqt08nb.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.1028405910307-am33jrsno99vde2ff7aa8bm29eqt08nb - PLIST_VERSION - 1 - BUNDLE_ID - com.xamarin.google.signinexample - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - 1:1028405910307:ios:946ba38fcdd039b3 - - diff --git a/samples/Google/SignIn/SignInExample/GoogleService-Info.plist.template b/samples/Google/SignIn/SignInExample/GoogleService-Info.plist.template new file mode 100644 index 000000000..4aeb3dec6 --- /dev/null +++ b/samples/Google/SignIn/SignInExample/GoogleService-Info.plist.template @@ -0,0 +1,17 @@ + + + + + CLIENT_ID + YOUR_CLIENT_ID.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.YOUR_REVERSED_CLIENT_ID + PLIST_VERSION + 1 + BUNDLE_ID + com.example.signinexample + IS_SIGNIN_ENABLED + + + + diff --git a/samples/Google/SignIn/SignInExample/Main.cs b/samples/Google/SignIn/SignInExample/Main.cs index 493aef829..6d0ac32b9 100644 --- a/samples/Google/SignIn/SignInExample/Main.cs +++ b/samples/Google/SignIn/SignInExample/Main.cs @@ -9,7 +9,7 @@ static void Main (string[] args) { // if you want to use a different Application Delegate class from "AppDelegate" // you can specify it here. - UIApplication.Main (args, null, "AppDelegate"); + UIApplication.Main (args, null, typeof (AppDelegate)); } } } diff --git a/samples/Google/SignIn/SignInExample/SignInExample.csproj b/samples/Google/SignIn/SignInExample/SignInExample.csproj index cf56c56e8..32b6f0864 100644 --- a/samples/Google/SignIn/SignInExample/SignInExample.csproj +++ b/samples/Google/SignIn/SignInExample/SignInExample.csproj @@ -15,7 +15,6 @@ iPhoneSimulator;iPhone Debug;Release false - manual SignInExample @@ -29,12 +28,10 @@ ios-arm64 - iPhone Developer full true prompt 4 - Entitlements.plist false SdkOnly @@ -48,14 +45,12 @@ ios-arm64 - iPhone Developer true full false prompt 4 false - Entitlements.plist SdkOnly @@ -63,14 +58,14 @@ ViewController.cs - - - + + + @@ -83,4 +78,4 @@ Resources\en.lproj\Main.strings - \ No newline at end of file + diff --git a/samples/Google/SignIn/SignInExample/ViewController.cs b/samples/Google/SignIn/SignInExample/ViewController.cs index ddc21d45e..1dafe5fcb 100644 --- a/samples/Google/SignIn/SignInExample/ViewController.cs +++ b/samples/Google/SignIn/SignInExample/ViewController.cs @@ -18,24 +18,38 @@ public override void ViewDidLoad () { base.ViewDidLoad (); - SignIn.SharedInstance.PresentingViewController = this; - SignIn.SharedInstance.SignedIn += (sender, e) => { - // Perform any operations on signed in user here. - if (e.User != null && e.Error == null) { - statusText.Text = string.Format ("Signed in user: {0}", e.User.Profile.Name); - ToggleAuthUI (); - } - }; + signInButton.TouchUpInside += (_, __) => { + SignIn.SharedInstance.SignInWithPresentingViewController (this, (result, error) => { + if (error != null) { + statusText.Text = error.LocalizedDescription; + return; + } - SignIn.SharedInstance.Disconnected += (sender, e) => { - // Perform any operations when the user disconnects from app here. - statusText.Text = "Disconnected user"; - ToggleAuthUI (); + var user = result?.User ?? SignIn.SharedInstance.CurrentUser; + statusText.Text = user?.Profile?.Name != null + ? $"Signed in user: {user.Profile.Name}" + : "Signed in"; + + ToggleAuthUI (); + }); }; // Automatically sign in the user. - if (SignIn.SharedInstance.HasPreviousSignIn) - SignIn.SharedInstance.RestorePreviousSignIn (); + if (SignIn.SharedInstance.HasPreviousSignIn) { + SignIn.SharedInstance.RestorePreviousSignInWithCompletion ((user, error) => { + if (error != null) { + statusText.Text = error.LocalizedDescription; + return; + } + + statusText.Text = user?.Profile?.Name != null + ? $"Signed in user: {user.Profile.Name}" + : "Signed in"; + + ToggleAuthUI (); + }); + } + ToggleAuthUI (); statusText.Text = "Google Sign in\niOS Demo"; @@ -49,12 +63,20 @@ partial void didTapSignOut (NSObject sender) partial void didTapDisconnect (NSObject sender) { - SignIn.SharedInstance.DisconnectUser (); + SignIn.SharedInstance.DisconnectWithCompletion (error => { + if (error != null) { + statusText.Text = error.LocalizedDescription; + return; + } + + statusText.Text = "Disconnected user"; + ToggleAuthUI (); + }); } void ToggleAuthUI () { - if (SignIn.SharedInstance.CurrentUser == null || SignIn.SharedInstance.CurrentUser.Authentication == null) { + if (SignIn.SharedInstance.CurrentUser == null) { // Not signed in statusText.Text = "Google Sign in\niOS Demo"; signInButton.Hidden = false; @@ -62,7 +84,8 @@ void ToggleAuthUI () disconnectButton.Hidden = true; } else { // Signed in - statusText.Text = string.Format ("Signed in user: {0}", SignIn.SharedInstance.CurrentUser.Profile.Name); + var name = SignIn.SharedInstance.CurrentUser.Profile?.Name; + statusText.Text = name != null ? $"Signed in user: {name}" : "Signed in"; signInButton.Hidden = true; signOutButton.Hidden = false; disconnectButton.Hidden = false; diff --git a/source/Google/GTMSessionFetcher/GTMSessionFetcher.csproj b/source/Google/GTMSessionFetcher/GTMSessionFetcher.csproj index 9aed43c7e..c329ed470 100644 --- a/source/Google/GTMSessionFetcher/GTMSessionFetcher.csproj +++ b/source/Google/GTMSessionFetcher/GTMSessionFetcher.csproj @@ -24,6 +24,7 @@ © Microsoft Corporation. All rights reserved. © 2024, Adam Essenmacher. https://github.com/AdamEssenmacher/GoogleApisForiOSComponents License.md + NUGET_README.md true 3.5.0.5 @@ -34,6 +35,7 @@ + diff --git a/source/Google/GoogleUtilities/GoogleUtilities.csproj b/source/Google/GoogleUtilities/GoogleUtilities.csproj index 155f83a79..9ff0a7a85 100644 --- a/source/Google/GoogleUtilities/GoogleUtilities.csproj +++ b/source/Google/GoogleUtilities/GoogleUtilities.csproj @@ -24,6 +24,7 @@ © Microsoft Corporation. All rights reserved. © 2024, Adam Essenmacher. https://github.com/AdamEssenmacher/GoogleApisForiOSComponents License.md + NUGET_README.md true 8.1.0.3 @@ -34,6 +35,7 @@ + diff --git a/source/Google/PromisesObjC/PromisesObjC.csproj b/source/Google/PromisesObjC/PromisesObjC.csproj index e3a939f66..311fc8a7a 100644 --- a/source/Google/PromisesObjC/PromisesObjC.csproj +++ b/source/Google/PromisesObjC/PromisesObjC.csproj @@ -24,6 +24,7 @@ © Microsoft Corporation. All rights reserved. © 2024, Adam Essenmacher. https://github.com/AdamEssenmacher/GoogleApisForiOSComponents License.md + NUGET_README.md true 2.4.0.5 @@ -34,6 +35,7 @@ + diff --git a/source/Google/SignIn/SignIn.csproj b/source/Google/SignIn/SignIn.csproj index be643e076..f1e4d0a8f 100644 --- a/source/Google/SignIn/SignIn.csproj +++ b/source/Google/SignIn/SignIn.csproj @@ -8,8 +8,8 @@ 15.0 Google.SignIn Google.SignIn - 8.0.0.4 - 8.0.0.4 + 9.0.0.0 + 9.0.0.0 Resources true @@ -24,8 +24,9 @@ googleiossignin_128x128.png https://github.com/AdamEssenmacher/GoogleApisForiOSComponents License.md + NUGET_README.md true - 8.0.0.4 + 9.0.0.0 @@ -38,6 +39,7 @@ +