Skip to content

Enable one-click crates.io release for mxc-sdk - #647

Closed
dhoehna wants to merge 8 commits into
microsoft:mainfrom
dhoehna:user/dahoehna/crate-release-pipeline
Closed

Enable one-click crates.io release for mxc-sdk#647
dhoehna wants to merge 8 commits into
microsoft:mainfrom
dhoehna:user/dahoehna/crate-release-pipeline

Conversation

@dhoehna

@dhoehna dhoehna commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a one-click Azure DevOps pipeline that publishes the mxc-sdk crate and its first-party dependency closure to crates.io, plus the crate-metadata changes required to make that closure publishable.

This branch contains two logical commits:

  1. Make the mxc-sdk crate closure publishable (11 Cargo.toml files) — registry-agnostic metadata: workspace repository, per-crate description/license/repository, dual { path, version } on internal path deps, and making sandbox_spec publishable. Required to publish from anywhere.
  2. Add the release pipeline (2 files) — .azure-pipelines/1ES.Crate.Release.yml and .azure-pipelines/scripts/publish_crates_to_cratesio.py.

How it works

  • Run pipeline with dryRun = true (the default) previews every crate via cargo package --list and publishes nothing.
  • With dryRun = false, a Publish stage waits for manual approval on the MXC-CratesIo-Production environment, then publishes leaf-first, waiting for index propagation between crates. Re-runs are idempotent (a version already on crates.io is skipped).

Secrets stay out of the repo

CARGO_REGISTRY_TOKEN comes from the ADO variable group MXC-CratesIo-Publish and is mapped to the publish step's env only. Nothing secret is committed.

One-time setup (not codeable in the repo)

Variable group, environment + approval, crates.io egress allowlist, crate-name reservation / OSPO sign-off, and registering the pipeline. See the checklist at the bottom of 1ES.Crate.Release.yml.

Microsoft Reviewers: Open in CodeFlow

dhoehna and others added 2 commits July 13, 2026 20:41
Add crates.io-required metadata so mxc-sdk and its first-party dependency
closure (10 crates) can be published:

- workspace: add repository URL; give the 8 first-party path-deps in
  [workspace.dependencies] explicit versions (cargo publish requires a version
  on every path dependency).
- wxc_common: version its optional `nanvix_common` path-dep. Although it is
  gated by the off-by-default `microvm` feature, cargo requires every
  dependency in a published manifest -- optional ones included -- to carry a
  version and exist on crates.io, so nanvix_common must ship too. It is a leaf
  (serde/serde_json only).
- add description to wxc_common, mxc_pty, and the appcontainer / bubblewrap /
  lxc / seatbelt common crates.
- add license + repository to mxc_telemetry; repository to nanvix_common.
- inherit repository on each published crate.
- make sandbox_spec publishable (remove publish = false).

Validated with `cargo metadata` and per-crate `cargo package --list` (plus
`cargo publish --dry-run --no-verify` for leaf crates) against real crates.io.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e5d2aa5b-7f04-4e4d-83d3-a02efe7020ab
New ADO pipeline publishes the mxc-sdk crate and its 10-crate first-party
closure to crates.io from a single "Run pipeline" click, with a dryRun
parameter (default true) that previews every crate before anything uploads.

- .azure-pipelines/scripts/publish_crates_to_cratesio.py: leaf-first ordered
  publish (mxc_telemetry, nanvix_common, mxc_pty, sandbox_spec, wxc_common,
  lxc_common, seatbelt_common, appcontainer_common, bwrap_common, mxc-sdk),
  per-crate version resolved via `cargo metadata`, idempotent version-exists
  skip via the crates.io sparse index, `cargo publish --no-verify` with a
  post-publish index-propagation wait, and a --dry-run mode that runs
  `cargo package --list` (validates each manifest without needing first-party
  deps on the index yet). Modeled on scripts/seed_feed.py conventions.
- .azure-pipelines/1ES.Crate.Release.yml: trigger:none, extends the 1ES
  Official template; DryRun stage always previews; Publish stage is compiled
  in only when dryRun=false and gates on manual approval via the
  MXC-CratesIo-Production environment. Reuses Rust.Toolchain.Public.yml and
  deliberately skips Cargo.Setup.* so cargo talks to the real crates.io.

