Skip to content

Trial Nx affected/cache + per-project OS routing for monorepo builds#204

Draft
Redth wants to merge 21 commits into
mainfrom
redth/monorepo-build-strategy
Draft

Trial Nx affected/cache + per-project OS routing for monorepo builds#204
Redth wants to merge 21 commits into
mainfrom
redth/monorepo-build-strategy

Conversation

@Redth
Copy link
Copy Markdown
Member

@Redth Redth commented Apr 30, 2026

As maui-labs grows into a true monorepo (DevFlow, CLI, Linux GTK4, samples, integration tests), the per-product workflow split is starting to feel coarse: a touch in Directory.Packages.props rebuilds every product, and shared infra like eng/** or global.json invalidates everything regardless of what actually depends on it. This PR sets up a non-gating trial of an Nx + @nx/dotnet driven affected/cache pipeline alongside the existing CI so we can measure whether it pays off before committing to a migration.

Nothing here changes the existing ci-devflow, ci-cli, ci-linux-gtk4, or devflow-integration workflows — they remain the source of truth for green PRs. Everything new is opt-in telemetry.

What's in here

Nx workspace plumbing

  • Adds Nx 22.7.1 + @nx/dotnet 22.7.0 with checked-in nx / nx.bat wrappers and a small Node bootstrap (eng/nx/nx-msbuild-resolvers.js) that exports the MSBuild SDK resolver environment so @nx/dotnet's evaluator can find Microsoft.DotNet.Arcade.Sdk from the dnceng feeds.
  • Restricts nx.json targetDefaults to --no-restore (drops the inferred --no-dependencies), with restore added to build.dependsOn. The fallback was needed because no-op multi-TFM ProjectReferences fail GetTargetPath resolution under --no-dependencies.

Eliminated project.json files (7 of them). Most metadata that was hand-rolled into project.json is now inferred by @nx/dotnet from MSBuild itself, or expressed as real ProjectReference edges, or moved out of the graph entirely.

  • Skill checks (plugins/dotnet-maui, tests/dotnet-maui, .github/plugin) were already invoked directly by their own workflows; removed from the graph.
  • samples/DevFlow.Sample/ and Microsoft.Maui.Cli.UnitTests/ are now fully inferred.
  • Integration tests previously needed a project.json for per-platform test:android|ios|maccatalyst|windows targets. Replaced with four per-platform csprojs (Android/, iOS/, MacCatalyst/, Windows/) each importing a shared IntegrationTests.Common.props. Sources stay shared via <Compile Include="..\*.cs" />. Platform identity is stamped into each assembly via [AssemblyMetadata("DevFlowTestPlatform", ...)] so AppFixtureFactory no longer needs an env var. The Sample is built via a real (no-op) ProjectReference from each platform test project, which means the dependency edge appears in the Nx graph instead of needing implicitDependencies.

NxBuildableOn for self-declared OS routing. Each project owns where it can run via an MSBuild property:

<PropertyGroup>
  <NxBuildableOn>linux</NxBuildableOn>            <!-- Linux.Gtk4 family -->
  <NxBuildableOn>macos</NxBuildableOn>            <!-- iOS / MacCatalyst integration tests -->
  <NxBuildableOn>windows</NxBuildableOn>          <!-- Windows integration tests -->
  <!-- Default when unset: linux;macos;windows  -->
</PropertyGroup>

The new eng/nx/nx-os-tags.js (createNodesV2) reads it from the csproj first, then walks Directory.Build.props files up to the workspace root, and stamps os:<name> tags onto each project. Defaults to all three OSes plus an os:any umbrella tag, matching the "managed-only code can build anywhere" reality (an -ios class library still gets os:any even though it targets Apple APIs).

Two new workflows, both continue-on-error: true:

  • nx-affected-report.yml: lists the affected projects and ships a Linux cache canary using raegen/nx.
  • nx-build-shadow.yml: matrix on Linux, macOS, and Windows. Each runner does affected -t restore/build/test --projects=tag:os:${{ matrix.os-tag }}. No centralized exclusion list; the only special case is skipping platform integration tests because those need real devices/sims handled by devflow-integration.yml.

Verified locally

  • ./nx show projects returns 23 projects, all inferred (zero project.json files in the tree).
  • ./nx show projects --projects=tag:os:macos returns 19 (Apple ITs + every os:any).
  • ./nx run Microsoft.Maui.DevFlow.Agent.IntegrationTests.MacCatalyst:test builds the Sample for net10.0-maccatalyst via the no-op ProjectReference, launches the app, and runs 113 tests (111 pass; 2 unrelated UI-property flakes).
  • A Driver local cache canary correctly restored artifacts/bin/obj from cache after deletion.

Things to keep an eye on

  • raegen/nx remote cache behavior on actual GitHub runs is still untested; the shadow workflow exists specifically to surface that signal.
  • The tags plugin reads <NxBuildableOn> via regex, so values inside conditional <PropertyGroup Condition="..."> blocks are picked up regardless of the condition. If you need a value that depends on $(TargetFramework) etc., put it directly in the csproj where you have that context.
  • nx.json targetDefaults.build now omits --no-dependencies globally. Nx's ^build chain still orders things; MSBuild's incremental check makes the redundant resolution cheap, but worth watching on cold-cache CI runs.
  • Integration tests against Android, iOS, and Windows lanes have not been re-validated locally after the per-platform csproj split — only MacCatalyst was exercised end-to-end. The shadow workflow will be the first signal here.

Marking draft because this is a trial — happy to keep iterating based on what the first few CI runs surface.

Redth and others added 19 commits May 1, 2026 13:12
…d with no-op ProjectReference

- Add Nx non-JS workspace (nx, nx.bat, .nx/nxw.js, nx.json) with @nx/dotnet
- Add eng/nx/nx-msbuild-resolvers.js to bootstrap MSBuild SDK resolvers (Arcade)
- Add report-only .github/workflows/nx-affected-report.yml + raegen/nx canary
- Model hidden cross-tree edges via per-project project.json (DevFlow.Tests,
  Cli.UnitTests, plugins/dotnet-maui, tests/dotnet-maui, .github/plugin)
- DevFlow integration tests reference samples/DevFlow.Sample as a no-op
  ProjectReference (ReferenceOutputAssembly=false, SkipGetTargetFrameworkProperties=true,
  SetTargetFramework=...) gated by DevFlowIntegrationPlatform; sample is now built
  by MSBuild as a real dependency, so fixtures no longer call BuildSampleAsync
- Simplify devflow-integration.yml to invoke ./nx run :test:<platform>

Verified locally: ./nx run Microsoft.Maui.DevFlow.Agent.IntegrationTests:test:maccatalyst
builds DevFlow.Sample for net10.0-maccatalyst via the conditional ProjectReference
and runs 113 tests (112 pass; 1 pre-existing WebView screenshot 400 unrelated to build).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Only Microsoft.Maui.DevFlow.Agent.IntegrationTests/project.json remains —
it defines the per-platform test:<platform> targets that workflows invoke
via Nx (and which @nx/dotnet cannot infer).

Removed:
- samples/DevFlow.Sample/project.json: build:<platform> targets are obsolete
  now that integration tests pull the sample in via no-op ProjectReference;
  @nx/dotnet infers a build target from the csproj.
- src/Cli/Microsoft.Maui.Cli.UnitTests/project.json: cross-link to
  DevFlowAgentService.cs was speculative — no test references that file.
- src/DevFlow/Microsoft.Maui.DevFlow.Tests/project.json: cache-correctness
  inputs to Agent's buildTransitive/*.targets are already covered by the
  project graph (Tests -> Agent project ref).
- plugins/dotnet-maui/project.json + tests/dotnet-maui/project.json +
  .github/plugin/project.json: skills check/evaluate are run directly
  by skill-check.yml / skill-evaluation.yml workflows (which already
  invoke skill-validator binary), so they don't need to be in the Nx graph.

Verified: ./nx show projects lists 23 real projects; DevFlow.Sample keeps
inferred build/restore/clean/publish/run targets via @nx/dotnet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Goal: zero project.json files in the repo. Eliminates the last hand-written
Nx config wrapper around `dotnet test` per platform.

Restructure src/DevFlow/Microsoft.Maui.DevFlow.Agent.IntegrationTests/:

  Android/Microsoft.Maui.DevFlow.Agent.IntegrationTests.Android.csproj
  iOS/Microsoft.Maui.DevFlow.Agent.IntegrationTests.iOS.csproj
  MacCatalyst/Microsoft.Maui.DevFlow.Agent.IntegrationTests.MacCatalyst.csproj
  Windows/Microsoft.Maui.DevFlow.Agent.IntegrationTests.Windows.csproj
  IntegrationTests.Common.props      (shared MSBuild config)
  *.cs                                (shared test sources)
  Fixtures/*.cs

Each platform csproj is a 6-line wrapper that sets DevFlowSamplePlatform and
imports IntegrationTests.Common.props. The common props pulls in the test
sources via Compile globs reaching into the parent dir, references the
Driver, declares xunit/coverlet, stamps the platform identity into the
assembly via [AssemblyMetadata("DevFlowTestPlatform", ...)], and wires up
ONE unconditional no-op ProjectReference to DevFlow.Sample with
SetTargetFramework derived from the platform name. Each csproj sits in its
own subdirectory because @nx/dotnet keys project nodes by directory.

AppFixtureFactory now reads the platform from assembly metadata first,
falling back to DEVFLOW_TEST_PLATFORM env var (kept for ad-hoc overrides).

Nx now infers all four projects with their build/test targets directly from
MSBuild, and the project graph carries the real ProjectReference edge to
DevFlow.Sample (no implicitDependencies hack needed).

nx.json: drop --no-dependencies from the inferred build args because the
multi-TFM Sample ProjectReference fails GetTargetPath resolution under it;
add restore to the build dependsOn so MSBuild has assets before each build.

Workflow update: ./nx run ...IntegrationTests:test:<platform>
                 -> ./nx run ...IntegrationTests.<Platform>:test

Verified locally: ./nx run ...IntegrationTests.MacCatalyst:test runs 113
tests (111 pass; 2 UI flakes unrelated to build plumbing).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Non-gating GitHub Actions workflow that mirrors the existing per-product CI
across Linux/macOS/Windows but routes builds through Nx affected + raegen/nx
remote cache. Lets us measure cache hit rate and OS-by-OS work distribution
without disrupting the existing ci-devflow / ci-cli / ci-linux-gtk4 lanes.

- Single 'affected' job computes base/head once on Linux, exposes the count.
- Matrix build job runs on ubuntu-latest, macos-latest, windows-latest.
- Per-OS exclude lists keep each runner from attempting projects it can't
  build (Linux skips MAUI multi-target; macOS/Windows skip Linux.Gtk4 family;
  cross-OS integration test projects skipped on irrelevant lanes).
- Test step additionally skips platform integration tests (those still run
  via devflow-integration.yml with proper device/sim/emulator setup).
- continue-on-error everywhere; this is purely shadow telemetry today.

Hardcoded exclusion lists are a stopgap. Long-term answer is project tags
(os:linux/macos/windows/any) emitted by an inference plugin from each
csproj's TFMs, then `--projects=tag:os:${{ matrix.os }}`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the centralized exclusion list in nx-build-shadow.yml with a
per-project MSBuild property. Each project (or a Directory.Build.props in
its tree) declares where it can run:

  <PropertyGroup>
    <NxBuildableOn>linux;macos;windows</NxBuildableOn>  <!-- default -->
    <!-- or -->
    <NxBuildableOn>linux</NxBuildableOn>
    <NxBuildableOn>macos</NxBuildableOn>
  </PropertyGroup>

eng/nx/nx-os-tags.js (createNodesV2) reads the property from the csproj
itself, then walks Directory.Build.props files up to the workspace root,
defaulting to all three OSes if nothing declares otherwise. Each value
becomes an os:<name> tag, plus an os:any tag when all three are present
so workflows can do --projects=tag:os:linux,tag:os:any.

Project-level overrides:
- platforms/Linux.Gtk4/Directory.Build.props -> linux (libgtk-4-dev,
  libwebkitgtk-6.0-dev only ship on Linux)
- IntegrationTests.iOS/MacCatalyst -> macos (need real Apple sims/devices
  for the tests, even though the assembly itself is managed-only)
- IntegrationTests.Windows -> windows (needs a Windows host)
- IntegrationTests.Android -> default (Android emulator works on any host)
- Everything else -> default (managed-only builds run anywhere with
  workloads, including iOS/macOS class libraries on Windows)

Workflow change: nx-build-shadow.yml now uses
  --projects=tag:os:${{ matrix.os-tag }}
instead of a hand-maintained exclude list. To onboard a project with a
host constraint in future, add one MSBuild line; nothing else changes.

Verified:
  ./nx show projects --projects=tag:os:linux  -> 23 (Gtk4 family + Any)
  ./nx show projects --projects=tag:os:macos  -> 19 (Apple ITs + Any)
  ./nx show projects --projects=tag:os:windows -> 18 (Windows IT + Any)
  ./nx show projects --projects=tag:os:any    -> 17 (defaults only)

Limitation documented in the plugin: the regex doesn't evaluate MSBuild
conditions, so put NxBuildableOn directly in the csproj when the value
needs to depend on a property like $(TargetFramework).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the hand-rolled JavaScript Nx plumbing with the experimental
DotnetNx packages from Redth/Maui.BuildHelpers:

- Removes eng/nx/nx-msbuild-resolvers.js (regex-based MSBuild SDK resolver
  bootstrap). nxdn computes the same environment via Microsoft.Build.Locator.
- Removes eng/nx/nx-os-tags.js (regex-based <NxBuildableOn> tag emitter).
  Replaced by @redth/dotnet-nx Nx plugin, which delegates to
  'nxdn project-metadata' for full MSBuild evaluation. This honours
  conditional PropertyGroups and imported props that the regex could not.
- Rewrites nx and nx.bat as thin shims around 'dotnet nxdn nx --'.
- Adds .config/dotnet-tools.json pinning DotnetNx.Tool 0.1.0-local.
- Vendors eng/nx/redth-dotnet-nx-0.1.0-alpha.0.tgz (npm plugin not yet
  published to npmjs; lives on GitHub Packages under @Redth scope).
- Adds scripts/nxdn(.cmd) shims so the npm plugin can locate the dotnet
  local tool via DOTNET_NX_NXDN.

Verified locally: ./nx show projects returns 26 projects, OS tag filtering
(tag:os:macos|windows|linux|any) produces the expected per-host subsets.

Note: this is a local-only trial. CI workflows
(.github/workflows/nx-affected-report.yml, nx-build-shadow.yml,
devflow-integration.yml) still reference the deleted JS files via
'node eng/nx/nx-msbuild-resolvers.js --export-github-env' and will need
to be updated to call 'dotnet nxdn export-env --format github' (or the
setup-nxdn composite action from Maui.BuildHelpers) before this can be
adopted on CI. Adoption also depends on DotnetNx.Tool and @redth/dotnet-nx
being published to feeds CI can restore from.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Removes the last of the bespoke Nx plumbing in favour of nxdn and the
composite actions from Redth/Maui.BuildHelpers/DotnetNx:

- Deleted nx and nx.bat checked-in wrappers; devs and CI now invoke Nx
  exclusively through 'dotnet nxdn nx -- ...' (or 'nxdn nx -- ...' once
  DotnetNx.Tool is on PATH globally via the setup-nxdn action).
- Deleted .nx/nxw.js and removed nx.json's 'installation' block. Nx is
  resolved through the standard package.json devDependency at
  node_modules/.bin/nx, which nxdn falls through to automatically.
- Rewrote .github/workflows/nx-affected-report.yml and nx-build-shadow.yml
  to use Redth/Maui.BuildHelpers/DotnetNx/actions/{setup-nxdn,
  affected-info,run-affected} composite actions, replacing the inline
  setup-dotnet/setup-node/npm-ci/resolver-export/raegen-nx steps.
- Updated devflow-integration.yml to call setup-nxdn per platform job
  and run integration tests via 'nxdn nx -- run <project>:test'. Windows
  job switches from pwsh + .\nx.bat to bash + nxdn.

Action references are pinned to commit
ab2fef3e080396a22b6fc3b6d1a9a9eb3c67b701 of Redth/Maui.BuildHelpers.

Note: setup-nxdn currently runs 'dotnet tool update --global DotnetNx.Tool'
and assumes the package is reachable from a default NuGet feed. Until
DotnetNx.Tool is published to nuget.org (or this repo's NuGet.config is
amended to include Redth's GitHub Packages feed), CI runs of these
workflows will fail at the tool-install step. Local dev keeps working via
the .config/dotnet-tools.json manifest committed earlier.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Removes the eng/nx/redth-dotnet-nx-0.1.0-alpha.0.tgz vendored copy. The
plugin is now declared as a normal devDependency at version
'0.1.0-alpha.0' and resolved through GitHub Packages via a new .npmrc:

  @Redth:registry=https://npm.pkg.github.com
  always-auth=true

package-lock.json was hand-edited to swap the file: resolution for the
GitHub Packages tarball URL and to drop the now-stale integrity hash.
The first authenticated 'npm install' after the package is published
will repopulate the integrity field.

Same caveat as the DotnetNx.Tool migration: until Redth runs
publish-dotnetnx.yml on Maui.BuildHelpers, neither local nor CI
'npm ci'/'npm install' can resolve @redth/dotnet-nx. Existing
node_modules/ directories continue to work because the plugin is
already extracted on disk.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@redth/dotnet-nx and DotnetNx.Tool are now published to GitHub Packages
on Redth/Maui.BuildHelpers, and the composite actions have a v0.1 tag.

Workflow / action updates:
- Bumped every composite-action ref from the SHA pin to @v0.1.
- Replaced the inline 'nxdn nx -- run <project>:test' calls in the iOS,
  MacCatalyst, and Windows integration jobs with the new
  Redth/Maui.BuildHelpers/DotnetNx/actions/run-target@v0.1 action.
  Android keeps the inline call because it must execute inside
  reactivecircus/android-emulator-runner's script (the emulator is only
  alive within that step).
- Added .github/actions/auth-gh-packages as a local composite action
  that runs setup-dotnet + setup-node, configures NuGet credentials for
  the Redth-DotnetNx feed via 'dotnet nuget add source --store-password-
  in-clear-text' (user-level NuGet.Config), writes ~/.npmrc with an
  _authToken for npm.pkg.github.com, and runs 'npm ci'. This lives at
  user level so nothing sensitive lands in the repo.
- Every Nx/integration workflow now declares 'permissions: contents:
  read, packages: read' and calls the auth action before setup-nxdn.
  setup-nxdn is invoked with 'setup-node: false' since the auth action
  has already provisioned Node.

Package wiring:
- .config/dotnet-tools.json: 0.1.0-local -> 0.1.0-alpha.1.
- package.json + package-lock.json: @redth/dotnet-nx file:reference -> 0.1.0-alpha.1
  resolved through https://npm.pkg.github.com.
- NuGet.config gains the Redth-DotnetNx feed declaration (no creds; CI
  creds are written at runtime, devs use ~/.nuget/NuGet/NuGet.Config).
- .npmrc declares '@Redth:registry=https://npm.pkg.github.com'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
v0.2 adds 'env' (multiline KEY=VALUE) and 'script' (Bash setup sourced
before nxdn) inputs on run-target and run-affected. setup-nxdn and
affected-info are unchanged. No call sites need new inputs today, but
floating to v0.2 lets us compose per-step env later without falling
back to extra 'run:' blocks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Marketplace search found no comprehensive 'GH Packages auth' action that
covers both npm and NuGet, but the official actions/setup-node@v6 has
first-class support for scoped GH Packages registries via 'registry-url'
+ 'scope' + NODE_AUTH_TOKEN. That is strictly better than us hand-rolling
~/.npmrc.

The auth-gh-packages composite now passes registry-url and scope to
setup-node and supplies NODE_AUTH_TOKEN to 'npm ci' instead of writing
.npmrc lines itself. The NuGet side keeps 'dotnet nuget add source ...
--store-password-in-clear-text' at user-level, which is the canonical
pattern (no widely-used marketplace action exists for GH Packages NuGet
feeds today).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bumps every Redth/Maui.BuildHelpers/DotnetNx/actions/* pin from @v0.2
to @v0.3 and folds in three of the four new composites:

- affected-matrix replaces nx-build-shadow's static 3-OS matrix with a
  dynamic matrix derived from per-OS affected sets. The 'affected' job
  now emits 'matrix' + 'has-work' outputs; the 'build' job uses
  matrix: ${{ fromJSON(needs.affected.outputs.matrix) }} and only
  fans out the runners that have affected work. A docs-only PR spawns
  zero build legs instead of three.

- setup-cache is added to every build leg (nx-build-shadow.yml 'build'
  matrix and nx-affected-report.yml 'cache-canary' job). It caches
  .nx/cache + artifacts/bin + artifacts/obj keyed on the hashes of
  csproj/props/targets/global.json/Directory.Packages.props/nx.json/
  package.json.

- doctor is added to nx-affected-report.yml's 'report' job so
  'nxdn diagnose' output lands in the workflow step summary for free.

configure-nx is intentionally skipped: our nx.json already commits the
plugin entries, so we don't need bootstrap-time configuration.

Also collapses the per-OS install_workloads / native_deps matrix
columns into 'if: matrix.osTag == ...' guards now that affected-matrix
defines the matrix shape.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…auth

GitHub Packages requires auth for public feeds (only ghcr.io ever got
anonymous reads on the GH Packages stack), so we can't avoid the
credential plumbing entirely - but we can replace the bespoke composite
with two stock mechanisms.

NuGet auth - workflow-level env var:
NuGet honors NuGetPackageSourceCredentials_<SourceName> with the form
'Username=USER;Password=TOKEN'. Set once at workflow level, it provides
credentials for the existing feed entry in NuGet.config, with no
runtime 'dotnet nuget add source' call and no user-level NuGet.Config
mutation. The feed key was renamed Redth-DotnetNx -> RedthDotnetNx so
the env-var lookup name has no hyphens.

npm auth - actions/setup-node@v6 native support:
setup-node already manages a scoped ~/.npmrc when given registry-url +
scope; npm ci consumes it via NODE_AUTH_TOKEN. This is the canonical
pattern from the official Node action.

Each of the 8 call sites in nx-affected-report.yml, nx-build-shadow.yml,
and devflow-integration.yml now has three inline steps (setup-node,
npm ci, setup-nxdn) instead of the composite + setup-nxdn pair. The
.github/actions/auth-gh-packages directory is deleted.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
reactivecircus/android-emulator-runner@v2 is a JS action that runs on
the host runner, so the nxdn tool installed by the prior setup-nxdn
step is at ~/.dotnet/tools/nxdn, but PATH propagation across the
action's spawned script: subshell isn't guaranteed across runner-image
revisions. Prepend $HOME/.dotnet/tools explicitly and add a 'which
nxdn' line so failures surface clearly if the binary is missing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace reactivecircus/android-emulator-runner@v2 (warm-up + run) and
the AVD cache with the prebuilt
ghcr.io/maui-containers/maui-emulator-linux:android${API}-dotnet10.0
image. The image ships a baked AVD plus Appium and boots the emulator
on container start with a Docker HEALTHCHECK.

- Drop Setup Java, manual KVM udev rule, and AVD cache (the image
  carries the AVD and runs the emulator under its own kvm-aware
  startup scripts; passing --device /dev/kvm is sufficient).
- Drop both reactivecircus runner invocations.
- docker run -d the emulator container with --device /dev/kvm
  --privileged and ports 5554/5555 mapped, then poll
  .State.Health.Status until healthy and adb connect localhost:5555.
- Set DEVFLOW_TEST_ANDROID_SERIAL=localhost:5555 at job level.
- Use the standard run-target@v0.3 action for the nx invocation --
  nothing has to live inside an emulator-runner subshell anymore.
- Tail emulator container logs on failure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace six workflows (_build.yml, ci-cli.yml, ci-devflow.yml,
ci-linux-gtk4.yml, nx-build-shadow.yml, nx-affected-report.yml) with
one .github/workflows/ci.yml.

The new workflow:
- Runs DotnetNx doctor + affected-matrix to compute a per-OS build
  matrix from <NxBuildableOn> project tags.
- Uploads the affected project graph and per-OS project lists as the
  nx-affected-report artifact.
- Per OS leg, runs restore/build/test/pack via run-affected@v0.3.
  Integration test projects (Android/iOS/MacCatalyst/Windows) are
  excluded; they continue to run from devflow-integration.yml.
- Conditionally runs the CLI NativeAOT publish smoke test on macOS
  when the CLI was actually built on this run.
- Uploads test results and nupkgs per OS.

Path filters and product-specific reusable workflows are gone --
project selection is entirely driven by Nx affected analysis.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The four integration-test projects share a stable
'Microsoft.Maui.DevFlow.Agent.IntegrationTests.*' prefix; Nx's
--exclude accepts the same project-spec syntax as --projects
(globs/tags/etc.), so we can drop the hand-maintained comma-separated
list in favour of one glob. New IntegrationTests.<Platform> projects
will be excluded automatically.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The maui-containers/maui-emulator-linux image ships with a Docker
HEALTHCHECK that waits for sys.boot_completed=1 plus Appium /status.
Two corrections to our adoption:

- Drop --privileged; only --device /dev/kvm is required for KVM
  passthrough. --privileged adds capabilities we don't need and can
  hide permission bugs.
- Remove the early-exit on 'unhealthy'. The HEALTHCHECK declares a
  120s --start-period during which the container reports 'starting',
  not 'unhealthy', so our prior bail-after-5-iterations branch was
  unreachable but misleading. Just wait for 'healthy' and let the
  outer 10-minute timeout bound the wait.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- AGENTS.md: replace per-product GH Actions workflow guidance with the
  consolidated Nx-driven ci.yml story; document <NxBuildableOn> for
  controlling OS fan-out; note Comet remains self-contained.
- AGENTS.md (Build Commands): add 'nx affected' commands; clarify
  cibuild.cmd is the AzDO official-pipeline path, not the CI you'll see
  on a GH PR.
- copilot-instructions.md: drop the multi-page per-product workflow
  template -- new products now need zero GH Actions edits, just an
  optional <NxBuildableOn>. AzDO checklist unchanged.
- testing.instructions.md: update CI Matrix section to reference
  ci.yml + 'nx affected -t test' instead of _build.yml + cibuild.sh.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Redth Redth force-pushed the redth/monorepo-build-strategy branch from cec998a to aca43d6 Compare May 1, 2026 17:16
Redth and others added 2 commits May 1, 2026 13:42
Maui.BuildHelpers PR #3 adds <NxTags>/<NxTag> MSBuild inputs that the
@redth/dotnet-nx plugin folds into Nx's tags field. Use this to remove
the last hard-coded project names from workflow YAML.

- IntegrationTests.Common.props: declare
    <NxTags>type:integration-test;device:$(DevFlowSamplePlatform)</NxTags>
  on the shared props consumed by all four platform integration projects.
- ci.yml: replace the glob exclude
    'Microsoft.Maui.DevFlow.Agent.IntegrationTests.*'
  with the semantic
    'tag:type:integration-test'.
- devflow-integration.yml: replace each run-target call (which named
  Microsoft.Maui.DevFlow.Agent.IntegrationTests.<Platform> explicitly)
  with
    nxdn nx -- run-many --projects=tag:device:<platform> -t test.
- Bump every Maui.BuildHelpers/DotnetNx action ref from @v0.3 to @v0.4
  for the tag metadata support.
- AGENTS.md + copilot-instructions.md: document the <NxTags>/<NxTag>
  pattern and link to the upstream tag reference.

Net effect: the YAML no longer mentions any integration-test project
name. Adding a fifth platform variant (e.g. tvOS) is now a one-csproj
change with no workflow edits.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…strategy

# Conflicts:
#	.github/workflows/_build.yml
#	.github/workflows/ci-cli.yml
#	AGENTS.md
#	NuGet.config
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant