Contributing to CanKit.Pro¶
Thanks for helping. This page covers the three things that are specific to this repository: how branches and commits work (they decide releases), how to run the tests, and where a change belongs — here or upstream in CanKit.
Does your change belong here?¶
CanKit.Pro sits on top of CanKit, which it consumes as a NuGet package. It does not fork it.
- Adapters,
ICanBus,CanFrame, timing, the registry → upstream, at pkuyo/CanKit. We cannot fix those here, and a workaround layered on top usually makes the eventual upstream fix harder. - Demultiplexing, actors and scheduling, deadlines and bus state, CAN-ID arithmetic, and the protocol stacks above them → here.
Moving to a new CanKit version is a single change: bump CanKitVersion in
eng/Dependencies.props and let CI tell you whether anything broke.
Getting set up¶
git clone https://github.com/dborgards/CanKit.Pro.git
cd CanKit.Pro
dotnet build CanKit.Pro.sln -c Release
dotnet test CanKit.Pro.sln -c Release
You need the .NET 10 SDK or newer (global.json pins 10.0.100 and rolls forward to a later
major if that is what you have installed). No CAN hardware is needed — the whole suite runs on CanKit's virtual:// loopback
adapter and on in-repo test doubles.
A local build produces version 0.0.0, deliberately: nothing in the build computes a version, it
only receives one from the pipeline or from semantic-release. To see what GitVersion makes of your
working copy, ask it directly:
Optional but useful before pushing:
Branching¶
Trunk-based, one long-lived branch:
| Branch | Purpose |
|---|---|
main |
Always releasable. Every merge is analysed by semantic-release and may publish a release. |
feat/…, fix/…, docs/… |
Short-lived. Branch from main, open a pull request into main. |
There is no develop and no release branch: the version is computed from the commits, so a
staging branch would only add a place for the two to disagree.
Commits and pull-request titles decide the release¶
Pull requests are merged with a merge commit, and every commit on the branch is retained and
analysed by semantic-release — not only the pull-request title. git log --first-parent main
shows Merge pull request … throughout; the merge commit's own subject is not a Conventional
Commit and contributes nothing, so the branch commits are what decide the release.
Write every commit as a Conventional Commit, and keep the pull-request title one as well — it is what reviewers read, and it is what would decide the release if the repository ever switches to squash merging:
feat(rawcan): expose per-subscription drop counters -> minor release
fix(actor): stop Dispose deadlocking on the loop thread -> patch release
feat(addressing)!: rename ComposePgn parameters -> major release
docs: explain the deadline rearm semantics -> no release
Scopes match the packages: rawcan, actor, addressing, reliability, plus docs, ci,
build, deps.
A breaking change needs both the ! marker and a footer that says what to do about it:
feat(rawcan)!: ISubscription.Frames requires a CancellationToken
BREAKING CHANGE: `Frames` is now `Frames(CancellationToken)`. Callers using
`await foreach (var f in sub.Frames.WithCancellation(token))` become
`await foreach (var f in sub.Frames(token))`.
Types that never release on their own: docs, test, chore, ci, style, refactor. Use
them honestly — labelling a behaviour change as refactor means it ships with no changelog entry
and no version bump, which is worse than a noisy changelog.
Tests¶
Every behavioural change needs a test, and tests here are expected to be deterministic:
- No hardware, no timing luck. Use the
virtual://loopback adapter for anything about real bus behaviour, andControllableBus(tests/CanKit.Pro.Tests/Infrastructure/) when the test needs to control what the bus does — echo frames, bus state, whether a transmit is accepted.ControllableBus.DeferredEchoCapable(...)parks each TX echo in aDeferredEchoQueueinstead of raising it insideTransmit, which is the only way to have two sends pending at once: a synchronous echo re-entersCanBusService's pending-send lock on the transmitting thread, so the pending list never holds more than that thread's own entry. Reach for it whenever the behaviour under test is about how several in-flight sends relate to each other. - Do not test through an adapter's internals. If a test needs reflection into another package's private state, it is testing that package, not ours; drive the scenario through the double instead.
- Cite the requirement. Tests reference
FR-RAW-*IDs from the SRS so a reader can tell intended behaviour from incidental behaviour. Keep that up.
Run a single class while iterating:
The net48 leg¶
The libraries ship netstandard2.0 as well as net10.0, so the suite multi-targets
net10.0;net48 — net48 consumes the netstandard2.0 assets, which is the only way that build
is ever executed rather than merely compiled (SRS NFR-004). The extra target framework is added
only when the build runs on Windows, because nothing else can host it; on Linux and macOS
dotnet test runs net10.0 alone and the Windows CI job covers the rest.
A test that genuinely cannot run on .NET Framework belongs behind #if NET, not outside the
suite. To compile-check the net48 leg without a Windows machine:
Public API¶
These are published libraries, so the public surface is a promise:
- XML documentation on every public type and member. The existing code documents in English with
a Chinese translation, inherited from CanKit's own style — English alone is fine for new code.
Those comments are published verbatim:
eng/build-api-docs.shturns them into the API reference on every website build, so what you write there is what readers see, and theFR-…/ADR-…ids you cite become links into the SRS and the arc42 document. - Prefer adding an overload to changing a signature. If a break is genuinely right, mark it
!and write the migration into the footer. - New public types need a matching section in the package's
README.md, which ships inside the.nupkgand is what people actually read on nuget.org.
Reporting bugs¶
Use the issue templates. A reproduction on virtual:// endpoints is worth a great deal: it can go
into the test suite as-is, which usually means the fix ships in the next release rather than the
one after.
License¶
Contributions are accepted under the MIT License. There is no CLA — opening a pull request licenses your contribution under the repository's license. See docs/licensing.md.