Secrets stay out of the repo: CARGO_REGISTRY_TOKEN comes from the
MXC-CratesIo-Publish variable group and is mapped to env only in the publish
step. One-time ADO/crates.io setup is documented in the pipeline header.

Validated locally against real crates.io: the full --dry-run passes for all
10 crates; all 10 target names are currently unregistered on crates.io.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e5d2aa5b-7f04-4e4d-83d3-a02efe7020ab
Copilot AI review requested due to automatic review settings July 14, 2026 19:04
@dhoehna
dhoehna requested a review from a team as a code owner July 14, 2026 19:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enables releasing the Rust mxc-sdk crate to crates.io by (1) making the crate’s first-party dependency closure publishable via Cargo metadata/manifest adjustments, and (2) adding an Azure DevOps “one-click” pipeline plus a helper script to publish the closure leaf-first.

Changes:

  • Add crates.io publish metadata to mxc-sdk and its first-party dependency closure (license/repository/description) and make sandbox_spec publishable.
  • Convert internal path dependencies used by the publishable closure to dual { path, version } so cargo publish can strip path and publish clean manifests.
  • Add an ADO pipeline (1ES.Crate.Release.yml) and a Python script to dry-run/package or publish the crate closure with index-propagation waits.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/Cargo.toml Adds workspace repository and sets { path, version } for publishable internal workspace deps.
src/core/wxc_common/Cargo.toml Adds publish metadata and ensures optional nanvix_common path dep includes a version for publishing.
src/core/mxc-sdk/Cargo.toml Adds workspace repository metadata needed for crates.io publishing.
src/core/mxc_pty/Cargo.toml Adds publish metadata (repository/description) for the closure.
src/mxc_telemetry/Cargo.toml Adds workspace license/repository metadata for publishing.
src/core/generated/base_container_specification/Cargo.toml Removes publish = false by making sandbox_spec publishable and adds repository metadata.
src/backends/appcontainer/common/Cargo.toml Adds publish metadata for publishing appcontainer_common.
src/backends/bubblewrap/common/Cargo.toml Adds publish metadata for publishing bwrap_common.
src/backends/lxc/common/Cargo.toml Adds publish metadata for publishing lxc_common.
src/backends/seatbelt/common/Cargo.toml Adds publish metadata for publishing seatbelt_common.
src/backends/nanvix/common/Cargo.toml Adds workspace repository metadata so nanvix_common is publishable.
.azure-pipelines/1ES.Crate.Release.yml New ADO pipeline that runs dry-run preview and gated publish to crates.io.
.azure-pipelines/scripts/publish_crates_to_cratesio.py New helper script to dry-run package or publish crates leaf-first and wait for sparse-index propagation.

Comment on lines +125 to +146
def _publish_one(crate: str, version: str, manifest_path: str, dry_run: bool) -> bool:
if version in _published_versions(crate):
print(f"SKIP {crate} {version} - already on crates.io")
return True

if dry_run:
# `cargo package --list` validates the manifest (it rejects a path dep
# that lacks a version, exactly as publish does) and prints the packaged
# file set, WITHOUT resolving first-party deps against the crates.io
# index. `cargo publish --dry-run` cannot be used for a whole-closure
# preview: it resolves dependencies against the live index, so every
# non-leaf crate would fail until its first-party deps are actually
# published. --allow-dirty keeps the preview frictionless if CI leaves
# the tree in a state cargo considers dirty; the real publish never
# allows it.
rc = _run_cargo(
["cargo", "package", "-p", crate, "--no-verify", "--allow-dirty",
"--manifest-path", manifest_path, "--list"]
)
ok = rc == 0
print(f"{'OK ' if ok else 'FAIL '} dry-run {crate} {version}")
return ok
Comment thread .azure-pipelines/1ES.Crate.Release.yml Outdated
Comment on lines +66 to +75
# ----------------------------------------------------------------------
# Preview: always runs, publishes nothing. Gives the operator the full
# `cargo publish --dry-run` + packaged-file list for every crate before
# anything reaches crates.io.
# ----------------------------------------------------------------------
- stage: DryRun
displayName: "Preview crate publish (dry run)"
jobs:
- job: DryRunJob
displayName: "cargo publish --dry-run"
Comment thread .azure-pipelines/1ES.Crate.Release.yml Outdated
# only cargo config in effect is the repo-root .cargo/config.toml (Windows
# rustflags only -- no source replacement, and inert on Linux).

