Contributing to AvroSharp

Ground rules

  • Implement from the specification. AvroSharp is a clean-room implementation of the Apache Avro™ specification under the MIT license. Apache.Avro (Apache-2.0) may be read for design ideas and used as a test oracle, but its code must not be copied or ported into src/. Code derived from Chr.Avro (MIT) must keep its copyright notice in THIRD-PARTY-NOTICES.md.

  • No reflection on serialization paths. Typed serialization is produced by source generators and must stay Native AOT and trimming compatible.

  • Performance is a feature. AvroSharp must be faster than Apache.Avro, with fewer allocations, on every scenario in the benchmark suite. Benchmarks run locally only, on request, on an idle machine (never in CI):

    dotnet run -c Release --project bench/AvroSharp.Benchmarks -f net10.0 -- --filter '*' --runtimes net8.0 net9.0 net10.0 --memory --gate
    
  • Codecs use fully managed libraries only. No native binaries and no P/Invoke.

  • Tests must exercise the product path. Write the test, revert the fix, and confirm the test fails.

Building and testing

dotnet build -c Release
dotnet test --solution AvroSharp.slnx -c Release -f net10.0

Locally, net10.0 is enough while working. CI runs net8.0, net9.0 and net10.0 on Linux and Windows, x64 and Arm64, and on Windows also net481, which tests the netstandard2.0 build on .NET Framework. Run -f net481 locally too when a change touches the netstandard code paths.

Formatting is not checked in CI. To check or fix it locally:

dotnet format AvroSharp.slnx --verify-no-changes   # check
dotnet format AvroSharp.slnx                       # fix

Native AOT smoke test:

dotnet publish tests/AvroSharp.AotSmoke -c Release -r win-x64   # or linux-x64

The documentation site, with the same DocFX version and broken-link check as the docs workflow:

dotnet tool restore
dotnet tool run docfx docfx.json --serve   # http://localhost:8080

Dev container

.devcontainer/ builds and tests as the Linux CI job does, with no local .NET setup. It has:

  • SDKs: Ubuntu 24.04 (as ubuntu-latest) with the .NET 8, 9 and 10 SDKs;
  • Native AOT: its prerequisites;
  • Tools: the repository's local tools (DocFX, ReportGenerator);
  • Environment: CI's variables;
  • Cache: the NuGet cache in a volume, so rebuilding the container doesn't download it again.

Open the repository in it with VS Code (Dev Containers: Reopen in Container) or Rider, or create a GitHub Codespace from the repository page (Code > Codespaces). On Windows, use Dev Containers: Clone Repository in Container Volume, or a clone inside WSL. A clone on the Windows file system is slow when bind-mounted, and its permissions can break the build. Then run the Linux CI job's steps:

build/ci-local.sh

It runs, in CI's order:

  • restore and build;
  • the tests on net8.0, net9.0 and net10.0 with coverage, and the coverage check (the summary is in artifacts/coverage/SummaryGithub.md);
  • the tests without hardware intrinsics;
  • the samples;
  • the Native AOT smoke test;
  • pack, and the package consumers.

Behind a proxy that intercepts HTTPS, the image build and restores fail with certificate errors, because the container doesn't trust the proxy's root certificate the way the host does. Add the certificate in a local copy of the Dockerfile, and don't commit it: COPY proxy-root.crt /usr/local/share/ca-certificates/ and then RUN update-ca-certificates, right after FROM.

It takes about as long as the CI job. Passing it means passing the Linux CI job on the container's architecture: ubuntu-latest on x64, or ubuntu-24.04-arm on an Arm64 host such as an Apple silicon Mac.

It doesn't cover:

  • Windows and .NET Framework: the Windows jobs and the net481 tests run in CI only.
  • The other architecture: the container runs on the host's architecture, so only CI runs both.
  • Fuzzing: the nightly libFuzzer runs are in fuzz/README.md. The random-schema test runs with the other tests, on 100 schemas.
  • Benchmarks: these need a quiet, dedicated machine, not a container.

Public API

Public API is tracked with Microsoft.CodeAnalysis.PublicApiAnalyzers. Add new members to PublicAPI.Unshipped.txt; the build fails otherwise.

Workflow

Branch, open a pull request, and merge (squash) once CI is green. main is never pushed to directly.

Releasing

Versions come from git tags through MinVer: v1.2.3, or v1.2.3-alpha.1 for a pre-release.

  1. Prepare a release pull request, and merge it:
    • in CHANGELOG.md, rename ## [Unreleased] to ## [1.2.3] - YYYY-MM-DD (the exact version, pre-release suffix included), start a new empty [Unreleased] section, and update the links at the bottom: [Unreleased] compares the new tag with HEAD, and the new version links to its release;
    • move the API listings to Shipped: the lines of each PublicAPI.Unshipped.txt (and src/AvroSharp/PublicAPI/net8.0/) go into the PublicAPI.Shipped.txt beside it, and the rules in AnalyzerReleases.Unshipped.md go into AnalyzerReleases.Shipped.md under ## Release 1.2.3, so a later change to shipped API is reported;
    • check that the package READMEs' status lines and docs/roadmap.md still describe the release: package READMEs are packed into the immutable .nupkg.
  2. Rehearse: run the Release workflow manually on main with publish off. It tests on Linux and Windows (including net481), packs, and checks the release notes, but pushes nothing.
  3. Tag the merge commit and push the tag: git tag v1.2.3 && git push origin v1.2.3. The workflow tests again, packs, pushes the packages and symbols to nuget.org, and creates the GitHub release from the CHANGELOG section, marked as a pre-release when the version has a suffix. It fails if the tag and the packed version differ, or if the CHANGELOG has no section for the version.

The push uses nuget.org's trusted publishing, so no API key is stored. It needs, once:

  • on nuget.org: a trusted publishing policy for the nuget.org account that owns the AvroSharp* packages (zcsizmadia). The policy names repository owner zcsizmadia, repository AvroSharp, workflow file release.yml and environment nuget.
  • on GitHub: an environment named nuget (Settings → Environments; add required reviewers there to approve each publish), and a repository variable NUGET_USER holding that nuget.org account or organization name.

The workflow asks GitHub for an OIDC token, and NuGet/login exchanges it for a key that is valid for about an hour.