From 7b7a1a43a11e60811f4b15ffd0351d17afb26ff7 Mon Sep 17 00:00:00 2001 From: botoddly <250804054+botoddly@users.noreply.github.com> Date: Mon, 9 Nov 2026 10:22:26 +0100 Subject: [PATCH 1/3] Pack XdlSharp and per-platform native packages and release them to nuget.org on every merge to main --- .github/workflows/build.yml | 78 +++++++++++++++ .github/workflows/release.yml | 99 +++++++++++++++++++ Directory.Build.props | 11 +++ Directory.Build.targets | 4 +- README.md | 18 +++- XDL_VERSION | 2 +- XdlSharp.slnx | 3 + .../XdlSharp.Native/XdlSharp.Native.csproj | 37 +++++++ .../XdlSharp.Native.browser-wasm.targets | 14 +++ src/XdlSharp/XdlSharp.csproj | 3 + tools/XdlRelease.proj | 11 ++- tools/pack.sh | 20 ++++ 12 files changed, 292 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml create mode 100644 packaging/XdlSharp.Native/XdlSharp.Native.csproj create mode 100644 packaging/XdlSharp.Native/buildTransitive/XdlSharp.Native.browser-wasm.targets create mode 100755 tools/pack.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..642db80 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,78 @@ +name: Build + +on: + workflow_dispatch: + pull_request: + # Release runs this workflow with the version to pack, and publishes its packages. + workflow_call: + inputs: + version: + description: The version of the packages, .. Without it the packages are marked local. + required: false + type: string + +permissions: + contents: read + +jobs: + desktop: + name: Generate, test and pack + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install the .NET 11 preview SDK + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '11.0.x' + dotnet-quality: 'preview' + + # The checked-in bindings are generated with clang 22, and another clang can print the AST differently. + - name: Install clang 22 + run: | + wget -qO llvm.sh https://apt.llvm.org/llvm.sh + sudo bash llvm.sh 22 + + - name: Check that the generated code is current + run: dotnet run --project tools/XdlSharp.Generator -- --check --clang clang-22 + + - name: Test + run: dotnet test tests/XdlSharp.Tests + + - name: Pack + env: + VERSION: ${{ inputs.version }} + run: tools/pack.sh artifacts/packages "$VERSION" + + - name: Upload the packages + uses: actions/upload-artifact@v4 + with: + name: packages + path: artifacts/packages + if-no-files-found: error + + browser: + name: Test in the browser runtime + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install the .NET 11 preview SDK + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '11.0.x' + dotnet-quality: 'preview' + + - name: Install the wasm-tools workload + run: dotnet workload install wasm-tools + + - name: Publish the browser tests + run: dotnet publish tests/XdlSharp.Browser.Tests + + - name: Run the browser tests + working-directory: tests/XdlSharp.Browser.Tests/bin/Release/net11.0/publish/wwwroot + run: node main.mjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4ce45aa --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,99 @@ +name: Release + +# Publishes each push to main on nuget.org as . and tags it v. N is one more than the highest N tagged for that +# SDL version, so it starts at 1. A failed release is run again by hand: the push skips packages that nuget.org already has. +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +# Two runs at once would take the same N. +concurrency: + group: release + cancel-in-progress: false + +jobs: + resolve: + # A run started from another branch would release unreviewed code. + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-24.04 + outputs: + version: ${{ steps.version.outputs.version }} + + steps: + # A shallow checkout has no tags, so every run would take N = 1. + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Find the version + id: version + run: | + if [[ -n "$(git tag --points-at HEAD 'v*')" ]]; then + echo "$(git tag --points-at HEAD 'v*') is released already." + exit 0 + fi + + sdl_version=$(cut -d- -f1 XDL_VERSION) + last=0 + for tag in $(git tag --list "v$sdl_version.*"); do + n=${tag#"v$sdl_version."} + if [[ "$n" =~ ^[0-9]+$ ]] && (( n > last )); then + last=$n + fi + done + echo "version=$sdl_version.$((last + 1))" >> "$GITHUB_OUTPUT" + + build: + needs: resolve + if: needs.resolve.outputs.version != '' + uses: ./.github/workflows/build.yml + with: + version: ${{ needs.resolve.outputs.version }} + + publish: + needs: [resolve, build] + runs-on: ubuntu-24.04 + permissions: + contents: write + # Trusted Publishing: nuget.org exchanges this workflow's OIDC token for a short-lived API key. + id-token: write + env: + VERSION: ${{ needs.resolve.outputs.version }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download the packages + uses: actions/download-artifact@v4 + with: + name: packages + path: packages + + - name: Install the .NET 11 preview SDK + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '11.0.x' + dotnet-quality: 'preview' + + - name: Log in to nuget.org + id: login + uses: NuGet/login@v1 + with: + user: stanoddly + + - name: Push the packages + env: + NUGET_API_KEY: ${{ steps.login.outputs.NUGET_API_KEY }} + run: dotnet nuget push "packages/*.nupkg" --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json --skip-duplicate + + # After the push, so a tag always names a published version. + - name: Tag the release + run: | + git tag "v$VERSION" "$GITHUB_SHA" + git push origin "v$VERSION" diff --git a/Directory.Build.props b/Directory.Build.props index 745e8af..94c94a9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,7 +10,18 @@ $([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)XDL_VERSION').Trim()) + $(XdlVersion.Split('-')[0]) $([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)', 'artifacts', 'xdl', '$(XdlVersion)')) $(XdlReleaseDirectory)include/ + + + + $(XdlSdlVersion).0 + local + stanoddly + https://github.com/stanoddly/XDLSharp + https://github.com/stanoddly/XDLSharp + xdl;sdl;sdl3;bindings;interop + diff --git a/Directory.Build.targets b/Directory.Build.targets index 3b52d77..7a00fdf 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -2,10 +2,12 @@ $(XdlReleaseDirectory)runtimes/$(NETCoreSdkPortableRuntimeIdentifier)/native/ $(XdlReleaseDirectory)runtimes/browser-wasm/native/ + + headers;$(NETCoreSdkPortableRuntimeIdentifier);browser-wasm-net11 - + diff --git a/README.md b/README.md index 86a6cfa..5df4702 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ SDL_Init(SDL_InitFlags.SDL_INIT_VIDEO); Pointer window = SDL_CreateWindow("Title"u8, 640, 480, SDL_WindowFlags.SDL_WINDOW_RESIZABLE); ``` +## Packages + +- `XdlSharp` holds the bindings. It is the same for every platform and holds no native library. +- `XdlSharp.Native.` holds XDL's runtime for one platform, from the XDL release: `linux-x64`, `linux-arm64`, `osx-x64`, `osx-arm64`, `win-x64`, `win-arm64` or `browser-wasm`. An app references the packages of the platforms it runs on. +- `XdlSharp.Native.browser-wasm` links its archives into `dotnet.native.wasm` and adds `--use-port=emdawnwebgpu` to the link. The link builds that Emscripten port into the Emscripten cache, and the workload's own cache is read-only, so set `WasmCachePath` to a writable directory. + +The version is the SDL version and a release number, such as `3.4.20.1`. The package description names the XDL release, such as `3.4.20-4`. + ## Mapping - Functions, constants, hints and property names of all four libraries are static members of `XdlSharp.Xdl`; types are in `XdlSharp`. Each function loads from its own library: `IMG_` functions from `XDL3_image`, and so on. @@ -27,7 +35,7 @@ Pointer window = SDL_CreateWindow("Title"u8, 640, 480, SDL_WindowFla ## XDL release -`XDL_VERSION` pins the XDL release. Building the generator or a test project runs `tools/XdlRelease.proj`, which downloads that release's headers, the runtime for this machine and the browser archives into `artifacts/xdl/`. +`XDL_VERSION` pins the XDL release. Building the generator or a test project runs `tools/XdlRelease.proj`, which downloads that release's headers, the runtime for this machine and the browser archives into `artifacts/xdl/`. Packing a native package downloads the runtime it packs. ## Generating @@ -57,6 +65,14 @@ dotnet publish tests/XdlSharp.Browser.Tests cd tests/XdlSharp.Browser.Tests/bin/Release/net11.0/publish/wwwroot && node main.mjs ``` +## Releasing + +The Build workflow runs on every pull request. It checks the generated code with clang 22, runs the tests on the desktop and in the browser, and packs the packages. + +The Release workflow publishes every push to `main`. For SDL 3.4.20, the first release is `3.4.20.1`, and each later one adds 1 to the highest `v3.4.20.` tag. The workflow pushes the packages to nuget.org through Trusted Publishing, and then tags the commit `v`. + +To pack locally, run `tools/pack.sh [version]`. Without a version, the packages are marked `local`, such as `3.4.20-local`. + ## Limits - The headers are parsed for Linux, so declarations that only other platforms see, such as `SDL_SetWindowsMessageHook`, are missing. The generator runs only on Linux. diff --git a/XDL_VERSION b/XDL_VERSION index 7e6f81d..edb0958 100644 --- a/XDL_VERSION +++ b/XDL_VERSION @@ -1 +1 @@ -3.4.20-3 +3.4.20-4 diff --git a/XdlSharp.slnx b/XdlSharp.slnx index 2f47ef9..22c0fa6 100644 --- a/XdlSharp.slnx +++ b/XdlSharp.slnx @@ -1,4 +1,7 @@ + + + diff --git a/packaging/XdlSharp.Native/XdlSharp.Native.csproj b/packaging/XdlSharp.Native/XdlSharp.Native.csproj new file mode 100644 index 0000000..b8bc13a --- /dev/null +++ b/packaging/XdlSharp.Native/XdlSharp.Native.csproj @@ -0,0 +1,37 @@ + + + + + XdlSharp.Native.$(XdlRuntimeIdentifier) + The $(XdlRuntimeIdentifier) runtime of XDL $(XdlVersion), the altered SDL $(XdlSdlVersion), with SDL_image, SDL_ttf and SDL_mixer built against it, for XdlSharp. + true + + $(XdlRuntimeIdentifier) + browser-wasm-net11 + $(XdlTarget) + false + true + + $(NoWarn);NU5128 + $(TargetsForTfmSpecificContentInPackage);AddXdlRuntimeToPackage + + + + + + + + + + + <_XdlRuntimeFile Include="$(XdlReleaseDirectory)runtimes/$(XdlRuntimeIdentifier)/native/*" /> + + + + + + + diff --git a/packaging/XdlSharp.Native/buildTransitive/XdlSharp.Native.browser-wasm.targets b/packaging/XdlSharp.Native/buildTransitive/XdlSharp.Native.browser-wasm.targets new file mode 100644 index 0000000..b0076ea --- /dev/null +++ b/packaging/XdlSharp.Native/buildTransitive/XdlSharp.Native.browser-wasm.targets @@ -0,0 +1,14 @@ + + + + + + + + $(EmccExtraLDFlags) --use-port=emdawnwebgpu + + diff --git a/src/XdlSharp/XdlSharp.csproj b/src/XdlSharp/XdlSharp.csproj index 6e495e9..d895eaa 100644 --- a/src/XdlSharp/XdlSharp.csproj +++ b/src/XdlSharp/XdlSharp.csproj @@ -2,10 +2,13 @@ XdlSharp + Low-level C# bindings for XDL $(XdlVersion), the altered SDL $(XdlSdlVersion), and for SDL_image, SDL_ttf and SDL_mixer built against it. The native libraries are in XdlSharp.Native.<rid>. + README.md + diff --git a/tools/XdlRelease.proj b/tools/XdlRelease.proj index f8ec76e..31872f0 100644 --- a/tools/XdlRelease.proj +++ b/tools/XdlRelease.proj @@ -1,8 +1,8 @@ @@ -12,9 +12,10 @@ - + - <_XdlZip Include="xdl3-headers.zip;xdl3-$(XdlRuntimeIdentifier).zip;xdl3-browser-wasm-net11.zip" /> + <_XdlTarget Include="$(XdlTargets)" /> + <_XdlZip Include="@(_XdlTarget->'xdl3-%(Identity).zip')" /> <_MissingXdlZip Include="@(_XdlZip)" Condition="!Exists('$(_DownloadDirectory)%(Identity)')" /> diff --git a/tools/pack.sh b/tools/pack.sh new file mode 100755 index 0000000..764ca0f --- /dev/null +++ b/tools/pack.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Packs XdlSharp and an XdlSharp.Native. package for each runtime of the XDL release into . +# usage: tools/pack.sh [version] +# Without a version, or with an empty one, the packages are marked local, as a release passes .. +set -euo pipefail + +output=$(realpath -m "$1") +version_argument=() +if [[ -n "${2:-}" ]]; then + version_argument=("-p:Version=$2") +fi + +root=$(dirname "$(realpath "$0")")/.. +# The runtimes of every XDL release; the browser one is the XDL target browser-wasm-net11. +runtimes=(linux-x64 linux-arm64 osx-x64 osx-arm64 win-x64 win-arm64 browser-wasm) + +dotnet pack "$root/src/XdlSharp" -c Release -o "$output" "${version_argument[@]}" +for runtime in "${runtimes[@]}"; do + dotnet pack "$root/packaging/XdlSharp.Native" -c Release -o "$output" "-p:XdlRuntimeIdentifier=$runtime" "${version_argument[@]}" +done From c26ad5c536f68972e7580589e613fa8e1e7951b9 Mon Sep 17 00:00:00 2001 From: botoddly <250804054+botoddly@users.noreply.github.com> Date: Mon, 9 Nov 2026 10:32:21 +0100 Subject: [PATCH 2/3] License XdlSharp under zlib as XDL is and declare the licenses of the packages --- LICENSE | 23 +++++++++++++++++++ README.md | 4 ++++ .../XdlSharp.Native/XdlSharp.Native.csproj | 2 ++ src/XdlSharp/XdlSharp.csproj | 1 + 4 files changed, 30 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3cf869b --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +XdlSharp +Copyright (C) 2026 Stan (stanoddly) + +Portions of this software are generated from the headers of XDL, which are +derived from Simple DirectMedia Layer, and from the headers of SDL_image, +SDL_ttf and SDL_mixer +Copyright (C) 1997-2026 Sam Lantinga + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/README.md b/README.md index 5df4702..5277d22 100644 --- a/README.md +++ b/README.md @@ -79,3 +79,7 @@ To pack locally, run `tools/pack.sh [version]`. Without a version, the - `SDL_main.h` is not bound: `SDL.h` does not include it. - `SDL_vulkan.h` is not bound: `SDL.h` does not include it, and `VkSurfaceKHR` is a pointer on 64-bit targets but a `uint64_t` on wasm32. - `wchar_t*` is `nint`, because `wchar_t` is 2 bytes on Windows and 4 elsewhere. + +## License + +XdlSharp uses the zlib license, as XDL does. See [LICENSE](LICENSE). Each `XdlSharp.Native.` package carries the release's `THIRD-PARTY-NOTICES.txt` as its license: XDL's license and the licenses of the third-party code in its libraries. diff --git a/packaging/XdlSharp.Native/XdlSharp.Native.csproj b/packaging/XdlSharp.Native/XdlSharp.Native.csproj index b8bc13a..50284bc 100644 --- a/packaging/XdlSharp.Native/XdlSharp.Native.csproj +++ b/packaging/XdlSharp.Native/XdlSharp.Native.csproj @@ -12,6 +12,8 @@ $(XdlRuntimeIdentifier) browser-wasm-net11 $(XdlTarget) + + THIRD-PARTY-NOTICES.txt false true diff --git a/src/XdlSharp/XdlSharp.csproj b/src/XdlSharp/XdlSharp.csproj index d895eaa..ce4fb36 100644 --- a/src/XdlSharp/XdlSharp.csproj +++ b/src/XdlSharp/XdlSharp.csproj @@ -4,6 +4,7 @@ XdlSharp Low-level C# bindings for XDL $(XdlVersion), the altered SDL $(XdlSdlVersion), and for SDL_image, SDL_ttf and SDL_mixer built against it. The native libraries are in XdlSharp.Native.<rid>. README.md + Zlib From 8e51773c4f0dee79b68491d1da87a23d5575f5cf Mon Sep 17 00:00:00 2001 From: botoddly <250804054+botoddly@users.noreply.github.com> Date: Mon, 9 Nov 2026 11:08:04 +0100 Subject: [PATCH 3/3] Test the packed packages on each runner, guard releases against reused or reordered versions, and keep browser archives out of app output --- .github/workflows/build.yml | 84 +++++++++++++++++-- .github/workflows/release.yml | 61 ++++++++++---- README.md | 10 +-- .../XdlSharp.Native/XdlSharp.Native.csproj | 10 ++- .../XdlSharp.Native.browser-wasm.targets | 2 +- .../XdlSharp.Package.Browser.Tests.csproj | 26 ++++++ .../wwwroot/main.mjs | 4 + tests/XdlSharp.Package.Tests/Program.cs | 17 ++++ .../XdlSharp.Package.Tests.csproj | 23 +++++ tests/XdlSharp.Tests/XdlSharp.Tests.csproj | 2 +- tools/pack.sh | 17 ++-- 11 files changed, 221 insertions(+), 35 deletions(-) create mode 100644 tests/XdlSharp.Package.Browser.Tests/XdlSharp.Package.Browser.Tests.csproj create mode 100644 tests/XdlSharp.Package.Browser.Tests/wwwroot/main.mjs create mode 100644 tests/XdlSharp.Package.Tests/Program.cs create mode 100644 tests/XdlSharp.Package.Tests/XdlSharp.Package.Tests.csproj diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 642db80..c9d2732 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ on: workflow_call: inputs: version: - description: The version of the packages, .. Without it the packages are marked local. + description: The version of the packages, .. Without it the version is marked local. required: false type: string @@ -21,10 +21,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install the .NET 11 preview SDK - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: '11.0.x' dotnet-quality: 'preview' @@ -47,7 +47,7 @@ jobs: run: tools/pack.sh artifacts/packages "$VERSION" - name: Upload the packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: packages path: artifacts/packages @@ -59,10 +59,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install the .NET 11 preview SDK - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: '11.0.x' dotnet-quality: 'preview' @@ -76,3 +76,75 @@ jobs: - name: Run the browser tests working-directory: tests/XdlSharp.Browser.Tests/bin/Release/net11.0/publish/wwwroot run: node main.mjs + + # The packages as an app restores them, on each platform that has a hosted runner. osx-x64 and win-arm64 have none. + packages: + name: Run the packages on ${{ matrix.os }} + needs: desktop + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, ubuntu-24.04-arm, windows-latest, macos-latest] + + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Download the packages + uses: actions/download-artifact@v8 + with: + name: packages + path: packages + + - name: Install the .NET 11 preview SDK + uses: actions/setup-dotnet@v6 + with: + dotnet-version: '11.0.x' + dotnet-quality: 'preview' + + - name: Run the packages + shell: bash + run: | + version=$(basename packages/XdlSharp.[0-9]*.nupkg .nupkg) + dotnet run --project tests/XdlSharp.Package.Tests "-p:XdlSharpPackageVersion=${version#XdlSharp.}" "-p:RestoreAdditionalProjectSources=$GITHUB_WORKSPACE/packages" + + packages-browser: + name: Run the packages in the browser runtime + needs: desktop + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Download the packages + uses: actions/download-artifact@v8 + with: + name: packages + path: packages + + - name: Install the .NET 11 preview SDK + uses: actions/setup-dotnet@v6 + with: + dotnet-version: '11.0.x' + dotnet-quality: 'preview' + + - name: Install the wasm-tools workload + run: dotnet workload install wasm-tools + + - name: Publish the packages in a browser app + run: | + version=$(basename packages/XdlSharp.[0-9]*.nupkg .nupkg) + dotnet publish tests/XdlSharp.Package.Browser.Tests "-p:XdlSharpPackageVersion=${version#XdlSharp.}" "-p:RestoreAdditionalProjectSources=$GITHUB_WORKSPACE/packages" + + # The archives are linked into dotnet.native.wasm, so a copy in the output means the package puts them where NuGet copies files. + - name: Check that no archive is in the published app + run: | + if find tests/XdlSharp.Package.Browser.Tests/bin/Release/net11.0/publish -name '*.a' | grep .; then + exit 1 + fi + + - name: Run the browser app + working-directory: tests/XdlSharp.Package.Browser.Tests/bin/Release/net11.0/publish/wwwroot + run: node main.mjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4ce45aa..845fd5e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,10 +10,11 @@ on: permissions: contents: read -# Two runs at once would take the same N. +# Two runs at once would take the same N. By default a new run cancels a pending one, so its merge would have no release of its own. concurrency: group: release cancel-in-progress: false + queue: max jobs: resolve: @@ -26,19 +27,22 @@ jobs: steps: # A shallow checkout has no tags, so every run would take N = 1. - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Find the version id: version run: | - if [[ -n "$(git tag --points-at HEAD 'v*')" ]]; then - echo "$(git tag --points-at HEAD 'v*') is released already." + sdl_version=$(cut -d- -f1 XDL_VERSION) + # The queue does not keep the order of the runs. A later commit released for the same SDL version contains this one, so this + # release would publish older code as a newer version. + released=$(git tag --contains HEAD "v$sdl_version.*") + if [[ -n "$released" ]]; then + echo "Released already in ${released//$'\n'/, }." exit 0 fi - sdl_version=$(cut -d- -f1 XDL_VERSION) last=0 for tag in $(git tag --list "v$sdl_version.*"); do n=${tag#"v$sdl_version."} @@ -55,28 +59,42 @@ jobs: with: version: ${{ needs.resolve.outputs.version }} + # Without a checkout or contents: write, so the steps that can get a push key from nuget.org hold no token that writes to the repository. publish: needs: [resolve, build] runs-on: ubuntu-24.04 permissions: - contents: write # Trusted Publishing: nuget.org exchanges this workflow's OIDC token for a short-lived API key. id-token: write env: VERSION: ${{ needs.resolve.outputs.version }} steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Download the packages - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: packages path: packages + # A tag push that failed after the packages were published leaves N untagged, and the next merge would take it again. The push would + # skip every package nuget.org has as a duplicate, and the tag would name a commit whose code is not on nuget.org. + - name: Check that nuget.org has no other commit as this version + run: | + for package in packages/*.nupkg; do + id=$(basename "$package" ".$VERSION.nupkg") + id=${id,,} + status=$(curl -sS -o nuspec -w '%{http_code}' "https://api.nuget.org/v3-flatcontainer/$id/$VERSION/$id.nuspec") + if [[ "$status" == 200 ]] && ! grep -q "commit=\"$GITHUB_SHA\"" nuspec; then + echo "nuget.org has $id $VERSION from another commit. Tag that commit v$VERSION, then run Release on main again, not only its failed jobs." >&2 + exit 1 + elif [[ "$status" != 200 && "$status" != 404 ]]; then + echo "nuget.org answered $status for $id $VERSION." >&2 + exit 1 + fi + done + - name: Install the .NET 11 preview SDK - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: '11.0.x' dotnet-quality: 'preview' @@ -92,8 +110,23 @@ jobs: NUGET_API_KEY: ${{ steps.login.outputs.NUGET_API_KEY }} run: dotnet nuget push "packages/*.nupkg" --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json --skip-duplicate - # After the push, so a tag always names a published version. + # After the push, so a tag always names a published version. + tag: + needs: [resolve, publish] + runs-on: ubuntu-24.04 + permissions: + contents: write + env: + GH_TOKEN: ${{ github.token }} + TAG: v${{ needs.resolve.outputs.version }} + + steps: + # A rerun after the tag was created finds it on this commit. - name: Tag the release run: | - git tag "v$VERSION" "$GITHUB_SHA" - git push origin "v$VERSION" + tagged=$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG" -q .object.sha 2>/dev/null || true) + if [[ "$tagged" == "$GITHUB_SHA" ]]; then + echo "$TAG is on this commit already." + else + gh api "repos/$GITHUB_REPOSITORY/git/refs" -f ref="refs/tags/$TAG" -f sha="$GITHUB_SHA" + fi diff --git a/README.md b/README.md index 5277d22..f7afb28 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Pointer window = SDL_CreateWindow("Title"u8, 640, 480, SDL_WindowFla - `XdlSharp` holds the bindings. It is the same for every platform and holds no native library. - `XdlSharp.Native.` holds XDL's runtime for one platform, from the XDL release: `linux-x64`, `linux-arm64`, `osx-x64`, `osx-arm64`, `win-x64`, `win-arm64` or `browser-wasm`. An app references the packages of the platforms it runs on. -- `XdlSharp.Native.browser-wasm` links its archives into `dotnet.native.wasm` and adds `--use-port=emdawnwebgpu` to the link. The link builds that Emscripten port into the Emscripten cache, and the workload's own cache is read-only, so set `WasmCachePath` to a writable directory. +- `XdlSharp.Native.browser-wasm` links its archives into `dotnet.native.wasm` and adds `--use-port=emdawnwebgpu` to the link. The link builds that Emscripten port into the Emscripten cache. The workload's own cache is read-only, so set `WasmCachePath` to a writable directory. The version is the SDL version and a release number, such as `3.4.20.1`. The package description names the XDL release, such as `3.4.20-4`. @@ -67,11 +67,11 @@ cd tests/XdlSharp.Browser.Tests/bin/Release/net11.0/publish/wwwroot && node main ## Releasing -The Build workflow runs on every pull request. It checks the generated code with clang 22, runs the tests on the desktop and in the browser, and packs the packages. +The Build workflow runs on every pull request. It checks the generated code with clang 22, runs the tests on the desktop and in the browser, and packs the packages. Then `tests/XdlSharp.Package.Tests` restores the packages and loads each library, as an app does, on linux-x64, linux-arm64, win-x64 and osx-arm64. `tests/XdlSharp.Package.Browser.Tests` does the same in the browser. -The Release workflow publishes every push to `main`. For SDL 3.4.20, the first release is `3.4.20.1`, and each later one adds 1 to the highest `v3.4.20.` tag. The workflow pushes the packages to nuget.org through Trusted Publishing, and then tags the commit `v`. +The Release workflow publishes every push to `main`. For SDL 3.4.20, the first release is `3.4.20.1`, and each later one adds 1 to the highest `v3.4.20.` tag. The workflow pushes the packages to nuget.org through Trusted Publishing, and then tags the commit `v`. It stops when nuget.org already has that version from another commit. -To pack locally, run `tools/pack.sh [version]`. Without a version, the packages are marked `local`, such as `3.4.20-local`. +To pack locally, run `tools/pack.sh [version]`. Without a version, the version is marked local with the time, such as `3.4.20-local.20261109103000`, so a consumer never restores an older local pack from its package cache. ## Limits @@ -82,4 +82,4 @@ To pack locally, run `tools/pack.sh [version]`. Without a version, the ## License -XdlSharp uses the zlib license, as XDL does. See [LICENSE](LICENSE). Each `XdlSharp.Native.` package carries the release's `THIRD-PARTY-NOTICES.txt` as its license: XDL's license and the licenses of the third-party code in its libraries. +XdlSharp uses the zlib license, as XDL does. See [LICENSE](https://github.com/stanoddly/XDLSharp/blob/main/LICENSE). Each `XdlSharp.Native.` package carries the release's `THIRD-PARTY-NOTICES.txt` as its license: XDL's license and the licenses of the third-party code in its libraries. diff --git a/packaging/XdlSharp.Native/XdlSharp.Native.csproj b/packaging/XdlSharp.Native/XdlSharp.Native.csproj index 50284bc..82c0521 100644 --- a/packaging/XdlSharp.Native/XdlSharp.Native.csproj +++ b/packaging/XdlSharp.Native/XdlSharp.Native.csproj @@ -3,6 +3,7 @@ XdlSharp.Native.$(XdlRuntimeIdentifier) @@ -14,13 +15,20 @@ $(XdlTarget) THIRD-PARTY-NOTICES.txt + README.md false + runtimes/$(XdlRuntimeIdentifier)/native/ + buildTransitive/native/ true $(NoWarn);NU5128 $(TargetsForTfmSpecificContentInPackage);AddXdlRuntimeToPackage + + + + @@ -30,7 +38,7 @@ <_XdlRuntimeFile Include="$(XdlReleaseDirectory)runtimes/$(XdlRuntimeIdentifier)/native/*" /> - + diff --git a/packaging/XdlSharp.Native/buildTransitive/XdlSharp.Native.browser-wasm.targets b/packaging/XdlSharp.Native/buildTransitive/XdlSharp.Native.browser-wasm.targets index b0076ea..eee05d4 100644 --- a/packaging/XdlSharp.Native/buildTransitive/XdlSharp.Native.browser-wasm.targets +++ b/packaging/XdlSharp.Native/buildTransitive/XdlSharp.Native.browser-wasm.targets @@ -5,7 +5,7 @@ into the Emscripten cache, so WasmCachePath must name a writable cache. --> - + diff --git a/tests/XdlSharp.Package.Browser.Tests/XdlSharp.Package.Browser.Tests.csproj b/tests/XdlSharp.Package.Browser.Tests/XdlSharp.Package.Browser.Tests.csproj new file mode 100644 index 0000000..1141fc6 --- /dev/null +++ b/tests/XdlSharp.Package.Browser.Tests/XdlSharp.Package.Browser.Tests.csproj @@ -0,0 +1,26 @@ + + + + + Exe + false + false + false + $([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)', '..', '..', 'artifacts', 'em-cache')) + + + + + + + + + + + + + diff --git a/tests/XdlSharp.Package.Browser.Tests/wwwroot/main.mjs b/tests/XdlSharp.Package.Browser.Tests/wwwroot/main.mjs new file mode 100644 index 0000000..d5b3c77 --- /dev/null +++ b/tests/XdlSharp.Package.Browser.Tests/wwwroot/main.mjs @@ -0,0 +1,4 @@ +import { dotnet } from './_framework/dotnet.js'; + +const exitCode = await dotnet.run(); +process.exit(exitCode); diff --git a/tests/XdlSharp.Package.Tests/Program.cs b/tests/XdlSharp.Package.Tests/Program.cs new file mode 100644 index 0000000..5a3b44b --- /dev/null +++ b/tests/XdlSharp.Package.Tests/Program.cs @@ -0,0 +1,17 @@ +using static XdlSharp.Xdl; + +// Each version call loads one library of the native package, and the satellites load XDL3 next to them. +(string Name, int Loaded, int Header)[] libraries = +[ + ("XDL3", SDL_GetVersion(), SDL_VERSION), + ("XDL3_image", IMG_Version(), SDL_IMAGE_VERSION), + ("XDL3_ttf", TTF_Version(), SDL_TTF_VERSION), + ("XDL3_mixer", MIX_Version(), SDL_MIXER_VERSION), +]; + +foreach ((string name, int loaded, int header) in libraries) +{ + Console.WriteLine($"{name}: {loaded}, the header says {header}"); +} + +return libraries.All(library => library.Loaded == library.Header) ? 0 : 1; diff --git a/tests/XdlSharp.Package.Tests/XdlSharp.Package.Tests.csproj b/tests/XdlSharp.Package.Tests/XdlSharp.Package.Tests.csproj new file mode 100644 index 0000000..9acce11 --- /dev/null +++ b/tests/XdlSharp.Package.Tests/XdlSharp.Package.Tests.csproj @@ -0,0 +1,23 @@ + + + + + Exe + false + false + + + + + + + + + + + + diff --git a/tests/XdlSharp.Tests/XdlSharp.Tests.csproj b/tests/XdlSharp.Tests/XdlSharp.Tests.csproj index 16803b5..bf1e947 100644 --- a/tests/XdlSharp.Tests/XdlSharp.Tests.csproj +++ b/tests/XdlSharp.Tests/XdlSharp.Tests.csproj @@ -4,7 +4,7 @@ false true $([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)', '..', 'native')) - $([MSBuild]::NormalizeDirectory('$(BaseIntermediateOutputPath)', 'native')) + $([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '$(BaseIntermediateOutputPath)', 'native')) diff --git a/tools/pack.sh b/tools/pack.sh index 764ca0f..fe369c3 100755 --- a/tools/pack.sh +++ b/tools/pack.sh @@ -1,20 +1,23 @@ #!/usr/bin/env bash # Packs XdlSharp and an XdlSharp.Native. package for each runtime of the XDL release into . # usage: tools/pack.sh [version] -# Without a version, or with an empty one, the packages are marked local, as a release passes .. +# Without a version, or with an empty one, the version is marked local with the time, such as 3.4.20-local.20261109103000, so a +# consumer never restores an older local pack from its package cache. A release passes .. set -euo pipefail -output=$(realpath -m "$1") -version_argument=() +mkdir -p "${1:?usage: tools/pack.sh [version]}" +output=$(CDPATH= cd "$1" && pwd) +root=$(CDPATH= cd "$(dirname "$0")/.." && pwd) if [[ -n "${2:-}" ]]; then - version_argument=("-p:Version=$2") + version_argument="-p:Version=$2" +else + version_argument="-p:VersionSuffix=local.$(date -u +%Y%m%d%H%M%S)" fi -root=$(dirname "$(realpath "$0")")/.. # The runtimes of every XDL release; the browser one is the XDL target browser-wasm-net11. runtimes=(linux-x64 linux-arm64 osx-x64 osx-arm64 win-x64 win-arm64 browser-wasm) -dotnet pack "$root/src/XdlSharp" -c Release -o "$output" "${version_argument[@]}" +dotnet pack "$root/src/XdlSharp" -c Release -o "$output" "$version_argument" for runtime in "${runtimes[@]}"; do - dotnet pack "$root/packaging/XdlSharp.Native" -c Release -o "$output" "-p:XdlRuntimeIdentifier=$runtime" "${version_argument[@]}" + dotnet pack "$root/packaging/XdlSharp.Native" -c Release -o "$output" "-p:XdlRuntimeIdentifier=$runtime" "$version_argument" done