trigger: none

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: To publish to crates.io using the official Microsoft open source account we will need to publish it through the ESRP release system. See: https://eng.ms/docs/microsoft-security/identity/trust-and-security-services/tss-release-distribute/tss-release-esrp-parent/oss-publishing/releasing-open-source/cratesio . You will need to follow that and do something similar to what we do in the 1ES.Release.yml. To be honest, we should be able to reuse that same yaml file to release to either Npm, Crates.io or both.

At the very least that one should be the entry point to all releases, but we can still have template yml files that we point to to keep it clean. Everything in terms of set up on the back end for ESRP should already be set up for us. So, let me know if you have any issues with that.

In the ESRP docs they say the azure rust sdk folks have this working, where they publish all their crate dependencies first then publish their main crate. I believe the yml file they do this in is this one https://github.com/Azure/azure-sdk-for-rust/blob/95eb8c3878cb2c7d3239b5f584b8179ee0483c31/eng/pipelines/templates/stages/archetype-rust-release.yml . We can probably do something similar.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Brandon -- followed this. Reworked the crate release to go through ESRP and folded it into 1ES.Release.yml so one pipeline releases npm, crates.io, or both:

  • 1ES.Release.yml now takes publishNpm / publishCrates boolean params. The existing NPM stage is gated on publishNpm, and a new Publish_to_CratesIo stage runs the ESRP path.
  • Following the azure-sdk-for-rust approach, the official build packages the closure leaf-first in a single cargo package invocation (all --package flags together, so intra-closure deps resolve locally) into the mxc-crates-package artifact. The release job then runs one EsrpRelease@12 task per crate (contenttype: Rust, account microsoft-oss-releases), leaf-first, verifying each on the crates.io sparse index before the next.
  • Deleted the old token-based 1ES.Crate.Release.yml + publish_crates_to_cratesio.py -- no more CARGO_REGISTRY_TOKEN. New templates: templates/Package.Crates.Job.yml (packaging) and templates/Publish.CratesIo.Job.yml (ESRP release loop).

This also obsoletes the two Copilot comments above (they were on the now-deleted script/pipeline).

One heads-up before we flip publishing on -- publishCrates defaults to false for now. Packaging the true --all-features closure of mxc-sdk surfaced that it now routes through the mxc_engine hub crate and expands to 17 first-party crates. Before a first real publish we still need to: (1) add a version to the 7 path deps in src/Cargo.toml that currently only carry path; (2) resolve isolation_session_bindings being publish = false; and (3) do the OSPO OSS-release registration + reserve the crate names. Happy to take that as a follow-up -- let me know if you'd rather have it in this PR or a separate one.

@microsoft-github-policy-service microsoft-github-policy-service Bot added the Needs-Author-Feedback Issue needs attention from issue or PR author label Jul 15, 2026
dhoehna and others added 2 commits July 15, 2026 12:53
Resolved Cargo.toml conflicts:
- src/Cargo.toml [workspace.dependencies]: kept version pins on wxc_common and
  appcontainer_common; kept upstream's new mxc_engine and mxc-sdk path deps
  (left unversioned - not part of the current publish closure).
- src/mxc_telemetry/Cargo.toml: kept repository.workspace = true.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e5d2aa5b-7f04-4e4d-83d3-a02efe7020ab
Rework the crates.io release per PR microsoft#647 review feedback (Brandon Bonaby):
publish through the ESRP OSS release system instead of a bespoke
CARGO_REGISTRY_TOKEN, folded into the existing 1ES.Release.yml so one
pipeline releases npm, crates.io, or both (modeled on azure-sdk-for-rust).

- scripts/crates_release.py: package/verify-order/stage/wait subcommands;
  cargo-packages the closure leaf-first, no token, no publish endpoint.
- templates/Package.Crates.Job.yml: official-build job -> mxc-crates-package.
- templates/Publish.CratesIo.Job.yml: releaseJob, EsrpRelease@12 contenttype
  Rust, one task per crate leaf-first, verify each on the crates.io index
  before the next.
- 1ES.Release.yml: publishNpm/publishCrates params; gate NPM stage; add
  Publish_to_CratesIo stage; ESRP one-time-setup notes in the header.
