Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5d0c986
feat(crates): publish releases through ESRP
dhoehna Jul 30, 2026
5edac7d
style(pipeline): remove trailing whitespace
dhoehna Jul 30, 2026
c1fddaa
fix(crates): unblock packaging and remove crates.io reads from release
dhoehna Aug 3, 2026
d408846
fix(crates): use a standard release job, not a deployment job
dhoehna Aug 3, 2026
3ec29fa
feat(crates): make crates.io release a single pipeline run
dhoehna Aug 3, 2026
0ba623e
feat(crates): gate the crates.io release on a release tag
dhoehna Aug 3, 2026
9aa04e3
Merge upstream main into crates-esrp-release
dhoehna Aug 4, 2026
e7ed928
feat(crates): publish the full 20-crate closure
dhoehna Aug 4, 2026
979a779
fix(crates): validate ESRP emails and correct stale crate count
dhoehna Aug 4, 2026
bf560a1
fix(crates): make release YAML compile in Azure DevOps
dhoehna Aug 4, 2026
94ba4d5
fix(crates): add missing crate descriptions and de-personalize ESRP o…
dhoehna Aug 4, 2026
b477dc1
Fix release ref gate: accept a selectable branch and the real tag con…
dhoehna Aug 4, 2026
88361db
Rename cratesDryRun to DryRun and default it to true
dhoehna Aug 5, 2026
8cfbbe1
Rename DryRun to dryRun to match parameter casing
dhoehna Aug 5, 2026
20611be
Take crateOrder out of the Run dialog and derive it from cargo metadata
dhoehna Aug 5, 2026
1bb85a7
Document how to regenerate the crate order from cargo when a crate is…
dhoehna Aug 5, 2026
2719139
Make the derived order the only crate order
dhoehna Aug 5, 2026
33456ec
Correct two claims an independent review caught
dhoehna Aug 5, 2026
159e750
Add the order subcommand to the module docstring
dhoehna Aug 5, 2026
a4b8c06
Remove ESRP email prompts and gate crate-order drift in CI
dhoehna Aug 5, 2026
55e8629
Enforce crate-order drift at release time, not just at PR time
dhoehna Aug 5, 2026
33276e0
Skip the publish stage on a dry run instead of running a no-op copy o…
dhoehna Aug 5, 2026
1cf117c
Correct the dry-run coverage claim and the real-release instructions
dhoehna Aug 5, 2026
9d14730
Verify the crate closure on all three target triples before publishing
dhoehna Aug 6, 2026
8080765
Make the release-ref gate case-sensitive again
dhoehna Aug 6, 2026
fa798a1
Cut the release pipeline comments down to what the code does
dhoehna Aug 7, 2026
afe07de
Drop the scaffolding around the three-target cargo package call
dhoehna Aug 7, 2026
e8e179a
Merge upstream main into the crates.io release branch
dhoehna Aug 7, 2026
acbc1e2
Gate the WSLC COM apartment guard to Windows
dhoehna Aug 7, 2026
eff0557
Gate the Windows Sandbox exe path resolution to Windows
dhoehna Aug 7, 2026
8d81889
Package crates natively on three agents instead of cross-compiling
dhoehna Aug 7, 2026
68f77ed
Note that the crate list has to compile on all three agent OSes
dhoehna Aug 7, 2026
779881d
Mark sandbox_spec publishable after crates.io clearance
dhoehna Aug 8, 2026
857bebb
Drop the version from the learning-mode PSEC dev-dependency
dhoehna Aug 8, 2026
3fa1349
Publish process_security_environment_spec with the closure
dhoehna Aug 8, 2026
b674d85
Record why the PSEC crate is publishable
dhoehna Aug 8, 2026
bdef317
Add the PSEC crate to the publish template's crate order
dhoehna Aug 8, 2026
ffc9402
Match the PSEC provenance wording to PR 782
dhoehna Aug 8, 2026
79cc8b5
Carry pool configuration in the packaging matrix
dhoehna Aug 8, 2026
a60f88d
Pick the pool image key with one macOS test
dhoehna Aug 8, 2026
9b11747
Add version requirements to path dependencies so cargo package succeeds
dhoehna Aug 8, 2026
47cc889
Package crates with cargo directly instead of the Python helper
dhoehna Aug 9, 2026
39044ad
Write the dependency edges cargo already knows into release-order.json
dhoehna Aug 9, 2026
bef1abb
Derive the packaging crate list instead of duplicating it
dhoehna Aug 9, 2026
e0a82ad
Correct a docstring that still described two copies of crateOrder
dhoehna Aug 9, 2026
7f74011
Remove Python from the crate packaging legs
dhoehna Aug 9, 2026
bb76d90
Remove all scripting from the crates.io release path
dhoehna Aug 9, 2026
694e943
Prune the crates.io release path to one crate list
dhoehna Aug 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .azure-pipelines/1ES.Build.Official.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# pipeline) so policies, retention, and queuing rules for official builds
# can be managed independently.
#
# No `trigger:`/`pr:` block — official builds are manually queued or
# `trigger: none` / `pr: none` — official builds are manually queued or
# scheduled externally. The actual stage list lives in
# `1ES.Build.Stages.yml`, shared with `1ES.Build.yml`; any
# build-content changes belong there, not here.
Expand All @@ -28,6 +28,10 @@ parameters:
displayName: Enable debug output for SDK tests
type: boolean
default: false
- name: packageCrates
displayName: Verify crate packaging on Windows, Linux, and macOS
type: boolean
default: true

