Skip to content

Commit ac8faf7

Browse files
Add Android Tools CI stage and merge follow-ups
Follow-up adjustments after merging dotnet/android-tools in-tree under external/xamarin-android-tools/: - .gitmodules: drop the external/xamarin-android-tools submodule entry - build-tools/scripts/XAVersionInfo.targets: remove the now-dead _GetSubmodulesVersionInfo plumbing (no submodules contribute version info anymore; Java.Interop and xamarin-android-tools are both in-tree) - Xamarin.Android.Build.Tasks.targets: @XAMARIN_ANDROID_TOOLS_COMMIT@ is now the literal "in-tree" (mirrors @JAVA_INTEROP_COMMIT@) - Add stage-xamarin-android-tools-tests.yaml mirroring the upstream android-tools build + unit-test jobs, wired into both azure-pipelines.yaml (1ES) and azure-pipelines-public.yaml. In-tree, external/xamarin-android-tools.override.props imports dotnet/android's root Directory.Build.props (pinning DotNetTargetFrameworkVersion=11.0 and AndroidToolsDisableMultiTargeting=true), so the stage forces -p:AndroidToolsDisableMultiTargeting=false -p:DotNetTargetFrameworkVersion=10.0 to reproduce upstream's standalone netstandard2.0 + net10.0 test configuration (its ProcessUtils tests assert ProcessStartInfo.ArgumentList, which only exists on the NET5_0_OR_GREATER build) - Delete unwired infra under external/xamarin-android-tools/ (.github, .vscode, azure-pipelines.yaml, Localize/onelocbuild.yaml, docs/CODE_REVIEW_POSTMORTEM.md, *.code-workspace, duplicate SECURITY.md and CODE-OF-CONDUCT.md) - Merge unique android-tools reviewer guidance into the in-tree android-reviewer skill + copilot-instructions.md - Normalize line endings of the in-tree files per dotnet/android's .gitattributes (EOL-only; no content changes) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 1814cfa commit ac8faf7

50 files changed

Lines changed: 445 additions & 4815 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/copilot-instructions.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@
1818
- **Run tests:** `dotnet-local.cmd test bin/TestDebug/net9.0/Xamarin.Android.Build.Tests.dll --filter Name~TestName`
1919
- **Device tests:** `dotnet-local.cmd test bin/TestDebug/MSBuildDeviceIntegration/net9.0/MSBuildDeviceIntegration.dll`
2020

21+
### external/xamarin-android-tools/
22+
23+
The in-tree Android tools libraries include SDK/JDK discovery (`AndroidSdkInfo`, `JdkInfo`, SDK manifest parsing, `AdbRunner`, `EmulatorRunner`) and MSBuild task infrastructure (`AndroidTask`, `AndroidToolTask`, `AsyncTask`, `Files`, `ProcessUtils`, `FileUtil`, `MemoryStreamPool`). They target `netstandard2.0` and/or modern .NET, so verify API availability across all target frameworks before using newer BCL APIs. Prefer `ANDROID_HOME` for new Android SDK environment handling; `ANDROID_SDK_ROOT` is deprecated and should only remain for compatibility.
24+
25+
Useful focused checks:
26+
```sh
27+
dotnet build external/xamarin-android-tools/Xamarin.Android.Tools.sln
28+
dotnet test external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj
29+
dotnet test external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj
30+
```
31+
2132
## Critical Rules
2233

2334
**Never use `git commit --amend`:** Always create new commits. The user will squash or fixup as needed.

.github/skills/android-reviewer/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ description: >-
99

1010
# Android PR Reviewer
1111

12-
Review PRs against guidelines distilled from past reviews by senior maintainers of dotnet/android.
12+
Review PRs against guidelines distilled from past reviews by senior maintainers of dotnet/android, including the in-tree shared tooling under `external/xamarin-android-tools/`.
1313

1414
## Review Mindset
1515

@@ -70,7 +70,7 @@ Based on the file types identified in step 2, read the appropriate rule files fr
7070