- 1ES.Build.Stages.yml: add Package_Crates stage.
- Delete 1ES.Crate.Release.yml + publish_crates_to_cratesio.py (token approach).

publishCrates defaults to false: enabling actual publishing still needs the
crate closure metadata reconciled (7 path deps need versions;
isolation_session_bindings is publish=false) plus OSPO OSS-release setup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 54ab05fd-1ef7-4fa4-b130-ea17a9ef8a47
@microsoft-github-policy-service microsoft-github-policy-service Bot added Needs-Attention Issue needs attention from Microsoft and removed Needs-Author-Feedback Issue needs attention from issue or PR author labels Jul 20, 2026
cmd_package looped `cargo package -p <crate>` one crate at a time, which
fails on the first non-leaf crate: packaging a crate alone strips the path
from each dependency and cargo then looks for the sibling on the crates.io
index, where it is not published yet (e.g. wxc_common -> mxc_telemetry ->
"no matching package named `mxc_telemetry` found").

Package the entire closure in a single `cargo package` invocation with every
crate passed via -p, so cargo resolves intra-closure deps from target/package/
instead of crates.io (the azure-sdk-for-rust Pack-Crates.ps1 approach). Verified
locally: all 9 packageable crates now tar in one run; the only remaining failure
is the known mxc-sdk -> mxc_engine missing-version blocker. Also corrected two
docstring claims that wrongly implied per-crate `--no-verify` packaging works.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 54ab05fd-1ef7-4fa4-b130-ea17a9ef8a47

- template: Mxc.Binary.Packaging.Job.yml

- stage: Package_Crates

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: we should probably add a conditional here to only do this packaging when the isOfficialBuild parameter is true.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 287305b -- gated the Package_Crates stage on isOfficialBuild in 1ES.Build.Stages.yml, so crate packaging only runs on the official signed build (not PR/CI).

Comment on lines +86 to +89
- script: |
set -euo pipefail
python3 "$(script)" wait --order-file "$(orderFile)" --crate "${{ crate }}"
displayName: "Wait for ${{ crate }} on crates.io index"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: the ESRP task above will actually keep polling until the package is published so we probably don't need this. It's done when the waitforreleasecompletion input to the task is set to true. That said, I'm not against the extra wait task.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point -- waitforreleasecompletion: true already blocks until each ESRP publish finishes. I kept the wait step as a lightweight guard that confirms the crate is resolvable on the feed index before the next dependent publishes, and it only emits a warning (never fails the release) on timeout. It is also skipped in the new cratesDryRun mode. Happy to drop it entirely if you would prefer -- your call. (Also fixed it to query the private feed per your other comment.)

Comment thread .azure-pipelines/1ES.Release.yml Outdated
Comment on lines +55 to +58
- name: publishNpm
displayName: "Publish the npm SDK to npmjs.com"
type: boolean
default: true

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: I like the parameters, lets make both false so it's clear in the UI that we expect the person initiating the pipeline run to select one or both.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 287305b -- publishNpm now defaults to false too, so the run dialog forces the operator to pick npm and/or crates.

"mxc-sdk",
]

CRATES_IO_SPARSE_INDEX = "https://index.crates.io"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: So this won't worked in the 1ES pipeline due to network isolation. You'd need to use the private feed's sparse index. Should be this one

Mxc-Azure-Feed = { index = "sparse+https://microsoft.pkgs.visualstudio.com/Dart/_packaging/Mxc-Azure-Feed/Cargo/index/" }

So it would look something like this:

AZURE_FEED_REGISTRY = "Mxc-Azure-Feed"
AZURE_FEED_SPARSE_INDEX = (
    "https://microsoft.pkgs.visualstudio.com/"
    "Dart/_packaging/Mxc-Azure-Feed/Cargo/index"
)
SYSTEM_ACCESS_TOKEN_ENV = "SYSTEM_ACCESSTOKEN"

then you can have a helper function in here somewhere:

def _feed_request(url: str) -> urllib.request.Request:
    """Create an Azure Artifacts request authenticated by the pipeline token."""
    token = os.environ.get(SYSTEM_ACCESS_TOKEN_ENV)
    if not token:
        raise RuntimeError(
            f"{SYSTEM_ACCESS_TOKEN_ENV} is not set. Map $(System.AccessToken) "
            f"to that environment variable in the pipeline task."
        )

    return urllib.request.Request(
        url,
        headers={"Authorization": f"Bearer {token}"},
    )

