diff --git a/.github/workflows/google-foundation-validation.yml b/.github/workflows/google-foundation-validation.yml index 0781dc8d..dc4cfd81 100644 --- a/.github/workflows/google-foundation-validation.yml +++ b/.github/workflows/google-foundation-validation.yml @@ -15,6 +15,8 @@ on: - "source/Google/Places/**" - "tests/E2E/Google.Foundation/**" - "tools/e2e/check-consumer-shapes.sh" + - "tools/e2e/check-package-structure.sh" + - "tools/e2e/check-offline-build.sh" - "tools/e2e/run-google-foundation.sh" push: branches: @@ -32,6 +34,8 @@ on: - "source/Google/Places/**" - "tests/E2E/Google.Foundation/**" - "tools/e2e/check-consumer-shapes.sh" + - "tools/e2e/check-package-structure.sh" + - "tools/e2e/check-offline-build.sh" - "tools/e2e/run-google-foundation.sh" workflow_dispatch: @@ -75,12 +79,24 @@ jobs: - name: Pack Google.Places run: dotnet tool run dotnet-cake -- --target=nuget --names=Google.Places + - name: Package structure checks + run: >- + tools/e2e/check-package-structure.sh + --target Places + --package-dir output + - name: Direct and transitive consumer checks run: >- tools/e2e/check-consumer-shapes.sh --target Places --package-dir output + - name: Offline build proof + run: >- + tools/e2e/check-offline-build.sh + --target Places + --package-dir output + - name: Simulator E2E run: >- tools/e2e/run-google-foundation.sh diff --git a/.gitignore b/.gitignore index 2f5d3b00..a8a423df 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ tools/e2e/* !tools/e2e/run-firebase-foundation.sh !tools/e2e/run-google-foundation.sh !tools/e2e/check-consumer-shapes.sh +!tools/e2e/check-package-structure.sh +!tools/e2e/check-offline-build.sh tmp-nugets *.userprefs *.DS_Store diff --git a/Readme.md b/Readme.md index 5448aeee..dda107db 100644 --- a/Readme.md +++ b/Readme.md @@ -197,7 +197,7 @@ Firebase `12.10.0` is the current published Firebase package line. | Package | Version | | --- | --- | | `Maps` | `9.2.0.8` | -| `Places` | `7.4.0.2` | +| `Places` | `7.4.0.3` | | `SignIn` | `9.0.0` | #### Google support packages (`AdamE.Google.iOS.*`) diff --git a/build.cake b/build.cake index ac389b2b..f6c31c47 100644 --- a/build.cake +++ b/build.cake @@ -171,6 +171,8 @@ Task ("externals") FirebaseAnalyticsDownload (); if (ARTIFACTS_TO_BUILD.Contains (GOOGLE_GOOGLE_APP_MEASUREMENT_ARTIFACT)) GoogleAppMeasurementDownload (); + if (ARTIFACTS_TO_BUILD.Contains (GOOGLE_PLACES_ARTIFACT)) + GooglePlacesDownload (); }); Task ("ci-setup") diff --git a/components.cake b/components.cake index 612ded0e..2fa8b851 100644 --- a/components.cake +++ b/components.cake @@ -21,7 +21,7 @@ Artifact GOOGLE_ANALYTICS_ARTIFACT = new Artifact ("Google.Analy Artifact GOOGLE_CAST_ARTIFACT = new Artifact ("Google.Cast", "4.7.0.1", "15.0", ComponentGroup.Google, csprojName: "Cast"); Artifact GOOGLE_MAPS_ARTIFACT = new Artifact ("Google.Maps", "9.2.0.8", "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.2", "15.0", ComponentGroup.Google, csprojName: "Places"); +Artifact GOOGLE_PLACES_ARTIFACT = new Artifact ("Google.Places", "7.4.0.3", "15.0", ComponentGroup.Google, csprojName: "Places"); Artifact GOOGLE_APP_CHECK_CORE_ARTIFACT = new Artifact ("Google.AppCheckCore", "11.2.0.0", "15.0", ComponentGroup.Google, csprojName: "AppCheckCore"); 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", "9.2.0.0", "15.0", ComponentGroup.Google, csprojName: "TagManager"); @@ -225,8 +225,10 @@ void SetArtifactsPodSpecs () GOOGLE_UMP_ARTIFACT.PodSpecs = new [] { PodSpec.Create ("GoogleUserMessagingPlatform", "1.1.0") }; + // The xcframework is fetched by GooglePlacesDownload() in custom_externals_download.cake + // rather than by a XamarinBuildDownload item in the .targets file. GOOGLE_PLACES_ARTIFACT.PodSpecs = new [] { - PodSpec.Create ("GooglePlaces", "7.4.0") + PodSpec.Create ("GooglePlaces", "7.4.0", frameworkSource: FrameworkSource.Custom) }; GOOGLE_SIGN_IN_ARTIFACT.PodSpecs = new [] { PodSpec.Create ("GoogleSignIn", "9.0.0", frameworkSource: FrameworkSource.Pods), diff --git a/custom_externals_download.cake b/custom_externals_download.cake index d2801dfa..ab38ebc1 100644 --- a/custom_externals_download.cake +++ b/custom_externals_download.cake @@ -1,27 +1,35 @@ class ExternalDownloadSource { + // Most of these archives live under the Firebase analytics path, but Google publishes + // other SDKs under different hosts/paths, so the prefix is overridable. + const string DefaultUrlPrefix = "https://dl.google.com/firebase/ios/analytics"; + public string Id { get; } public string Version { get; } public string ArchiveKey { get; } + public string UrlPrefix { get; } - public ExternalDownloadSource (string id, string version, string archiveKey) + public ExternalDownloadSource (string id, string version, string archiveKey, string urlPrefix = DefaultUrlPrefix) { Id = id; Version = version; ArchiveKey = archiveKey; + UrlPrefix = urlPrefix; } public string ArchiveFileName => $"{Id}-{Version}.tar.gz"; public string ExtractionRootName => $"{Id}-{Version}"; - public string Url => $"https://dl.google.com/firebase/ios/analytics/{ArchiveKey}/{ArchiveFileName}"; + public string Url => $"{UrlPrefix}/{ArchiveKey}/{ArchiveFileName}"; } // *.tar.gz URLs can be found in the podspecs (e.g., CocoaPods Specs repo paths), such as: // FirebaseAnalytics: https://github.com/CocoaPods/Specs/tree/master/Specs/e/2/1/FirebaseAnalytics // GoogleAppMeasurement: https://github.com/CocoaPods/Specs/tree/master/Specs/e/3/b/GoogleAppMeasurement +// GooglePlaces: https://github.com/CocoaPods/Specs/tree/master/Specs/c/3/2/GooglePlaces var ExternalDownloads = new Dictionary { { "FirebaseAnalytics", new ExternalDownloadSource ("FirebaseAnalytics", "12.10.0", "3c185b45848d98d8") }, { "GoogleAppMeasurement", new ExternalDownloadSource ("GoogleAppMeasurement", "12.10.0", "5f5e4d8cb469941e") }, + { "GooglePlaces", new ExternalDownloadSource ("GooglePlaces", "7.4.0", "3e8dc2602895d53405d075ff4eb569bff93ff1af97e69915d1e657c07ef28dd8", "https://dl.google.com/dl/geosdk") }, }; FilePath GetArchivePath (ExternalDownloadSource source, DirectoryPath externalsPath) => @@ -97,6 +105,27 @@ void FirebaseAnalyticsDownload () }); } +void GooglePlacesDownload () +{ + var source = ExternalDownloads["GooglePlaces"]; + + DownloadAndExtract ( + source, + () => DirectoryExists (new DirectoryPath ("./externals/GooglePlaces.xcframework")), + (extractionRoot, externalsPath, deleteSettings) => { + var frameworkSource = extractionRoot.Combine ("Frameworks").Combine ("GooglePlaces.xcframework"); + var frameworkDestination = externalsPath.Combine ("GooglePlaces.xcframework"); + + if (!DirectoryExists (frameworkSource)) + throw new Exception ($"Expected GooglePlaces.xcframework at {frameworkSource} after extraction."); + + if (DirectoryExists (frameworkDestination)) + DeleteDirectory (frameworkDestination, deleteSettings); + + CopyDirectory (frameworkSource, frameworkDestination); + }); +} + void GoogleAppMeasurementDownload () { var source = ExternalDownloads["GoogleAppMeasurement"]; diff --git a/source/Google/Places/Places.buildTransitive.targets b/source/Google/Places/Places.buildTransitive.targets new file mode 100644 index 00000000..542d94e4 --- /dev/null +++ b/source/Google/Places/Places.buildTransitive.targets @@ -0,0 +1,8 @@ + + + + diff --git a/source/Google/Places/Places.csproj b/source/Google/Places/Places.csproj index f50eea0d..4e6b9a48 100644 --- a/source/Google/Places/Places.csproj +++ b/source/Google/Places/Places.csproj @@ -4,12 +4,12 @@ enable true true - false + true 15.0 Google.Places Google.Places - 7.4.0.2 - 7.4.0.2 + 7.4.0.3 + 7.4.0.3 Resources true true @@ -26,7 +26,7 @@ https://github.com/AdamEssenmacher/GoogleApisForiOSComponents License.md true - 7.4.0.2 + 7.4.0.3 @@ -36,15 +36,30 @@ - + - - + + + + + + + Framework + True + True + CoreGraphics CoreLocation QuartzCore Security UIKit + -ObjC + - + + diff --git a/source/Google/Places/Places.targets b/source/Google/Places/Places.targets index 5eed15af..30bc2cf9 100644 --- a/source/Google/Places/Places.targets +++ b/source/Google/Places/Places.targets @@ -1,210 +1,200 @@ - <_GooglePlacesAssemblyName>Google.Places, Version=7.4.0.1, Culture=neutral, PublicKeyToken=null - <_GooglePlacesItemsFolder>GPlcs-7.4.0 - <_GooglePlacesSDKBaseFolder>$(XamarinBuildDownloadDir)$(_GooglePlacesItemsFolder)\GooglePlaces-7.4.0\Frameworks\ + <_GooglePlacesResourcesBaseFolder>$(MSBuildThisFileDirectory)GooglePlaces.bundle\ + - - https://dl.google.com/dl/geosdk/3e8dc2602895d53405d075ff4eb569bff93ff1af97e69915d1e657c07ef28dd8/GooglePlaces-7.4.0.tar.gz - Tgz - - - Framework - True - True - CoreGraphics CoreLocation QuartzCore Security UIKit - -ObjC - - + + GooglePlaces.bundle\build-with-google-black@2x.png + False + + + GooglePlaces.bundle\build-with-google-black@3x.png + False + + + GooglePlaces.bundle\build-with-google-white@2x.png + False + + + GooglePlaces.bundle\build-with-google-white@3x.png + False + + + GooglePlaces.bundle\Info.plist + + + GooglePlaces.bundle\oss_licenses_places.txt.gz + + + GooglePlaces.bundle\sad_cloud@2x.png + False + + + GooglePlaces.bundle\sad_cloud@3x.png + False + + + GooglePlaces.bundle\sad_cloud_dark@2x.png + False + + + GooglePlaces.bundle\sad_cloud_dark@3x.png + False + + + GooglePlaces.bundle\ar.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\ca.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\cs.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\da.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\de.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\el.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\en.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\en_AU.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\en_GB.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\en_IN.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\es.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\es_419.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\es_MX.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\fi.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\fr.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\fr_CA.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\he.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\hi.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\hr.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\hu.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\id.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\it.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\ja.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\ka.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\ko.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\lt.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\lv.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\ms.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\my.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\nb.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\nl.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\pl.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\pt.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\pt_BR.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\pt_PT.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\ro.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\ru.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\sk.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\sq.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\sr.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\sv.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\sw.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\th.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\tr.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\uk.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\vi.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\zh_CN.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\zh_HK.lproj\GooglePlaces.strings + + + GooglePlaces.bundle\zh_TW.lproj\GooglePlaces.strings + - - - - GooglePlaces.bundle\build-with-google-black@2x.png - False - - - GooglePlaces.bundle\build-with-google-black@3x.png - False - - - GooglePlaces.bundle\build-with-google-white@2x.png - False - - - GooglePlaces.bundle\build-with-google-white@3x.png - False - - - GooglePlaces.bundle\Info.plist - - - GooglePlaces.bundle\oss_licenses_places.txt.gz - - - GooglePlaces.bundle\sad_cloud@2x.png - False - - - GooglePlaces.bundle\sad_cloud@3x.png - False - - - GooglePlaces.bundle\sad_cloud_dark@2x.png - False - - - GooglePlaces.bundle\sad_cloud_dark@3x.png - False - - - GooglePlaces.bundle\ar.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\ca.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\cs.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\da.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\de.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\el.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\en.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\en_AU.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\en_GB.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\en_IN.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\es.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\es_419.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\es_MX.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\fi.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\fr.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\fr_CA.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\he.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\hi.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\hr.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\hu.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\id.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\it.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\ja.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\ka.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\ko.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\lt.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\lv.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\ms.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\my.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\nb.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\nl.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\pl.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\pt.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\pt_BR.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\pt_PT.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\ro.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\ru.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\sk.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\sq.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\sr.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\sv.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\sw.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\th.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\tr.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\uk.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\vi.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\zh_CN.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\zh_HK.lproj\GooglePlaces.strings - - - GooglePlaces.bundle\zh_TW.lproj\GooglePlaces.strings - - - diff --git a/tests/E2E/Google.Foundation/GoogleFoundationE2E/GoogleFoundationE2E.csproj b/tests/E2E/Google.Foundation/GoogleFoundationE2E/GoogleFoundationE2E.csproj index 7899fa4b..69cef9aa 100644 --- a/tests/E2E/Google.Foundation/GoogleFoundationE2E/GoogleFoundationE2E.csproj +++ b/tests/E2E/Google.Foundation/GoogleFoundationE2E/GoogleFoundationE2E.csproj @@ -22,7 +22,7 @@ --> Places - 7.4.0.2 + 7.4.0.3 @@ -51,10 +51,8 @@ <_Parameter2>$(GoogleE2ETarget) <_Parameter1>TargetPackageVersion diff --git a/tests/E2E/Google.Foundation/README.md b/tests/E2E/Google.Foundation/README.md index 05fcadcd..90947090 100644 --- a/tests/E2E/Google.Foundation/README.md +++ b/tests/E2E/Google.Foundation/README.md @@ -10,12 +10,11 @@ binding-layer failures such as `EntryPointNotFoundException`, `DllNotFoundExcept ## Current scope and baseline -The sole runtime adapter is `AdamE.Google.iOS.Places` 7.4.0.2. +The sole runtime adapter is `AdamE.Google.iOS.Places` 7.4.0.3. -That package is the pre-migration behavioral baseline: it uses `Xamarin.Build.Download` to fetch the -Google Places SDK during the consumer build. The harness deliberately does not assert how the native -SDK arrives. It asserts the resulting consumer behavior, so the same checks can be run unchanged -before and after a package-delivery refactor. +The behavioral expectations were established against the 7.4.0.2 pre-migration package, which used +`Xamarin.Build.Download` to fetch the Google Places SDK during the consumer build. The harness does +not assert how the native SDK arrives, so the same checks validate the self-contained package. The Places adapter verifies: @@ -26,9 +25,9 @@ The Places adapter verifies: - The bundle contains the baseline's 59 files, including representative data, localized string, and image files. -This harness does not inspect the `.nupkg`, prove offline builds, compare stored baselines, exercise a -physical device, or test the Places backend. Those concerns belong in separate package-delivery -checks when the package format changes. +The companion `check-package-structure.sh` and `check-offline-build.sh` scripts inspect the package +and prove the consumer build no longer downloads native content. The harness does not compare stored +baselines, exercise a physical device, or test the Places backend. ## Run it @@ -50,7 +49,7 @@ When the directory contains multiple versions, select one explicitly: tools/e2e/run-google-foundation.sh \ --target Places \ --package-dir output \ - --package-version 7.4.0.2 + --package-version 7.4.0.3 ``` `NuGet.config` maps `AdamE.*` packages to the repository's local `output/` feed. For another package @@ -79,6 +78,9 @@ The same ignored directory contains the simulator log and target-neutral build d These files aid failure investigation; they are not golden baselines. +Failed structure and offline checks copy their diagnostics to `package-structure-Places/` and +`offline-Places/` beneath the same artifacts directory. + ## Adding another Google package The host app is reusable, while each binding needs an explicit runtime adapter. Add a target by: diff --git a/tools/e2e/check-offline-build.sh b/tools/e2e/check-offline-build.sh new file mode 100755 index 00000000..8599a5ac --- /dev/null +++ b/tools/e2e/check-offline-build.sh @@ -0,0 +1,311 @@ +#!/bin/zsh +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: tools/e2e/check-offline-build.sh --target Places [options] + + --package-dir Local NuGet feed (default: output) + --package-version Exact package version (required when the feed contains multiple) + --allow-xcode-mismatch Pass ValidateXcodeVersion=false (local escape hatch, not for CI) + +Restores a throwaway consumer with network access, then builds it with --no-restore and HTTP(S) +egress pointed at a dead proxy. The selected local package must be restored byte-for-byte, the +isolated XamarinBuildDownload directory must remain empty, and the isolated NuGet cache must remain +unchanged throughout the offline build. +EOF +} + +repo_root="$(cd "$(dirname "$0")/../.." && pwd)" +target="Places" +package_version="" +package_dir="$repo_root/output" +allow_xcode_mismatch="false" + +while [[ $# -gt 0 ]]; do + case "$1" in + --target) + [[ $# -ge 2 ]] || { echo "--target requires a value" >&2; exit 1; } + target="$2"; shift 2 + ;; + --package-version) + [[ $# -ge 2 ]] || { echo "--package-version requires a value" >&2; exit 1; } + package_version="$2"; shift 2 + ;; + --package-dir) + [[ $# -ge 2 ]] || { echo "--package-dir requires a value" >&2; exit 1; } + package_dir="$2"; shift 2 + ;; + --allow-xcode-mismatch) allow_xcode_mismatch="true"; shift ;; + --help|-h) usage; exit 0 ;; + *) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;; + esac +done + +[[ "$package_dir" != /* ]] && package_dir="$repo_root/$package_dir" + +case "$target" in + Places) + package_id="AdamE.Google.iOS.Places" + probe_expr="typeof(Google.Places.AutocompleteFilter).FullName!" + ;; + *) echo "Unknown target: $target" >&2; exit 1 ;; +esac + +if [[ ! -d "$package_dir" ]]; then + echo "Package directory does not exist: $package_dir" >&2 + exit 1 +fi + +if [[ -n "$package_version" ]]; then + nupkg="$package_dir/$package_id.$package_version.nupkg" + if [[ ! -f "$nupkg" ]]; then + echo "No $package_id $package_version package found at $nupkg" >&2 + exit 1 + fi +else + candidates=("$package_dir"/"$package_id".*.nupkg(N)) + nupkgs=() + for candidate in "${candidates[@]}"; do + [[ "$candidate" == *.symbols.nupkg ]] && continue + nupkgs+=("$candidate") + done + + case ${#nupkgs[@]} in + 0) + echo "No $package_id package found in $package_dir" >&2 + exit 1 + ;; + 1) + nupkg="${nupkgs[1]}" + package_version="${${nupkg:t}#$package_id.}" + package_version="${package_version%.nupkg}" + ;; + *) + echo "Multiple $package_id packages found in $package_dir; pass --package-version:" >&2 + printf ' %s\n' "${nupkgs[@]:t}" >&2 + exit 1 + ;; + esac +fi + +echo "Offline build check: $package_id $package_version from $nupkg" + +work="$(cd "$(mktemp -d)" && pwd -P)" +xbd_dir="$work/xbd/" +export NUGET_PACKAGES="$work/nuget/packages" +export NUGET_HTTP_CACHE_PATH="$work/nuget/http-cache" +export NUGET_PLUGINS_CACHE_PATH="$work/nuget/plugins-cache" +diagnostics="$work/diagnostics" +artifacts_dir="$repo_root/tests/E2E/Google.Foundation/artifacts/offline-$target" +mkdir -p "$xbd_dir" "$NUGET_PACKAGES" "$NUGET_HTTP_CACHE_PATH" "$NUGET_PLUGINS_CACHE_PATH" "$diagnostics" +rm -rf "$artifacts_dir" +cp "$repo_root/global.json" "$work/global.json" + +failures=0 +completed="false" +pass() { print -r -- " PASS $1"; } +fail() { print -r -- " FAIL $1" >&2; failures=$((failures + 1)); } + +cleanup() { + local exit_status=$? + + if [[ "$completed" != "true" ]]; then + cp "$work"/*.log(N) "$diagnostics/" 2>/dev/null || true + cp "$work"/NuGet.config "$work"/global.json "$diagnostics/" 2>/dev/null || true + cp "$work/app"/*.cs(N) "$work/app"/*.csproj(N) "$work/app"/Info.plist(N) "$diagnostics/" 2>/dev/null || true + cp "$work/app/obj/project.assets.json" "$diagnostics/" 2>/dev/null || true + mkdir -p "$artifacts_dir" + cp -R "$diagnostics"/. "$artifacts_dir/" 2>/dev/null || true + print -r -- "$nupkg" > "$artifacts_dir/selected-package.txt" + print -r -- "Diagnostics copied to $artifacts_dir" >&2 + fi + + rm -rf "$work" + return "$exit_status" +} +trap cleanup EXIT + +snapshot_files() { + local root="$1" destination="$2" relative_path + : > "$destination" + [[ -d "$root" ]] || return + while IFS= read -r relative_path; do + print -r -- "$(shasum -a 256 "$root/$relative_path" | awk '{ print $1 }') $relative_path" >> "$destination" + done < <(cd "$root" && find . -type f -print | sed 's|^\./||' | LC_ALL=C sort) +} + +directory_is_empty() { + [[ -z "$(find "$1" -mindepth 1 -print -quit 2>/dev/null)" ]] +} + +cat > "$work/NuGet.config" < + + + + + + + + + + + + + + + +EOF + +mkdir -p "$work/app" +cat > "$work/app/OfflineApp.csproj" < + + net10.0-ios + Exe + enable + 15.0 + iossimulator-arm64 + iPhoneSimulator + false + manual + OfflineApp + OfflineProbe + + + + + +EOF + +cat > "$work/app/Main.cs" <<'EOF' +using UIKit; +UIApplication.Main(args, null, typeof(OfflineProbe.AppDelegate)); +EOF + +cat > "$work/app/AppDelegate.cs" < "$work/app/Info.plist" <<'EOF' + + + + + CFBundleIdentifier + com.googleapisforioscomponents.tests.offlineprobe + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + +EOF + +msbuild_args=("-p:XamarinBuildDownloadDir=$xbd_dir") +[[ "$allow_xcode_mismatch" == "true" ]] && msbuild_args+=("-p:ValidateXcodeVersion=false") + +echo +echo "Restoring (network allowed)" +if ! dotnet restore "$work/app/OfflineApp.csproj" \ + --configfile "$work/NuGet.config" \ + --packages "$NUGET_PACKAGES" \ + --force-evaluate \ + "${msbuild_args[@]}" > "$work/restore.log" 2>&1; then + fail "restore failed" + tail -25 "$work/restore.log" >&2 + exit 1 +fi +pass "consumer restore completed" + +restored_nupkg="$NUGET_PACKAGES/${package_id:l}/$package_version/${package_id:l}.$package_version.nupkg" +if [[ ! -f "$restored_nupkg" ]]; then + fail "restored package is missing at $restored_nupkg" +elif cmp -s "$nupkg" "$restored_nupkg"; then + pass "restore consumed the selected local package byte-for-byte" +else + fail "restored package differs from the selected local package" +fi + +snapshot_files "$xbd_dir" "$diagnostics/xbd-before.txt" +snapshot_files "$NUGET_PACKAGES" "$diagnostics/nuget-before.txt" +if directory_is_empty "$xbd_dir"; then + pass "isolated XamarinBuildDownload directory is empty after restore" +else + fail "isolated XamarinBuildDownload directory was populated during restore" +fi + +rm -rf "$NUGET_HTTP_CACHE_PATH" +mkdir -p "$NUGET_HTTP_CACHE_PATH" + +echo +echo "Building with HTTP(S) egress blackholed" +if env \ + http_proxy="http://127.0.0.1:9" \ + https_proxy="http://127.0.0.1:9" \ + all_proxy="http://127.0.0.1:9" \ + HTTP_PROXY="http://127.0.0.1:9" \ + HTTPS_PROXY="http://127.0.0.1:9" \ + ALL_PROXY="http://127.0.0.1:9" \ + no_proxy="" \ + NO_PROXY="" \ + dotnet build "$work/app/OfflineApp.csproj" \ + --configuration Debug \ + --no-restore \ + "${msbuild_args[@]}" > "$work/build.log" 2>&1; then + pass "app built with no network egress available" +else + fail "app build failed without network egress" + tail -25 "$work/build.log" >&2 +fi + +snapshot_files "$xbd_dir" "$diagnostics/xbd-after.txt" +snapshot_files "$NUGET_PACKAGES" "$diagnostics/nuget-after.txt" +diff -u "$diagnostics/xbd-before.txt" "$diagnostics/xbd-after.txt" > "$diagnostics/xbd.diff" || true +diff -u "$diagnostics/nuget-before.txt" "$diagnostics/nuget-after.txt" > "$diagnostics/nuget.diff" || true + +if directory_is_empty "$xbd_dir"; then + pass "isolated XamarinBuildDownload directory remained empty" +else + fail "isolated XamarinBuildDownload directory was populated during the build" +fi +if cmp -s "$diagnostics/xbd-before.txt" "$diagnostics/xbd-after.txt"; then + pass "isolated XamarinBuildDownload state did not change" +else + fail "isolated XamarinBuildDownload state changed during the build" +fi +if cmp -s "$diagnostics/nuget-before.txt" "$diagnostics/nuget-after.txt"; then + pass "isolated NuGet package cache did not change during the build" +else + fail "isolated NuGet package cache changed during the build" +fi +if directory_is_empty "$NUGET_HTTP_CACHE_PATH"; then + pass "isolated NuGet HTTP cache remained empty during the build" +else + fail "isolated NuGet HTTP cache was populated during the build" +fi + +echo +if (( failures > 0 )); then + echo "$failures offline check(s) failed for $package_id." >&2 + exit 1 +fi + +completed="true" +echo "Offline build checks passed for $package_id $package_version." diff --git a/tools/e2e/check-package-structure.sh b/tools/e2e/check-package-structure.sh new file mode 100755 index 00000000..00130f78 --- /dev/null +++ b/tools/e2e/check-package-structure.sh @@ -0,0 +1,468 @@ +#!/bin/zsh +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: tools/e2e/check-package-structure.sh --target Places [options] + + --package-dir Local NuGet feed (default: output) + --package-version Exact package version (required when the feed contains multiple) + +Checks a packed binding package without building a consumer. It verifies that every managed TFM +contains the native XCFramework and linker metadata, that the resource bundle exactly matches the +embedded SDK, that packaged MSBuild files match source, and that Xamarin.Build.Download is absent. +EOF +} + +repo_root="$(cd "$(dirname "$0")/../.." && pwd)" +target="Places" +package_version="" +package_dir="$repo_root/output" + +while [[ $# -gt 0 ]]; do + case "$1" in + --target) + [[ $# -ge 2 ]] || { echo "--target requires a value" >&2; exit 1; } + target="$2"; shift 2 + ;; + --package-dir) + [[ $# -ge 2 ]] || { echo "--package-dir requires a value" >&2; exit 1; } + package_dir="$2"; shift 2 + ;; + --package-version) + [[ $# -ge 2 ]] || { echo "--package-version requires a value" >&2; exit 1; } + package_version="$2"; shift 2 + ;; + --help|-h) usage; exit 0 ;; + *) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;; + esac +done + +[[ "$package_dir" != /* ]] && package_dir="$repo_root/$package_dir" + +typeset -A expected_slice_archs +case "$target" in + Places) + package_id="AdamE.Google.iOS.Places" + assembly_name="Google.Places" + xcframework="GooglePlaces.xcframework" + framework_binary="GooglePlaces" + expected_slices=("ios-arm64" "ios-arm64_x86_64-simulator") + expected_slice_archs=( + "ios-arm64" "arm64" + "ios-arm64_x86_64-simulator" "arm64 x86_64" + ) + resource_bundle="GooglePlaces.bundle" + expected_bundle_files=59 + source_project="$repo_root/source/Google/Places/Places.csproj" + source_build_targets="$repo_root/source/Google/Places/Places.targets" + source_transitive_targets="$repo_root/source/Google/Places/Places.buildTransitive.targets" + source_files=( + "$source_project" + "$source_build_targets" + "$source_transitive_targets" + ) + expected_kind="Framework" + expected_smartlink="True" + expected_forceload="True" + expected_frameworks="CoreGraphics CoreLocation QuartzCore Security UIKit" + expected_linkerflags="-ObjC" + ;; + *) echo "Unknown target: $target" >&2; exit 1 ;; +esac + +if [[ ! -d "$package_dir" ]]; then + echo "Package directory does not exist: $package_dir" >&2 + exit 1 +fi + +if [[ -n "$package_version" ]]; then + nupkg="$package_dir/$package_id.$package_version.nupkg" + if [[ ! -f "$nupkg" ]]; then + echo "No $package_id $package_version package found at $nupkg" >&2 + exit 1 + fi +else + candidates=("$package_dir"/"$package_id".*.nupkg(N)) + nupkgs=() + for candidate in "${candidates[@]}"; do + [[ "$candidate" == *.symbols.nupkg ]] && continue + nupkgs+=("$candidate") + done + + case ${#nupkgs[@]} in + 0) + echo "No $package_id package found in $package_dir" >&2 + exit 1 + ;; + 1) + nupkg="${nupkgs[1]}" + package_version="${${nupkg:t}#$package_id.}" + package_version="${package_version%.nupkg}" + ;; + *) + echo "Multiple $package_id packages found in $package_dir; pass --package-version:" >&2 + printf ' %s\n' "${nupkgs[@]:t}" >&2 + exit 1 + ;; + esac +fi + +echo "Checking $package_id $package_version from $nupkg" + +work="$(cd "$(mktemp -d)" && pwd -P)" +diagnostics="$work/diagnostics" +artifacts_dir="$repo_root/tests/E2E/Google.Foundation/artifacts/package-structure-$target" +mkdir -p "$diagnostics" +rm -rf "$artifacts_dir" + +failures=0 +completed="false" +pass() { print -r -- " PASS $1"; } +fail() { print -r -- " FAIL $1" >&2; failures=$((failures + 1)); } + +cleanup() { + local exit_status=$? + + if [[ "$completed" != "true" ]]; then + mkdir -p "$artifacts_dir" + cp -R "$diagnostics"/. "$artifacts_dir/" 2>/dev/null || true + print -r -- "$nupkg" > "$artifacts_dir/selected-package.txt" + print -r -- "Diagnostics copied to $artifacts_dir" >&2 + fi + + rm -rf "$work" + return "$exit_status" +} +trap cleanup EXIT + +if ! unzip -Z1 "$nupkg" > "$diagnostics/package-files.txt" 2> "$diagnostics/unzip-list.log"; then + fail "could not list package contents" + exit 1 +fi +if ! unzip -q "$nupkg" -d "$work/pkg" 2> "$diagnostics/unzip.log"; then + fail "could not extract package" + exit 1 +fi + +write_file_manifest() { + local root="$1" destination="$2" + (cd "$root" && find . -type f -print | sed 's|^\./||' | LC_ALL=C sort) > "$destination" +} + +read_xml_value() { + local file="$1" field="$2" + sed -nE "s|.*<$field>([^<]*).*|\\1|p" "$file" | sed -n '1p' +} + +check_manifest_value() { + local manifest="$1" tfm="$2" field="$3" expected="$4" actual + actual="$(read_xml_value "$manifest" "$field" || true)" + if [[ "$actual" == "$expected" ]]; then + pass "$tfm: manifest $field = $expected" + else + fail "$tfm: manifest $field = '$actual', expected '$expected'" + fi +} + +assembly_inspector="" +inspector_dir="$work/assembly-inspector" +mkdir -p "$inspector_dir" +cp "$repo_root/global.json" "$work/global.json" +cat > "$work/NuGet.config" <<'EOF' + + + + + + +EOF +cat > "$inspector_dir/AssemblyInspector.csproj" <<'EOF' + + + net10.0 + Exe + enable + enable + false + + +EOF +cat > "$inspector_dir/Program.cs" <<'EOF' +using System.Diagnostics; +using System.Reflection; + +var assemblyName = AssemblyName.GetAssemblyName(args[0]); +var fileVersion = FileVersionInfo.GetVersionInfo(args[0]).FileVersion ?? string.Empty; +Console.WriteLine($"{assemblyName.Name}|{assemblyName.Version}|{fileVersion}"); +EOF +if dotnet restore "$inspector_dir/AssemblyInspector.csproj" \ + --configfile "$work/NuGet.config" \ + --packages "$work/inspector-packages" > "$diagnostics/inspector-restore.log" 2>&1 \ + && dotnet build "$inspector_dir/AssemblyInspector.csproj" \ + --configuration Release \ + --no-restore > "$diagnostics/inspector-build.log" 2>&1; then + assembly_inspector="$inspector_dir/bin/Release/net10.0/AssemblyInspector.dll" +else + fail "could not build the managed assembly identity inspector" +fi + +echo +echo "Package identity" +nuspec="$work/pkg/$package_id.nuspec" +if [[ ! -f "$nuspec" ]]; then + fail "$package_id.nuspec is missing" +else + cp "$nuspec" "$diagnostics/package.nuspec" + nuspec_id="$(read_xml_value "$nuspec" id || true)" + nuspec_version="$(read_xml_value "$nuspec" version || true)" + if [[ "$nuspec_id" == "$package_id" ]]; then + pass "nuspec id = $package_id" + else + fail "nuspec id = '$nuspec_id', expected '$package_id'" + fi + if [[ "$nuspec_version" == "$package_version" ]]; then + pass "nuspec version = $package_version" + else + fail "nuspec version = '$nuspec_version', expected '$package_version'" + fi +fi + +for version_field in AssemblyVersion FileVersion PackageVersion; do + source_version="$(read_xml_value "$source_project" "$version_field" || true)" + if [[ "$source_version" == "$package_version" ]]; then + pass "source $version_field = $package_version" + else + fail "source $version_field = '$source_version', expected '$package_version'" + fi +done + +echo +echo "Native payload per TFM" +lib_dirs=("$work/pkg"/lib/*(/N)) +if [[ ${#lib_dirs[@]} -eq 0 ]]; then + fail "package contains no lib/ folders" +fi + +for lib_dir in "${lib_dirs[@]}"; do + tfm="${lib_dir:t}" + payload_root="$work/payload-$tfm" + mkdir -p "$payload_root" + + if [[ ! -f "$lib_dir/$assembly_name.dll" ]]; then + fail "$tfm: missing $assembly_name.dll" + continue + fi + if [[ -n "$assembly_inspector" ]]; then + assembly_identity="$(dotnet "$assembly_inspector" "$lib_dir/$assembly_name.dll" 2>> "$diagnostics/assembly-inspector.log" || true)" + print -r -- "$tfm|$assembly_identity" >> "$diagnostics/assembly-identities.txt" + if [[ "$assembly_identity" == "$assembly_name|$package_version|$package_version" ]]; then + pass "$tfm: managed assembly identity and file version = $package_version" + else + fail "$tfm: managed assembly identity '$assembly_identity', expected '$assembly_name|$package_version|$package_version'" + fi + fi + + if [[ -f "$lib_dir/$assembly_name.resources.zip" ]]; then + payload="$lib_dir/$assembly_name.resources.zip" + if ! unzip -Z1 "$payload" > "$diagnostics/payload-$tfm-files.txt" 2> "$diagnostics/payload-$tfm-list.log"; then + fail "$tfm: could not list $assembly_name.resources.zip" + continue + fi + if ! unzip -q "$payload" -d "$payload_root" 2> "$diagnostics/payload-$tfm-unzip.log"; then + fail "$tfm: could not extract $assembly_name.resources.zip" + continue + fi + pass "$tfm: native payload present ($assembly_name.resources.zip)" + elif [[ -d "$lib_dir/$assembly_name.resources" ]]; then + payload="$lib_dir/$assembly_name.resources" + cp -R "$payload"/. "$payload_root/" + write_file_manifest "$payload_root" "$diagnostics/payload-$tfm-files.txt" + pass "$tfm: native payload present ($assembly_name.resources/)" + else + fail "$tfm: no native payload (neither $assembly_name.resources.zip nor $assembly_name.resources/)" + continue + fi + + framework_root="$payload_root/$xcframework" + framework_info="$framework_root/Info.plist" + if [[ ! -d "$framework_root" ]]; then + fail "$tfm: $xcframework missing from native payload" + continue + fi + pass "$tfm: $xcframework present" + + if [[ -f "$framework_info" ]]; then + /usr/bin/plutil -p "$framework_info" > "$diagnostics/xcframework-$tfm.txt" 2>&1 || true + for slice in "${expected_slices[@]}"; do + if [[ "$(grep -F -c "\"$slice\"" "$diagnostics/xcframework-$tfm.txt" || true)" -gt 0 ]]; then + pass "$tfm: slice $slice declared" + else + fail "$tfm: slice $slice missing from $xcframework/Info.plist" + fi + + slice_binary="$framework_root/$slice/$framework_binary.framework/$framework_binary" + if [[ ! -s "$slice_binary" ]]; then + fail "$tfm: slice $slice framework binary is missing or empty" + continue + fi + + file_description="$(file -b "$slice_binary" 2>/dev/null || true)" + print -r -- "$file_description" > "$diagnostics/framework-$tfm-$slice-file.txt" + if [[ "$file_description" == *"Mach-O"* ]]; then + pass "$tfm: slice $slice contains a Mach-O framework binary" + else + fail "$tfm: slice $slice framework binary is not Mach-O" + fi + + actual_archs="$(lipo -archs "$slice_binary" 2>/dev/null \ + | tr ' ' '\n' | sed '/^$/d' | LC_ALL=C sort | tr '\n' ' ' | sed 's/ $//' || true)" + expected_archs="${expected_slice_archs[$slice]}" + print -r -- "$actual_archs" > "$diagnostics/framework-$tfm-$slice-archs.txt" + if [[ "$actual_archs" == "$expected_archs" ]]; then + pass "$tfm: slice $slice architectures = $expected_archs" + else + fail "$tfm: slice $slice architectures = '$actual_archs', expected '$expected_archs'" + fi + done + else + fail "$tfm: $xcframework/Info.plist is missing" + fi + + manifest="$payload_root/manifest" + if [[ -f "$manifest" ]]; then + cp "$manifest" "$diagnostics/native-manifest-$tfm.xml" + check_manifest_value "$manifest" "$tfm" Kind "$expected_kind" + check_manifest_value "$manifest" "$tfm" SmartLink "$expected_smartlink" + check_manifest_value "$manifest" "$tfm" ForceLoad "$expected_forceload" + check_manifest_value "$manifest" "$tfm" Frameworks "$expected_frameworks" + check_manifest_value "$manifest" "$tfm" LinkerFlags "$expected_linkerflags" + else + fail "$tfm: native reference manifest is missing" + fi + + upstream_bundle="$framework_root/ios-arm64/GooglePlaces.framework/Resources/$resource_bundle" + packaged_bundle="$work/pkg/build/$resource_bundle" + if [[ ! -d "$upstream_bundle" || ! -d "$packaged_bundle" ]]; then + [[ -d "$upstream_bundle" ]] || fail "$tfm: upstream $resource_bundle is missing from the device slice" + [[ -d "$packaged_bundle" ]] || fail "build/$resource_bundle is missing from the package" + continue + fi + + upstream_manifest="$diagnostics/upstream-bundle-$tfm.txt" + packaged_manifest="$diagnostics/packaged-bundle-$tfm.txt" + bundle_diff="$diagnostics/bundle-files-$tfm.diff" + content_mismatches="$diagnostics/bundle-content-$tfm.txt" + write_file_manifest "$upstream_bundle" "$upstream_manifest" + write_file_manifest "$packaged_bundle" "$packaged_manifest" + + if diff -u "$upstream_manifest" "$packaged_manifest" > "$bundle_diff"; then + pass "$tfm: packaged bundle file set matches the embedded SDK" + else + fail "$tfm: packaged bundle file set differs from the embedded SDK" + fi + + : > "$content_mismatches" + while IFS= read -r relative_path; do + if [[ ! -f "$packaged_bundle/$relative_path" ]] || ! cmp -s "$upstream_bundle/$relative_path" "$packaged_bundle/$relative_path"; then + print -r -- "$relative_path" >> "$content_mismatches" + fi + done < "$upstream_manifest" + if [[ -s "$content_mismatches" ]]; then + fail "$tfm: packaged bundle contents differ from the embedded SDK" + else + pass "$tfm: packaged bundle contents match the embedded SDK byte-for-byte" + fi +done + +echo +echo "Resource bundle" +packaged_bundle="$work/pkg/build/$resource_bundle" +if [[ -d "$packaged_bundle" ]]; then + actual_count="$(find "$packaged_bundle" -type f | wc -l | tr -d ' ')" + if [[ "$actual_count" == "$expected_bundle_files" ]]; then + pass "build/$resource_bundle contains $actual_count files" + else + fail "build/$resource_bundle contains $actual_count files, expected $expected_bundle_files" + fi +else + fail "build/$resource_bundle is missing from the package" +fi +if [[ -e "$work/pkg/buildTransitive/$resource_bundle" ]]; then + fail "buildTransitive unexpectedly contains a second $resource_bundle" +else + pass "buildTransitive does not duplicate $resource_bundle" +fi + +echo +echo "MSBuild integration" +check_packaged_targets() { + local folder="$1" source="$2" packaged + packaged="$work/pkg/$folder/$package_id.targets" + if [[ ! -f "$packaged" ]]; then + fail "$folder/$package_id.targets is missing" + return + fi + if cmp -s "$source" "$packaged"; then + pass "$folder/$package_id.targets matches source" + else + diff -u "$source" "$packaged" > "$diagnostics/$folder-targets.diff" || true + fail "$folder/$package_id.targets differs from source" + fi +} +check_packaged_targets build "$source_build_targets" +check_packaged_targets buildTransitive "$source_transitive_targets" + +transitive_targets="$work/pkg/buildTransitive/$package_id.targets" +expected_import='' +if [[ -f "$transitive_targets" ]] && grep -Fq "$expected_import" "$transitive_targets"; then + pass "buildTransitive imports the primary build target" +else + fail "buildTransitive does not import the primary build target" +fi + +echo +echo "XamarinBuildDownload removal" +if [[ -f "$nuspec" ]]; then + if grep -Eq "Xamarin\.Build\.Download|XamarinBuildDownload" "$nuspec"; then + fail "nuspec still references Xamarin.Build.Download" + else + pass "nuspec has no Xamarin.Build.Download dependency" + fi +fi + +packaged_build_assets="$diagnostics/packaged-msbuild-assets.txt" +find "$work/pkg" -type f \( -name '*.props' -o -name '*.targets' \) -print \ + | LC_ALL=C sort > "$packaged_build_assets" + +xbd_hits="$diagnostics/packaged-xbd-references.txt" +: > "$xbd_hits" +while IFS= read -r packaged_asset; do + asset_matches="$(grep -En "Xamarin\.Build\.Download|XamarinBuildDownload" "$packaged_asset" || true)" + if [[ -n "$asset_matches" ]]; then + print -r -- "${packaged_asset#$work/pkg/}" >> "$xbd_hits" + print -r -- "$asset_matches" >> "$xbd_hits" + fi +done < "$packaged_build_assets" +if [[ -s "$xbd_hits" ]]; then + fail "packaged MSBuild assets still reference Xamarin.Build.Download" +else + pass "all packaged MSBuild assets are free of Xamarin.Build.Download" +fi + +for source_file in "${source_files[@]}"; do + if grep -Eq "Xamarin\.Build\.Download|XamarinBuildDownload" "$source_file"; then + fail "${source_file#$repo_root/} still references Xamarin.Build.Download" + else + pass "${source_file#$repo_root/} is free of Xamarin.Build.Download" + fi +done + +echo +if (( failures > 0 )); then + echo "$failures structural check(s) failed for $package_id." >&2 + exit 1 +fi + +completed="true" +echo "All structural checks passed for $package_id $package_version."