7171
**Conditionally load based on changed file types:**
7272
- `references/csharp-rules.md` — When any `.cs` files changed. Covers nullable, async, error handling, performance, and code organization.
73-
- `references/msbuild-rules.md` — When `.targets`, `.props`, `.projitems`, or `.csproj` files changed, or when MSBuild task C# files changed (e.g., files under `src/Xamarin.Android.Build.Tasks/`).
73+
- `references/msbuild-rules.md` — When `.targets`, `.props`, `.projitems`, or `.csproj` files changed, or when MSBuild task C# files changed (e.g., files under `src/Xamarin.Android.Build.Tasks/` or `external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/`).
7474
- `references/native-rules.md` — When `.c`, `.cpp`, `.h`, or `.hpp` files changed. Covers memory management, C++ best practices, symbol visibility, and platform-specific code.
7575
- `references/interop-rules.md` — When both C# and native files changed, when the diff contains P/Invoke or JNI interop code (e.g., `DllImport`, `[Register]` attribute changes, `JNIEnv` calls, `[MarshalAs]`, `[StructLayout]`, `JniObjectReference`, `JniPeerMembers`, `JniTransition`), or when files under `external/Java.Interop/`, `src/Mono.Android/`, or `src/native/` changed.
7676
- `references/testing-rules.md` — When test files changed (e.g., files under `tests/`, `**/Tests/`, or test project directories).

.github/skills/android-reviewer/references/csharp-rules.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ General C# guidance applicable to any .NET repository.
44

55
---
66

7+
## Target Framework Compatibility
8+
9+
| Check | What to look for |
10+
|-------|-----------------|
11+
| **Oldest TFM must compile** | Code under `external/xamarin-android-tools/` may target `netstandard2.0` and modern .NET. Verify every API and overload against the oldest target framework; common traps include cancellation-token overloads such as `HttpContent.ReadAsStringAsync(CancellationToken)`, modern `ProcessStartInfo.ArgumentList` usage without the existing fallback helpers, and newer language/BCL features that need `#if` guards or polyfills. |
12+
| **Prefer existing compatibility helpers** | Use repository helpers such as `ProcessUtils`, `FileUtil`, and nullable extension methods instead of direct modern-BCL calls when they provide `netstandard2.0` fallbacks or better annotations. |
13+
14+
---
15+
716
## Nullable Reference Types
817

918
| Check | What to look for |
@@ -53,6 +62,8 @@ General C# guidance applicable to any .NET repository.
5362
|-------|-----------------|
5463
| **Avoid unnecessary allocations** | Don't create intermediate collections when LINQ chaining or a single list would do. Char arrays for `string.Split()` should be `static readonly` fields. |
5564
| **ArrayPool for large buffers** | Buffers ≥ 1 KB should use `ArrayPool<byte>.Shared.Rent()` with `try`/`finally` return. Large allocations go to the LOH and are expensive to GC. |
65+
| **Use existing pools for repeated streams/buffers** | In Android tools code, prefer existing pooling helpers such as `MemoryStreamPool`/`ObjectPool<T>` for repeated temporary streams or buffers instead of allocating new instances in hot paths. If mutable buffers are reused with single-caller assumptions, document the thread-safety invariant. |
66+
| **Static `HttpClient`** | `HttpClient` instances should be `static readonly` fields, not per-instance objects that are repeatedly constructed or disposed. Per-instance clients can exhaust sockets; inject clients only when there is a real caller requirement. |
5667
| **`HashSet.Add()` already handles duplicates** | Calling `.Contains()` before `.Add()` does the hash lookup twice. Just call `.Add()`. (Postmortem `#41`) |
5768
| **Don't wrap a value in an interpolated string** | `$"{someString}"` creates an unnecessary `string.Format` call when `someString` is already a string. (Postmortem `#42`) |
5869
| **Consider allocations when choosing types** | `Stopwatch` is heap-allocated; `DateTime`/`ValueStopwatch` is a struct. On hot paths or startup, prefer value types. (Postmortem `#39`) |