Then your _published_versions function below can look similar to:

def _published_versions(crate: str) -> set[str]:
    """Return the versions of `crate` already in the Azure Artifacts feed."""
    url = f"{AZURE_FEED_SPARSE_INDEX}/{_sparse_index_path(crate)}"
    with urllib.request.urlopen(_feed_request(url), timeout=30) as response:
        body = response.read().decode("utf-8")
    versions: set[str] = set()
    for line in body.splitlines():
        line = line.strip()
        if line:
            versions.add(json.loads(line)["vers"])
    return versions

You also have have references to crates.io in the comments of this file as well. You'd want to update those to just say Azure Artifacts Feed or something like that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in cfaaf18 -- switched the version check from public index.crates.io to the private Mxc-Azure-Feed sparse index (AZURE_FEED_SPARSE_INDEX, matching .azure-pipelines/.cargo/config.toml), added a _feed_request() Bearer-auth helper that reads SYSTEM_ACCESSTOKEN, and rewrote _published_versions() to hit the feed. Mapped $(System.AccessToken) -> SYSTEM_ACCESSTOKEN on the wait step and relabeled the crates.io comments to "Azure Artifacts feed". One assumption: the build identity has read access to Mxc-Azure-Feed -- let me know if that needs onboarding.

Comment on lines +60 to +77
# !!! KNOWN-INCOMPLETE PLACEHOLDER -- DO NOT enable `publishCrates` trusting this
# list as-is. !!!
# This is an OUTDATED 10-crate closure. A later refactor inserted the `mxc_engine`
# hub crate ("shared by the public SDK and the executor binaries") between mxc-sdk
# and the backends, so the true `--all-features` first-party closure of mxc-sdk is
# now 17 crates (adds mxc_engine, windows_sandbox_common, isolation_session_common,
# isolation_session_bindings, wslc_common, hyperlight_common, nanvix_runner).
# Before the closure can actually publish, three things must be resolved (tracked
# in the session plan / PR #647 discussion):
# 1. 7 path deps in src/Cargo.toml [workspace.dependencies] lack a `version`
# (add `version = "0.7.0"` to match their 8 siblings).
# 2. `isolation_session_bindings` is `publish = false` (a Windows WinRT bindings
# crate) -- it must be made publishable or dropped from mxc_engine's published
# manifest, since crates.io validates even optional/feature-gated deps.
# 3. This list + the pipeline's `crateOrder` parameter must be reconciled to the
# final closure and OSPO OSS-release setup / crate-name reservation completed.
# Until then the pipeline ships with `publishCrates=false` (default) and the
# `verify-order` guard fails safely if `crateOrder` and this list disagree.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: We should update this to include all the crates that will be published as part of the mxc-sdk. That said, The crate names themselves should actually be prefixed with mxc- so they are easily discoverable in crates.io.

I think we also need a follow up PR to add a GitHub PR workflow/action to check that all crates are publishable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on all three -- two need a decision/setup before I wire them in, so flagging rather than guessing:

  • Full closure: the real mxc-sdk --all-features closure is 17 crates (the mxc_engine hub was added by a later refactor). Before expanding CRATES/crateOrder, two blockers need resolving -- 7 path deps in src/Cargo.toml lack a version, and isolation_session_bindings is publish = false. Want those in this PR, or a follow-up?
  • mxc- prefix: renaming published crate names touches each [package] name and needs the names reserved on crates.io. Can you confirm the scheme (e.g. wxc_common -> mxc-wxc-common)? I will apply it once we agree.
  • Publishability check action: agree this is the follow-up PR you mentioned; I can open it separately.

The list is a clearly-marked placeholder and publishCrates defaults false, so nothing ships until this is settled.

