Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ jobs:
name: Pack native-AOT tool for ${{ matrix.rid }}
shell: bash

# Container image, linux-x64 only for now — the platform every GitHub-hosted Linux runner
# already matches. No --push here and no registry login on this leg, so this only proves the
# container build itself still works: AOT compiles, the chiseled base image resolves, and the
# SDK's container tooling produces an image in the local Docker daemon. The real push to
# ghcr.io happens once, in the build job below, which does pass --push.
- name: Build container image (no push)
if: matrix.rid == 'linux-x64'
run: ./build.sh publishcontainers -s true
shell: bash

- name: Report container image size
if: matrix.rid == 'linux-x64'
shell: bash
run: docker images nullean/assembly-rewriter --format '{{.Tag}}\t{{.Size}}'

- name: Upload per-RID package
if: github.event_name == 'push'
uses: actions/upload-artifact@v4
Expand All @@ -50,6 +65,10 @@ jobs:
build:
runs-on: ubuntu-latest
needs: aot-pack
permissions:
contents: write # create GitHub release
packages: write # push to GitHub Packages and ghcr.io
issues: write # release-notes creates labels for its categories
steps:
- uses: actions/checkout@v5
with:
Expand Down Expand Up @@ -92,6 +111,21 @@ jobs:
run: |
until dotnet nuget push build/output/*.nupkg -k ${{secrets.GITHUB_TOKEN}} --skip-duplicate --no-symbols; do echo "Retrying"; sleep 1; done;

# ghcr.io/nullean/assembly-rewriter, matching curb's own tagging: "edge" on every push, plus
# "latest" and the semver when this push is an exact release tag — see
# publishContainers/containerImageTags in Targets.fs for how that split is decided.
- name: Log in to ghcr.io
if: github.event_name == 'push'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Publish container image
if: github.event_name == 'push'
run: ./build.sh publishcontainers -s true --push

- run: ./build.sh generatereleasenotes -s true
name: Generate release notes for tag
if: github.event_name == 'push' && startswith(github.ref, 'refs/tags')
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/create-major-tag.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: create-major-tag

# Lets a workflow pin this repo as a GitHub Action with `uses: nullean/assembly-rewriter@v1` instead of
# an exact release tag, the same way actions/checkout@v5 stays on v5 across patch and minor releases.

on:
release:
types:
- published

permissions: {}

concurrency:
group: create-major-tag
cancel-in-progress: false

jobs:
create-major-tag:
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: true

- name: Validate and extract major version
env:
TAG: ${{ github.event.release.tag_name }}
run: |
if [[ ! "$TAG" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Tag '${TAG}' does not match expected semver format (N.N.N)"
exit 1
fi
echo "MAJOR_VERSION=${TAG%%.*}" >> "${GITHUB_ENV}"

- name: Create major tag
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
git tag "v${MAJOR_VERSION}"
git push -f origin "refs/tags/v${MAJOR_VERSION}"
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,33 @@ dotnet assembly-rewriter

You can omit `dotnet` if you install this as a global tool

## GitHub Action

```yaml
- uses: nullean/assembly-rewriter@main
with:
args: -i Nest.dll -o Nest620.dll
```

Runs `assembly-rewriter` from a pre-built, distroless container (`ghcr.io/nullean/assembly-rewriter`) —
no .NET SDK install needed in the workflow. `args` is the full command line, since every input and
output path is passed as an `-i`/`-o` pair (see below). Mount your working directory's DLLs where the
container can see them; a container action's default working directory already maps to the workflow's
checkout. Linux runners only (`ubuntu-latest` and similar) — container actions can't run on Windows or
macOS runners.

## Container image

`ghcr.io/nullean/assembly-rewriter` also works as a general-purpose container, outside GitHub Actions —
GitLab CI, a local machine without the .NET SDK, anywhere `docker run` works:

```sh
docker run --rm -v "$(pwd)":/workspace ghcr.io/nullean/assembly-rewriter:edge -i /workspace/Nest.dll -o /workspace/Nest620.dll
```

Distroless: native-AOT, chiseled `runtime-deps` base, no shell, runs as a non-root user. Tags follow
`assembly-rewriter`'s own releases — `edge` tracks the latest commit on `master`, `latest` and a semver
tag follow tagged releases.

## Examples

Expand Down
14 changes: 14 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: 'assembly-rewriter'
description: 'Rewrite a .NET assembly namespace so two versions of the same assembly can be referenced side by side'
branding:
icon: 'git-branch'
color: 'blue'
inputs:
args:
description: 'Arguments passed through to assembly-rewriter verbatim, e.g. "-i Nest.dll -o Nest620.dll"'
required: true
runs:
using: 'docker'
image: 'docker://ghcr.io/nullean/assembly-rewriter:edge'
args:
- ${{ inputs.args }}
4 changes: 4 additions & 0 deletions build/scripts/CommandLine.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,32 @@
| [<CliPrefix(CliPrefix.None);SubCommand>] Release

| [<CliPrefix(CliPrefix.None);Hidden;SubCommand>] CreateReleaseOnGithub
| [<CliPrefix(CliPrefix.None);Hidden;SubCommand>] PublishContainers
| [<CliPrefix(CliPrefix.None);SubCommand>] Publish

| [<Inherit;AltCommandLine("-s")>] SingleTarget of bool
| [<Inherit>] Token of string
| [<Inherit>] Push
with
interface IArgParserTemplate with
member this.Usage =
match this with
| Clean _ -> "clean known output locations"

Check warning on line 28 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / AOT pack (linux-x64)

Pattern discard is not allowed for union case that takes no data.

Check warning on line 28 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.

Check warning on line 28 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.

Check warning on line 28 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.

Check warning on line 28 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.
| Build _ -> "Run build and tests"

Check warning on line 29 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / AOT pack (linux-x64)

Pattern discard is not allowed for union case that takes no data.

Check warning on line 29 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.

Check warning on line 29 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.

Check warning on line 29 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.

Check warning on line 29 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.
| Release _ -> "runs build, and create an validates the packages shy of publishing them"

Check warning on line 30 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / AOT pack (linux-x64)

Pattern discard is not allowed for union case that takes no data.

Check warning on line 30 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.

Check warning on line 30 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.

Check warning on line 30 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.
| Publish _ -> "Runs the full release"

Check warning on line 31 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / AOT pack (linux-x64)

Pattern discard is not allowed for union case that takes no data.

Check warning on line 31 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.

Check warning on line 31 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.

Check warning on line 31 in build/scripts/CommandLine.fs

View workflow job for this annotation

GitHub Actions / build

Pattern discard is not allowed for union case that takes no data.

| SingleTarget _ -> "Runs the provided sub command without running their dependencies"
| Token _ -> "Token to be used to authenticate with github"
| Push -> "publishcontainers only: push the built image to ghcr.io instead of building it into the local Docker daemon"

| PristineCheck
| GeneratePackages
| ValidatePackages
| GenerateReleaseNotes
| GenerateApiChanges
| CreateReleaseOnGithub
| PublishContainers
-> "Undocumented, dependent target"
member this.Name =
match FSharpValue.GetUnionFields(this, typeof<Arguments>) with
Expand Down
42 changes: 41 additions & 1 deletion build/scripts/Targets.fs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,45 @@ let private createReleaseOnGithub (arguments:ParseResults<Arguments>) =

exec "dotnet" (["release-notes"] @ releaseArgs) |> ignore

/// Tags for the container image, mirroring the versioning currentVersion already derives from git:
/// "edge" always (so `ghcr.io/nullean/assembly-rewriter:edge` is always the latest master build), plus
/// "latest" and the plain semver when this is an exact release tag rather than a canary commit —
/// MinVer's canary suffix always contains a hyphen, a clean tag never does.
let private containerImageTags =
lazy(
let version = currentVersion.Value
if version.Contains("-") then "edge" else sprintf "edge;latest;%s" version
)

/// Publishes the CLI's native-AOT build as a container image via the .NET SDK's own container
/// support (`dotnet publish -t:PublishContainer`), the same mechanism used for curb — see
/// https://github.com/nullean/curb/pull/72. linux-x64 only for now; a second RID becomes a second
/// manifest-list platform later with no change to action.yml.
///
/// Base image is the chiseled/distroless runtime-deps image: no shell, minimal surface, and correct
/// for an AOT binary specifically because there is no managed runtime to host — a plain `runtime`
/// image would carry a CLR this binary never uses.
///
/// --push is an explicit flag, not inferred from a CI/event-name environment variable: the aot-pack
/// job's linux-x64 leg calls this on every trigger (PR, push, tag) purely to prove the container
/// build itself still works, with no ghcr.io credentials configured there, and an env-based "is this
/// a push?" check would have tried (and failed) to push from that job on every non-PR trigger. Only
/// the build job, which does log in, passes --push.
let private publishContainers (arguments:ParseResults<Arguments>) =
let baseImageTag = "10.0-noble-chiseled"
let registryArgs =
if arguments.Contains Push then ["-p"; "ContainerRegistry=ghcr.io"] else []
let args =
["publish"; Paths.RootRelative Paths.ToolProject.FullName; "-c"; "Release"; "-f"; "net10.0"; "-r"; "linux-x64"]
@ ["/t:PublishContainer"
"-p"; "DebugType=none"
"-p"; sprintf "ContainerBaseImage=mcr.microsoft.com/dotnet/runtime-deps:%s" baseImageTag
"-p"; sprintf "ContainerRepository=%s" Paths.Repository
"-p"; sprintf "ContainerImageTags=\"%s\"" containerImageTags.Value
"-p"; "ContainerUser=1001:1001"]
@ registryArgs
exec "dotnet" args |> ignore

let private release (arguments:ParseResults<Arguments>) = printfn "release"

let private publish (arguments:ParseResults<Arguments>) = printfn "publish"
Expand Down Expand Up @@ -177,7 +216,8 @@ let Setup (parsed:ParseResults<Arguments>) (subCommand:Arguments) =
<| fun _ -> release parsed

step CreateReleaseOnGithub.Name createReleaseOnGithub
step PublishContainers.Name publishContainers
cmd Publish.Name
(Some [Release.Name])
(Some [CreateReleaseOnGithub.Name; ])
(Some [CreateReleaseOnGithub.Name; PublishContainers.Name])
<| fun _ -> publish parsed
4 changes: 4 additions & 0 deletions src/assembly-rewriter/assembly-rewriter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
The root package's DotnetToolSettings.xml (v2) maps each RID to its per-RID package.
Installing requires the .NET 10 SDK or greater. -->
<RuntimeIdentifiers>linux-x64;linux-arm64;win-x64;win-arm64;osx-arm64;any</RuntimeIdentifiers>
<!-- Lets `dotnet publish -f net10.0 -r <rid> -t:PublishContainer` containerize this same AOT
publish output — see build/scripts/Targets.fs's publishContainers target and
ghcr.io/nullean/assembly-rewriter. -->
<EnableSdkContainerSupport>true</EnableSdkContainerSupport>
</PropertyGroup>

<!-- AOT settings apply only when a specific RID is targeted (dotnet pack -r <rid>). Without this
Expand Down
Loading