variables:
# Variable group managed in Dart — see the pipeline's variable group list.
Expand All @@ -50,6 +54,7 @@ extends:
- template: templates/1ES.Build.Stages.yml@self
parameters:
isOfficialBuild: true
packageCrates: ${{ parameters.packageCrates }}
debug: ${{ parameters.debug }}
ESRPInfo:
serviceName: $(serviceName)
Expand Down
8 changes: 5 additions & 3 deletions .azure-pipelines/1ES.Build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
# to `main` and on PRs via the unsigned 1ES PipelineTemplate, delegating the
# stage list to `1ES.Build.Stages.yml`.
#
# The `main` push trigger populates the Azure Pipelines target-dir and
# CARGO_HOME cache scopes for the default branch so PR builds can read them
# as a fallback; without it, every PR runs a cold cargo compile.
# The `main` push trigger keeps the default branch green on the same stage set
# PRs run. Note there is no cargo cache task anywhere in this pipeline -- the
# only Cache@2 tasks in .azure-pipelines cache npm (see Package.NpmSdk.Job.yml,
# SDK.Unit.Test.Job.yml, and SDK.Integration.Test.Job.yml), so cargo
# compilation is not restored from a prior run.
trigger:
branches:
include:
Expand Down
110 changes: 110 additions & 0 deletions .azure-pipelines/1ES.Release.Crates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
#
# MXC Crates.io Release Pipeline (ESRP)
#
# Packages the MXC Rust crate closure and publishes every crate to crates.io
# through ESRP, leaf-first.
#
# The ref the run is queued against supplies both the source and this
# definition. Queue it against a release branch (release/v0.8.0) or a release
# tag (v0.8.0).
#
# Crate versions come from src/Cargo.toml at that ref.
#
# Setup, prerequisites, and known blockers: docs/crates-release.md

trigger: none
pr: none

name: $(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)

resources:
repositories:
- repository: 1ESPipelineTemplates
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/release

parameters:
- name: dryRun
displayName: 'Dry run — package only; publish stage is skipped'
type: boolean
default: true

variables:
- group: MXC-ESRP-Signing

extends:
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
parameters:
pool:
name: Azure-Pipelines-1ESPT-ExDShared
image: windows-2022
os: windows
customBuildTags:
- ES365AIMigrationTooling

sdl:
sourceAnalysisPool:
name: Azure-Pipelines-1ESPT-ExDShared
image: windows-latest
os: windows

stages:
- stage: Validate_Release_Ref
displayName: 'Validate Release Ref'
dependsOn: []
isSkippable: false
jobs:
- job: validate_release_ref
displayName: 'Require a release ref'
steps:
- checkout: none
- pwsh: |
$ErrorActionPreference = 'Stop'
$ref = $env:SOURCE_REF
Write-Host "Run was queued against: $ref"
# -clike, case-sensitive compare. Git refs are case-sensitive.
if ($ref -clike 'refs/heads/release/?*')
{
Write-Host "OK: releasing from branch $($ref.Substring('refs/heads/'.Length))"
}
elseif ($ref -clike 'refs/tags/v?*')
{
Write-Host "OK: releasing from tag $($ref.Substring('refs/tags/'.Length))"
}
else
{
Write-Host "##vso[task.logissue type=error]This pipeline may only run from a release branch (refs/heads/release/*) or a release tag (refs/tags/v*). It was queued against '$ref'."
Write-Host "Cut a branch such as 'release/v0.8.0' from the commit you are releasing, confirm the release/* ruleset has frozen it, then pick that branch in the ref selector of the Run pipeline dialog."
exit 1
}
displayName: 'Require refs/heads/release/* or refs/tags/v*'
env:
SOURCE_REF: $(Build.SourceBranch)

# Packages in a normal job, since 1ES forbids checkout in a release job.
- stage: Package_Crates_For_Release
displayName: 'Package Crates'
dependsOn: Validate_Release_Ref
jobs:
- template: .azure-pipelines/templates/Package.Crates.Job.yml@self