Comment thread .azure-pipelines/1ES.Release.yml Outdated
Comment on lines +3 to +36
#
# Single entry point for MXC package releases. A run can publish the npm SDK,
# the crates.io Rust crates, or both, selected by the `publishNpm` /
# `publishCrates` parameters in the "Run pipeline" dialog. Both feeds publish
# through ESRP Release, so no registry tokens live in this repo.
#
# Artifacts are consumed from the official build (`MXC-Official-Build`):
# * npm SDK -> `mxc-npm-sdk-package` (Package.NpmSdk.Job.yml)
# * crates.io -> `mxc-crates-package` (Package.Crates.Job.yml)
#
# crates.io specifics (per the ESRP crates.io docs): ESRP publishes pre-built
# `.crate` files under the official `microsoft-oss-releases` account via the
# crates.io API, which does NOT enforce dependency order. The crate closure is
# therefore published leaf-first, one ESRP task per crate, verifying each on the
# crates.io index before its dependents -- all handled by
# templates/Publish.CratesIo.Job.yml.
#
# ---------------------------------------------------------------------------
# ONE-TIME crates.io enablement (out of band -- not codeable in this repo)
# ---------------------------------------------------------------------------
# 1. OSPO / CELA open-source release registration + checklist (LICENSE, README,
# SECURITY.md, CONTRIBUTING.md) for publishing MXC to crates.io.
# 2. Reserve (or first-publish) the crate names under the `microsoft-oss-releases`
# account: mxc_telemetry, nanvix_common, mxc_pty, sandbox_spec, wxc_common,
# lxc_common, seatbelt_common, appcontainer_common, bwrap_common, mxc-sdk.
# 3. Have the ESRP Release team enable the `Rust` content type for this pipeline's
# ESRP client id (see the "Tenant does not have access to the given Main
# Publisher" note in the ESRP crates.io docs). The same App-Registration
# identity used for npm/signing (ESRPInfo below) is reused.
# 4. Allow read egress to the crates.io index (index.crates.io) on the release
# pool so the between-crates propagation check can confirm each publish.
# No CARGO_REGISTRY_TOKEN, variable group, or custom environment is required --
# ESRP owns the crates.io credentials, and the 1ES production releaseJob provides
# the manual release gate.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: most of this comment can be removed or reduced now I believe.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 287305b -- trimmed the header to the essentials and dropped the stale "egress to index.crates.io" note (superseded by the private-feed fix).


# Parameters for ESRP release info will be passed from the ADO UI.
parameters:
- name: publishNpm

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: for your testing, you can add a dry-run boolean in here too, that when set won't go into the EsrpRelease@10 tasks. You can actually test this out if you create an in repo branch (noticed this PR is coming from your fork). But yea if you create an in repo branch e.g user/dhoehna/test-rust-publishing with these same changes, you should be able to use the branch to run the official pipeline via https://microsoft.visualstudio.com/Dart/_build?definitionId=190018 and then see any errors that pop up so you can fix them in this PR. Let me know if you don't have access.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a cratesDryRun boolean in 1ES.Release.yml (passed through to Publish.CratesIo.Job.yml) in 287305b: when set, each crate still runs stage + verify-order but skips the EsrpRelease task and the feed wait, so the plumbing runs without publishing.

On the in-repo branch: I do not have push access to microsoft/mxc from here (this PR is from my fork), so I cannot create user/dhoehna/test-rust-publishing myself. Could you create it from these changes (or grant access)? Then I will run pipeline 190018 against it and fix whatever surfaces.