.github/skills/android-reviewer/references/repo-conventions.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,13 @@ and merge conflicts.
4040
| Check | What to look for |
4141
|-------|-----------------|
4242
| **Use existing utilities** | Check `MonoAndroidHelper`, `FileUtil`, `PathUtil`, `ITaskItemExtensions`, and other utilities before writing new helpers. Duplicating existing logic is the most expensive AI pattern. |
43+
| **Use Android tools utilities** | In `external/xamarin-android-tools/`, process execution should go through `ProcessUtils`, file extraction/download/checksum/path helpers through `FileUtil`, and repeated buffers/streams through `ObjectPool<T>` or `MemoryStreamPool` where applicable. |
4344
| **`Log.LogDebugMessage` for diagnostics** | Use `Log.LogDebugMessage(…)` for verbose/debug output, not `Console.WriteLine` or `Debug.WriteLine`. Don't spam logcat with messages that fire on every type lookup miss. (Postmortem `#9`) |
45+
| **Android tools logger delegate** | SDK/JDK discovery helpers in `external/xamarin-android-tools/` commonly use `Action<TraceLevel, string>? logger` (see `AndroidSdkInfo.DefaultConsoleLogger`) so callers can route diagnostics into MSBuild or IDE logs. Don't replace this with `Console.WriteLine` or `Debug.WriteLine`. |
4446
| **Return `IReadOnlyList<T>`** | Public methods should return `IReadOnlyList<T>` or `IReadOnlyCollection<T>` instead of mutable `List<T>`. |
4547
| **Prefer C# pattern matching** | Use `is`, `switch` expressions, and property patterns instead of `if`/`else` type-check chains. |
4648
| **Structured args, not string interpolation** | Process arguments should be `IEnumerable<string>` or use `ArgumentList`, not a single interpolated string. |
49+
| **Android SDK environment variables** | In `external/xamarin-android-tools/`, use `EnvironmentVariableNames.AndroidHome`/`ANDROID_HOME` for new SDK-root behavior. `ANDROID_SDK_ROOT` is deprecated by Android and should only be read for backward compatibility. |
4750
| **Method names must reflect behavior** | If `CreateFoo()` sometimes returns an existing instance, rename it `GetOrCreateFoo()` or `GetFoo()`. (Postmortem `#4`) |
4851
| **Choose collision-proof names** | Types and constants that could collide with user code or Android concepts need disambiguating prefixes (e.g., `__Xamarin.Android.Resource.Designer` with a `__` prefix). (Postmortem `#2`) |
4952
| **Don't assume transitive assembly references** | An assembly containing an `Activity` subclass does not necessarily reference `Mono.Android.dll` directly — the reference may be transitive. Skipping assemblies based on direct reference checks can break user code. (Postmortem `#64`) |
@@ -93,6 +96,7 @@ and merge conflicts.
9396
| Check | What to look for |
9497
|-------|-----------------|
9598
| **XmlReader over LINQ XML** | For forward-only XML parsing (manifests, config files), prefer `XmlReader` — it's streaming and allocation-free. `XElement`/`XDocument` builds a full DOM tree. |
99+
| **SDK manifest parsing stays streaming** | Android SDK repository manifests can be large; keep `external/xamarin-android-tools/` manifest parsing on streaming `XmlReader`-style paths unless there is measured evidence a DOM is acceptable. |
96100
| **p/invoke over process spawn** | For single syscalls like `chmod`, use `[DllImport("libc")]` instead of spawning a child process. Process creation is orders of magnitude more expensive. |
97101
| **Use `Files.CopyIfStringChanged()`** | Don't write to a file if the content hasn't changed — it breaks incremental builds by updating timestamps. (Postmortem `#53`) |
98102
| **Don't remove caches without measurement** | If a cache (like `TypeDefinitionCache`) had a measured perf win, removing it requires proving the replacement provides equivalent caching. (Postmortem `#57`) |

.github/skills/android-reviewer/references/security-rules.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ I/O, archives, or process execution.
1111
|-------|-----------------|
1212
| **Zip Slip protection** | Archive extraction must validate that every entry path, after `Path.GetFullPath()`, resolves under the destination directory. Never use `ZipFile.ExtractToDirectory()` for untrusted archives without entry-by-entry validation. |
1313
| **Path traversal** | `StartsWith()` checks on paths must normalize with `Path.GetFullPath()` first. A path like `C:\Program Files\..\Users\evil` bypasses naive prefix checks. Also check for directory boundary issues (`C:\Program FilesX` matching `C:\Program Files`). |
14+
| **Mandatory checksum verification** | Downloads or archive installs in Android tools code must not proceed unverified when checksum/hash data is expected but missing or mismatched. Fail closed with an actionable error. |
1415

1516
---
1617

