GitHub user Fatorin created a discussion: Release Workflow Design for OpenDAL 
.NET Binding

## Summary

The .NET binding publish pipeline was introduced in 
[#7323](https://github.com/apache/opendal/pull/7323).
Before cutting the first release, there are two design decisions that need 
community input.
Both are strongly tied to how NuGet (the .NET package platform) works.

**Key NuGet platform constraint**:
Unlike PyPI (`test.pypi.org`) or Maven (staging repositories), **NuGet has no 
staging or test feed**.
Pre-release packages are published directly to nuget.org — but they are 
**hidden by default** from all standard tooling (Visual Studio, `dotnet` CLI) 
unless users explicitly opt in with `--prerelease`.
This is the standard practice across the .NET ecosystem, including the .NET SDK 
itself.

---

## 1. Testing the Release Workflow via `workflow_dispatch`

I'd like to run a few `workflow_dispatch` builds to validate that the full CI 
pipeline (native build matrix → pack → publish) works end-to-end before we tag 
a real release.
Since NuGet packages **cannot be deleted** (only unlisted), this needs to be 
done carefully.

**The current workflow does not properly handle `workflow_dispatch` 
versioning** — it only derives the package version from git tags, so manual 
dispatch builds fall back to the bare `.csproj` version without any pre-release 
suffix.
I plan to submit a PR to fix this, adding a `preview.{run_number}` suffix for 
`workflow_dispatch` builds so each run produces a unique pre-release version.

The proposed test plan after the CI fix:

1. **Dry run** — `workflow_dispatch` with `dotnet_publish = false`.
   Validate that the build matrix and pack step produce the correct artifact.
   No package is published.

2. **Publish test** — `workflow_dispatch` with `dotnet_publish = true`.
   Publish a `preview` pre-release package to nuget.org and verify installation 
from a consumer project on all three platforms.

For tag-triggered releases, the workflow follows the **.NET SDK's own 
lifecycle** (`preview` → `rc` → `stable`):

| Trigger | Version | Example |
|---------|---------|---------|
| `workflow_dispatch` | `<csproj Version>-preview.{run_number}` | 
`0.1.0-preview.42` |
| Tag with pre-release suffix | Use tag as-is | `v0.1.0-rc.1` → `0.1.0-rc.1` |
| Tag without suffix (safety net) | Auto-append `-rc` | `v0.1.0` → `0.1.0-rc` |

The `-rc` safety net on bare tags prevents accidental stable releases and will 
be removed once the workflow is validated.

**Are there any concerns before I proceed with the CI fix and testing?**

---

## 2. Tag Namespace and Versioning

Following the precedent of other OpenDAL bindings (Java `0.48.2`, Node.js 
`0.49.2`, Go `0.1.x` — all independent from core `0.55.0`), the .NET binding 
will also use **independent versioning**, starting at `0.1.0`.

### Current behavior and the problem

The current .NET workflow triggers on `v[0-9]+.[0-9]+.[0-9]+*` and **derives 
the package version from the tag**.
This is the same pattern used by core and Node.js.

Since all bindings version independently, this causes a conflict:
when core pushes `v0.55.0` or `v0.56.0`, the .NET workflow is triggered and 
would attempt to publish `0.55.0` or `0.56.0` — versions that have nothing to 
do with the .NET binding.
This is especially dangerous because NuGet packages **cannot be deleted**, only 
unlisted.

Note: **Java has the same issue in theory** — its tag pattern `v*-rc.*` would 
also match RC tags from other bindings.
In practice this hasn't caused problems because Maven uses a staging repository 
where mistakenly staged artifacts can be dropped before promotion.
NuGet has no such safety net.

### Why reading from `.csproj` doesn't fully solve it either

One idea is to ignore the tag version and always read from `.csproj` instead.
But this creates a different problem: **version collisions**.

If the `.csproj` is at `0.1.0` and we append `-rc` on every tag push:
- Core pushes `v0.55.0` → .NET workflow produces `0.1.0-rc`
- Core pushes `v0.56.0` → .NET workflow produces `0.1.0-rc` again (same 
version, `--skip-duplicate` skips it)
- Core pushes `v0.55.0-rc.1` → .NET workflow produces `0.1.0-rc` again

Every unrelated tag triggers a wasted build, and if any of them happens to be 
the first run, it publishes a `0.1.0-rc` that wasn't intentionally released.

### The core question

The shared `v*` tag namespace is **not viable** for the .NET binding — whether 
we read the version from the tag or from `.csproj`, unrelated tags will either 
publish wrong versions or trigger wasted/colliding builds.

This means we need a .NET-specific trigger mechanism.
The options are:

1. **Binding-specific tag prefix** (e.g., `dotnet-v0.1.0-rc.1`).
   Maintainers push a dedicated tag when they want to release the .NET binding.
   Version comes from the tag, no ambiguity.

2. **`workflow_dispatch` only** — remove tag-triggered publishing entirely.
   Before each release, manually update `.csproj` `<Version>`, merge, then 
trigger the workflow.
   But for **urgent releases** (e.g., a critical bug fix), this requires 
multiple steps: update `.csproj` → merge PR → manually trigger workflow — which 
adds friction in time-sensitive situations.

**How should we handle the .NET binding's release trigger?**

---

## Appendix: How Other OpenDAL Bindings Handle Versioning

For reference, here is how the existing Java and Node.js bindings handle 
versioning and releases.
All bindings use **independent versioning** — none of them align with the Rust 
core version.

| | Java | Node.js | .NET (current) |
|--|------|---------|----------------|
| **Binding version** | `0.48.2` | `0.49.2` | `0.1.0` |
| **Rust core version** | `0.55.0` | `0.55.0` | `0.55.0` |
| **Tag trigger** | `v*-rc.[0-9]+` (RC only) | `v*` | `v*` |
| **`workflow_dispatch`** | Yes, no parameters | Yes, dry-run + publish toggles 
| Yes, publish toggle |
| **Publish platform** | Maven (Nexus staging) | npm | NuGet |

Key observations:

- **Java** only accepts `-rc.N` tags because Maven has a staging repository 
mechanism.
  RC packages are pushed to Nexus staging, reviewed, and then promoted to Maven 
Central as a stable release.
  The tag itself is always an RC, but the final published artifact on Maven 
Central is a stable version.

- **Node.js** triggers on any `v*` tag and publishes directly to npm.
  Similar to NuGet, npm has no staging feed, so the tag version is the 
published version.

- **Both bindings version independently from core.**
  The Rust core is at `0.55.0`, while Java is at `0.48.2` and Node.js is at 
`0.49.2`.
  This is explicitly documented in 
[`bindings/README.md`](https://github.com/apache/opendal/blob/main/bindings/README.md).

---

## References

For context, here is how other projects in the NuGet ecosystem handle 
pre-releases:

| Project | Pre-release Pattern | Strategy |
|---------|-------------------|----------|
| **.NET SDK** | `-preview.N` → `-rc.N` → stable | Full lifecycle on nuget.org |
| **Npgsql** | `-rc.N` on nuget.org, CI on MyGet | Separate feed for CI builds |
| **Polly** | `-beta.N` | Direct to nuget.org |
| **Serilog** | `-dev-NNNNN` | Every CI build to nuget.org |
| **MassTransit** | `-develop.NNN` | Every CI build to nuget.org |

All of these publish pre-releases directly to nuget.org.
NuGet's default hiding of pre-release packages makes this safe for end users.

---

Feedback on both topics is welcome — especially from maintainers with 
experience in other binding release workflows.


GitHub link: https://github.com/apache/opendal/discussions/7394

----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to: [email protected]

Reply via email to