The 1ES official build runs network-isolated and cannot reach the public
crates.io sparse index (https://index.crates.io), so crates_release.py's
post-publish version check would fail at runtime. Query the private
Mxc-Azure-Feed sparse index instead (reachable in-pipeline), authenticated
with the build identity's $(System.AccessToken).

- crates_release.py: replace CRATES_IO_SPARSE_INDEX with AZURE_FEED_SPARSE_INDEX
  (matches .azure-pipelines/.cargo/config.toml); add _feed_request() Bearer-auth
  helper reading SYSTEM_ACCESSTOKEN; _published_versions() now hits the feed;
  relabel poll/index wording to "Azure Artifacts feed".
- Publish.CratesIo.Job.yml: map $(System.AccessToken) -> SYSTEM_ACCESSTOKEN on
  the wait step so the feed request can authenticate.

Addresses Brandon Bonaby's review comment on PR microsoft#647.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 54ab05fd-1ef7-4fa4-b130-ea17a9ef8a47
Copilot AI review requested due to automatic review settings July 24, 2026 18:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 4 comments.

Comment on lines +62 to +66
# This is an OUTDATED 10-crate closure. A later refactor inserted the `mxc_engine`
# hub crate ("shared by the public SDK and the executor binaries") between mxc-sdk
# and the backends, so the true `--all-features` first-party closure of mxc-sdk is
# now 17 crates (adds mxc_engine, windows_sandbox_common, isolation_session_common,
# isolation_session_bindings, wslc_common, hyperlight_common, nanvix_runner).
python3 "$(script)" stage --order-file "$(orderFile)" --crate "${{ crate }}" --out-dir "$(esrpDir)"
displayName: "Stage ${{ crate }} for ESRP"

- task: EsrpRelease@12
Comment on lines +302 to +308
# ESRP ran with waitforreleasecompletion:true, so the publish itself already
# completed; only index propagation is unconfirmed. Warn (don't fail the
# release) and let the next crate proceed.
print(f"##vso[task.logissue type=warning]{crate} {version} not confirmed on the "
f"Azure Artifacts feed within {args.timeout}s; continuing (ESRP reported the "
f"publish complete).")
return 0
Comment on lines +59 to +62
- name: publishCrates
displayName: "Publish the Rust crate closure to crates.io"
type: boolean
default: false
Applies Brandon Bonaby's PR microsoft#647 review comments:

- 1ES.Build.Stages.yml: gate the Package_Crates stage on isOfficialBuild so
  crate packaging only runs on the official signed build, not PR/CI.
- 1ES.Release.yml: default publishNpm to false too, so the operator must
  explicitly select npm and/or crates in the run dialog.
- 1ES.Release.yml: reduce the oversized header comment (and drop the stale
  index.crates.io egress note superseded by the private-feed fix).
- Add a cratesDryRun parameter (1ES.Release.yml -> Publish.CratesIo.Job.yml)
  that runs stage + verify-order but skips the EsrpRelease publish and the feed
  wait, for testing the pipeline without publishing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 54ab05fd-1ef7-4fa4-b130-ea17a9ef8a47
Copilot AI review requested due to automatic review settings July 24, 2026 19:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (4)

.azure-pipelines/scripts/crates_release.py:99

  • The packaged list is not the mxc-sdk dependency closure: mxc-sdk directly depends on mxc_engine (src/core/mxc-sdk/Cargo.toml:15), but that crate and its seven first-party dependencies are absent here, their workspace dependencies still lack versions, and isolation_session_bindings remains non-publishable. Cargo rejects a packaged crate whose registry manifest contains a versionless path dependency, so the new unconditional Package_Crates stage will fail every official build instead of producing the release artifact. Complete the 17-crate closure and its publish metadata before enabling this stage.
    "mxc-sdk",

.azure-pipelines/1ES.Release.yml:45

  • These defaults do not provide the advertised safe preview: with both publish flags false the template expands to no release stages, while enabling publishCrates without also changing the secondary option immediately takes the live ESRP branch because cratesDryRun is false. Default the crate action to a dry run (or provide an equivalent explicit no-op/validation design) so a default run cannot accidentally publish.
    default: false
  - name: cratesDryRun
    displayName: "Crates dry run: stage + verify only, skip the ESRP publish"
    type: boolean
    default: false

.azure-pipelines/templates/Publish.CratesIo.Job.yml:76

  • Every rerun invokes ESRP for every crate unconditionally. The only _published_versions call occurs later in wait, so an existing crate version is never skipped and crates.io's duplicate-version rejection will fail a resumed release. Add a pre-publish check that bypasses the ESRP task and wait for versions already present, as promised by the PR's idempotency contract.
                  - task: EsrpRelease@12

.azure-pipelines/scripts/crates_release.py:308

  • Returning success after propagation times out defeats the leaf-first ordering guarantee: the next ESRP upload can run while this dependency is still unresolved and fail after earlier crates have already been released. waitforreleasecompletion confirms the ESRP operation, not index visibility, so stop the release when visibility cannot be confirmed.
    print(f"##vso[task.logissue type=warning]{crate} {version} not confirmed on the "
          f"Azure Artifacts feed within {args.timeout}s; continuing (ESRP reported the "
          f"publish complete).")
    return 0

default: false

jobs:
- deployment: publish_crates
Resolve src/Cargo.toml conflict in [workspace.dependencies]: keep main's
roxmltree -> quick-xml migration and the new windows_sandbox_lifecycle
dependency, alongside the version = "0.7.0" pins added for crate publishing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 54ab05fd-1ef7-4fa4-b130-ea17a9ef8a47
Copilot AI review requested due to automatic review settings July 24, 2026 19:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

.azure-pipelines/scripts/crates_release.py:89

  • CRATES is not the mxc-sdk dependency closure: mxc-sdk directly depends on mxc_engine, which is omitted and still has no registry version in src/Cargo.toml; its additional first-party dependencies are likewise unresolved. Because the new Package_Crates stage invokes this command on every official build, cargo package will reject the SDK manifest and no release artifact will be produced. Complete and version the full closure (including resolving the non-publishable bindings crate) before enabling this stage.
CRATES: list[str] = [

.azure-pipelines/1ES.Release.yml:45

  • Selecting publishCrates while leaving this option untouched immediately enters the live ESRP branch, contrary to the PR's documented default-on, non-publishing preview. Make dry-run the safe default so an operator must explicitly opt into publication.
    default: false

.azure-pipelines/templates/Publish.CratesIo.Job.yml:34

  • This deployment is not bound to the MXC-CratesIo-Production environment, so that environment's manual approval checks will not run; templateContext.isProduction only classifies the 1ES job. Explicitly target the environment described by the release contract before any publish steps execute.
  - deployment: publish_crates
    displayName: Publish crates to crates.io (ESRP)

.azure-pipelines/templates/Publish.CratesIo.Job.yml:76

  • Every retry invokes ESRP for every crate unconditionally; the existing-version lookup only runs afterward in wait. Since crates.io rejects an already-published name/version, a retry after a partial release fails on the first completed crate instead of resuming as the PR promises. Add a pre-publish existence check and condition both this task and its wait step on the version being absent.
                  - task: EsrpRelease@12

@dhoehna

dhoehna commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@bbonaby quick process question. I now have write access to microsoft/mxc, so in-repo branches are possible.

As you noted earlier, this fork-based PR can't actually exercise the release pipeline end-to-end: the official build and 1ES.Release are both trigger: none and depend on the MXC-ESRP-Signing variable group / ESRP service connection, none of which is exposed to a fork PR (and a manual queue can't target a fork ref).

Would you like me to abandon this PR and re-create it from an in-repo branch (e.g. user/dhoehna/test-rust-publishing) so we can queue MXC-Official-Build and then 1ES.Release with publishCrates: true + cratesDryRun: true (stage/verify only, nothing published)? Or would you rather keep this PR for the code review and I open a separate in-repo branch purely for the test run? Either works for me — whichever you prefer.

@bbonaby

bbonaby commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

@bbonaby quick process question. I now have write access to microsoft/mxc, so in-repo branches are possible.

As you noted earlier, this fork-based PR can't actually exercise the release pipeline end-to-end: the official build and 1ES.Release are both trigger: none and depend on the MXC-ESRP-Signing variable group / ESRP service connection, none of which is exposed to a fork PR (and a manual queue can't target a fork ref).

Would you like me to abandon this PR and re-create it from an in-repo branch (e.g. user/dhoehna/test-rust-publishing) so we can queue MXC-Official-Build and then 1ES.Release with publishCrates: true + cratesDryRun: true (stage/verify only, nothing published)? Or would you rather keep this PR for the code review and I open a separate in-repo branch purely for the test run? Either works for me — whichever you prefer.

Yes let's abandon this one and make a new in repo branch for it. You can do the testing for your branch before you create a PR too. That's what I usually do so I can try things out fast without having Github send PR emails to reviewers about new commits.

@dhoehna

dhoehna commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Closing in favor of an in-repo branch. Per discussion with @bbonaby above, I've moved these changes to user/dhoehna/test-rust-publishing on microsoft/mxc (head 237abe6, full history + all review feedback preserved).

A fork PR can't exercise the release pipeline (the official build and 1ES.Release are trigger: none and depend on the MXC-ESRP-Signing variable group / ESRP service connection, none of which is exposed to forks). From the in-repo branch I'll queue MXC-Official-Build and then 1ES.Release with publishCrates: true + cratesDryRun: true (stage/verify only, nothing published), and open a fresh PR from that branch once the dry run is green. Thanks for the review here!

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.

3 participants