.github/skills/android-reviewer/references/testing-rules.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Guidance for test code. The repo-specific conventions (e.g., `BaseTest`,
1616
| **Test assertions must be specific** | `Assert.IsNotNull(result)` or `Assert.IsTrue(success)` don't tell you what went wrong. Prefer `Assert.AreEqual(expected, actual)` or NUnit constraints (`Assert.That` with `Does.Contain`, `Is.EqualTo`, etc.) for richer failure messages. |
1717
| **Deterministic test data** | Tests should not depend on system locale, timezone, or current date. Use explicit `CultureInfo.InvariantCulture` and hardcoded dates when testing formatting. |
1818
| **Test edge cases** | Empty collections, null inputs, boundary values, concurrent calls, and very large inputs should all be considered. If the PR only tests the happy path, suggest edge cases. |
19+
| **Android tools SDK/JDK fixtures** | Tests under `external/xamarin-android-tools/tests/` commonly build isolated fake SDK/JDK layouts and platform-specific tool scripts (`.bat` on Windows, shell scripts on Unix). Keep these fixtures self-contained and cleaned up in setup/teardown rather than depending on the developer machine's installed SDK/JDK. |
1920
| **Generator tests must include Invoker types** | Tests for generated binding code (under `external/Java.Interop/tools/generator/` and related test projects) should verify both the interface/class output and the `*Invoker` type behavior. Invoker codegen has historically had subtle bugs with default interface methods and virtual dispatch. |
2021
| **JVM-dependent tests** | Tests that require a running JVM must be in projects that configure the JVM environment (e.g., `Java.Interop-Tests`). Verify that test classes requiring a JVM are not placed in unit-test-only projects, where they will silently skip or fail with obscure errors. |
2122
| **Expected codegen output tests** | Generator tests that compare against expected output files should be updated when the expected format changes. Stale expected-output files cause spurious test failures that mask real regressions. |

.gitmodules

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@
2222
path = external/robin-map
2323
url = https://github.com/xamarin/robin-map
2424
branch = master
25-
[submodule "external/xamarin-android-tools"]
26-
path = external/xamarin-android-tools
27-
url = https://github.com/dotnet/android-tools
28-
branch = main
2925
[submodule "external/xxHash"]
3026
path = external/xxHash
3127
url = https://github.com/Cyan4973/xxHash.git

build-tools/automation/azure-pipelines-public.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,20 @@ stages:
429429
- ImageOverride -equals ACES_VM_SharedPool_Tahoe
430430
os: macOS
431431

432+
# Android Tools Tests Stage
433+
- template: /build-tools/automation/yaml-templates/stage-xamarin-android-tools-tests.yaml@self
434+
parameters:
435+
windowsPool:
436+
name: $(NetCorePublicPoolName)
437+
demands:
438+
- ImageOverride -equals $(WindowsPoolImageNetCorePublic)
439+
os: windows
440+
macPool:
441+
name: AcesShared
442+
demands:
443+
- ImageOverride -equals ACES_VM_SharedPool_Tahoe
444+
os: macOS
445+
432446
# MAUI Tests Stage
433447
- stage: maui_tests
434448
displayName: MAUI Tests

build-tools/automation/azure-pipelines.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,17 @@ extends:
110110
image: $(WindowsPoolImage1ESPT)
111111
os: windows
112112

113+
- template: /build-tools/automation/yaml-templates/stage-xamarin-android-tools-tests.yaml@self
114+
parameters:
115+
# The internal Xamarin.Android pipeline uses 1ES Pipeline Templates, which
116+
# require Windows jobs to run on a 1ES-hosted pool. macOS is fine on the
117+
# hosted Azure Pipelines pool as long as `os: macOS` is set (which is now
118+
# the template default).
119+
windowsPool:
120+
name: MAUI-1ESPT
121+
image: $(WindowsPoolImage1ESPT)
122+
os: windows
123+
113124
- stage: maui_tests
114125
displayName: MAUI Tests
115126
dependsOn: mac_build
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# xamarin-android-tools tests stage
2+
#
3+
# Mirrors the build + test jobs from the upstream dotnet/android-tools
4+
# azure-pipelines.yaml so that the Xamarin.Android.Tools build + unit tests
5+
# continue to run as part of dotnet/android CI now that android-tools has been
6+
# merged in-tree under external/xamarin-android-tools/.
7+
#
8+
# The upstream pipeline also packed a NuGet and published build artifacts;
9+
# those are intentionally dropped here (the binaries aren't consumed from this
10+
# stage -- dotnet/android's own build produces the shipping assemblies). Only
11+
# logs + test results are surfaced.
12+
#
13+
# Referenced from both:
14+
# - build-tools/automation/azure-pipelines.yaml (official / 1ES)
15+
# - build-tools/automation/azure-pipelines-public.yaml (public PR validation)
16+
17+
parameters:
18+
- name: stageName
19+
type: string
20+
default: xamarin_android_tools_tests
21+
- name: dependsOn
22+
type: object
23+
default: []
24+
- name: condition
25+
type: string
26+
default: succeeded()
27+
- name: windowsPool
28+
type: object
29+
# NOTE: This default is a Microsoft-hosted Azure Pipelines pool, which is NOT
30+
# allowed by 1ES Pipeline Templates in Official mode. Callers that extend the
31+
# 1ES Official template (e.g. build-tools/automation/azure-pipelines.yaml) must
32+
# override this with a 1ES-hosted pool like `MAUI-1ESPT`.
33+
default:
34+
name: Azure Pipelines
35+
vmImage: $(HostedWinImage)
36+
os: windows
37+
- name: macPool
38+
type: object
39+
default:
40+
name: Azure Pipelines
41+
vmImage: $(HostedMacImage)
42+
os: macOS
43+
44+
stages:
45+
- stage: ${{ parameters.stageName }}
46+
displayName: Android Tools Tests
47+
dependsOn: ${{ parameters.dependsOn }}
48+
condition: ${{ parameters.condition }}
49+
variables:
50+
DotNetCoreVersion: 10.0.x
51+
XatSourceDirectory: $(System.DefaultWorkingDirectory)/external/xamarin-android-tools
52+
# When built in-tree, external/xamarin-android-tools.override.props imports
53+
# dotnet/android's root Directory.Build.props, which pins
54+
# DotNetTargetFrameworkVersion=11.0 and sets AndroidToolsDisableMultiTargeting=true
55+
# (so the product build gets a single netstandard2.0 assembly). Upstream
56+
# dotnet/android-tools CI instead builds standalone: multi-targeting
57+
# netstandard2.0 + net10.0, with the test projects running on net10.0. Its
58+
# ProcessUtils tests assert ProcessStartInfo.ArgumentList, which only exists on
59+
# the net10.0 (NET5_0_OR_GREATER) build -- the netstandard2.0 build takes the
60+
# string-Arguments path. To reproduce upstream's green test configuration we
61+
# re-enable multi-targeting and force net10.0 via these global properties.
62+
XatBuildProperties: -p:AndroidToolsDisableMultiTargeting=false -p:DotNetTargetFrameworkVersion=10.0
63+
jobs:
64+
65+
# Check - "Xamarin.Android (Android Tools Tests Windows - .NET)"
66+
- job: xamarin_android_tools_windows
67+
displayName: Windows - .NET
68+
pool: ${{ parameters.windowsPool }}
69+
timeoutInMinutes: 30
70+
workspace:
71+
clean: all
72+
steps:
73+
- checkout: self
74+
clean: true
75+
76+
- task: UseDotNet@2
77+
displayName: Use .NET Core $(DotNetCoreVersion)
78+
inputs:
79+
version: $(DotNetCoreVersion)
80+
81+
- task: DotNetCoreCLI@2
82+
displayName: Build Xamarin.Android.Tools.sln
83+
inputs:
84+
command: build
85+
projects: $(XatSourceDirectory)/Xamarin.Android.Tools.sln
86+
arguments: -c Debug $(XatBuildProperties) -bl:$(Build.ArtifactStagingDirectory)/build-windows.binlog
87+
88+
- task: DotNetCoreCLI@2
89+
displayName: Run Tests
90+
inputs:
91+
command: test
92+
projects: $(XatSourceDirectory)/bin/TestDebug/**/*-Tests.dll
93+
94+
- task: PublishBuildArtifacts@1
95+
displayName: Upload build logs
96+
condition: always()
97+
inputs:
98+
pathToPublish: $(Build.ArtifactStagingDirectory)
99+
artifactName: android-tools-logs-windows
100+
101+
# Check - "Xamarin.Android (Android Tools Tests Mac - .NET)"
102+
- job: xamarin_android_tools_mac
103+
displayName: Mac - .NET
104+
pool: ${{ parameters.macPool }}
105+
timeoutInMinutes: 30
106+
workspace:
107+
clean: all
108+
steps:
109+
- checkout: self
110+
clean: true
111+
112+
- task: UseDotNet@2
113+
displayName: Use .NET Core $(DotNetCoreVersion)
114+
inputs:
115+
version: $(DotNetCoreVersion)
116+
117+
- task: DotNetCoreCLI@2
118+
displayName: Build Xamarin.Android.Tools.sln
119+
inputs:
120+
command: build
121+
projects: $(XatSourceDirectory)/Xamarin.Android.Tools.sln
122+
arguments: -c Debug $(XatBuildProperties) -bl:$(Build.ArtifactStagingDirectory)/build-mac.binlog
123+
124+
- task: DotNetCoreCLI@2
125+
displayName: Run Tests
126+
inputs:
127+
command: test
128+
projects: $(XatSourceDirectory)/bin/TestDebug/**/*-Tests.dll
129+
130+
- task: PublishBuildArtifacts@1
131+
displayName: Upload build logs
132+
condition: always()
133+
inputs:
134+
pathToPublish: $(Build.ArtifactStagingDirectory)
135+
artifactName: android-tools-logs-mac

0 commit comments

Comments
 (0)