- stage: Publish_to_CratesIo
displayName: 'Publish Crates.io Packages'
dependsOn: Package_Crates_For_Release
condition: and(succeeded(), ${{ eq(parameters.dryRun, false) }})
jobs:
- template: .azure-pipelines/templates/Publish.CratesIo.Job.yml@self
parameters:
ESRPInfo:
serviceName: $(serviceName)
tenantId: $(tenantId)
azureKeyVaultName: $(azureKeyVaultName)
authCertName: $(authCertName)
signCertName: $(signCertName)
clientId: $(clientId)
OwnersEmail: $(Build.RequestedForEmail)
ApproversEmail: $(Build.RequestedForEmail)
dryRun: ${{ parameters.dryRun }}
91 changes: 91 additions & 0 deletions .azure-pipelines/scripts/Get-CrateOrder.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
#
# Prints the crates.io release closure of mxc-sdk, leaf-first.
#
# Two callers:
# Package.Crates.Job.yml at run time, to build the cargo package -p list
# a developer to regenerate crateOrder in Publish.CratesIo.Job.yml
#
# pwsh .azure-pipelines/scripts/Get-CrateOrder.ps1 -Yaml

[CmdletBinding()]
param
(
[string] $ManifestPath = 'src/Cargo.toml',

# The crate whose dependency closure ships. Everything reachable from it
# is released; everything else in the workspace is internal.
[string] $RootCrate = 'mxc-sdk',

# Emits the YAML list body ready to paste under crateOrder.
[switch] $Yaml
)

$ErrorActionPreference = 'Stop'

$metadata = cargo metadata --format-version 1 --no-deps --manifest-path $ManifestPath | ConvertFrom-Json
if ($LASTEXITCODE -ne 0) { throw "cargo metadata failed with exit $LASTEXITCODE" }

$members = @{}
foreach ($package in $metadata.packages) { $members[$package.name] = $package }

# A path dependency carrying a version is the one kind cargo rewrites into a
# registry dependency when it packages, so it is the one kind that has to be
# live on crates.io first. A version-less path dependency is legal only as a
# dev-dependency, which cargo drops from the packaged manifest; as a normal
# dependency cargo refuses to package at all. Neither constrains release
# order.
function Get-FirstPartyDependencies([string] $Name)
{
$result = @()
foreach ($dependency in $members[$Name].dependencies)
{
if ($dependency.path -and $members.ContainsKey($dependency.name) -and $dependency.req -and $dependency.req -ne '*')
{
$result += $dependency.name
}
}
return $result | Sort-Object -Unique
}

$closure = [System.Collections.Generic.HashSet[string]]::new()
$pending = [System.Collections.Generic.Stack[string]]::new()
if (-not $members.ContainsKey($RootCrate)) { throw "root crate '$RootCrate' is not a member of $ManifestPath" }
$pending.Push($RootCrate)
while ($pending.Count -gt 0)
{
$name = $pending.Pop()
if (-not $closure.Add($name)) { continue }
foreach ($dependency in Get-FirstPartyDependencies $name) { $pending.Push($dependency) }
}

$edges = @{}
foreach ($name in $closure) { $edges[$name] = @(Get-FirstPartyDependencies $name | Where-Object { $closure.Contains($_) }) }

# Kahn, emitting a whole ready batch at a time and sorting each batch
# ordinally, so the output is byte-stable across runs and agent cultures.
$order = @()
$remaining = [System.Collections.Generic.HashSet[string]]::new($closure)
while ($remaining.Count -gt 0)
{
$ready = @($remaining | Where-Object { @($edges[$_] | Where-Object { $remaining.Contains($_) }).Count -eq 0 })
if ($ready.Count -eq 0) { throw "dependency cycle among: $($remaining -join ', ')" }

$ready = [string[]] $ready
[Array]::Sort($ready, [System.StringComparer]::Ordinal)
foreach ($name in $ready)
{
$order += $name
$remaining.Remove($name) | Out-Null
}
}

if ($Yaml)
{
foreach ($name in $order) { " - $name" }
}
else
{
$order
}
26 changes: 26 additions & 0 deletions .azure-pipelines/templates/1ES.Build.Stages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ parameters:
type: boolean
default: false

# Runs `cargo package` for the crate closure on Windows, Linux, and macOS so
# a per-OS packaging break is caught on the build that introduced it. Kept as
# a parameter purely as an escape hatch: set it to false to skip the stage.
- name: packageCrates
type: boolean
default: true

- name: ESRPInfo
type: object
default:
Expand Down Expand Up @@ -82,6 +89,25 @@ stages:

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

# Runs on official and unofficial builds alike: the point is to prove every
# supported OS can package the closure, and a PR that breaks packaging on one
# OS should fail on that PR. The job picks the internal or public mirror from
# isOfficialBuild, since an unofficial build cannot authenticate to the
# internal feed.
- ${{ if parameters.packageCrates }}:
- stage: Package_Crates
displayName: 'Package Crates'
# Source-only (no binaries), so it does not depend on Build_Binaries.
# Verification only: 1ES.Release.Crates.yml declares no pipeline resource, so
# it repackages from the release ref and publishes that artifact, and the one
# produced here is never consumed. Isolated in its own stage so it never
# gates npm packaging or the integration tests that depend on Package_MXC.
dependsOn: []
jobs:
- template: Package.Crates.Job.yml
parameters:
isOfficialBuild: ${{ parameters.isOfficialBuild }}

- stage: Lint
displayName: 'Lint'
dependsOn: []
Expand Down
Loading