Versioning policy
.NET nanoFramework has several things with a version or a version-like value: the NuGet packages of the class libraries, the firmware, the PE format and the native contract of each native assembly. This page says who sets each one and when it has to change. How they are generated and checked is explained in Package and assembly versions and the native contract hash.
The rule in one line
The native implementation never breaks the contract that the managed code was built for. Bug fixes and improvements in the native code are fine. Breaking changes are not allowed there: when a breaking change is needed, it starts in the C# API.
The rest of this page explains what that means in practice.
Summary
| What | Set by | Changes when |
|---|---|---|
| Version of a class library (the assembly and the NuGet package) | The developer sets major and minor in the version.json of the repository; the last number is automatic |
Major and minor: a deliberate managed breaking change (see below). Last number: every commit. |
| Native contract hash of an assembly | Nobody. It's computed by the metadata processor at build time | The native methods or the field layout of the assembly change, which means that the C# declarations changed |
| Firmware (nanoCLR) version | The firmware repository | Every firmware build |
| PE format | The framework generation | v2 PE files (NFMRK2) are loaded by nanoCLR 2.x and later |
The versions are independent from each other. The firmware version doesn't tell if a package works with it, and the package version doesn't tell if a firmware works with it. What tells that is the native contract hash, and only within the same PE generation: the package and the firmware also have to use the same PE format (v2 PE files with nanoCLR 2.x and later). The hash tells about the native compatibility between them once that's the case.
Class libraries
- The last number of the version is generated by the build and always increases. Nothing to do.
- Minor version: bump it when you make a managed binary breaking change: changing or removing a public API that other libraries and applications use, so they have to be updated and rebuilt. An assembly reference is resolved by name and by the major and minor numbers, so after the bump the dependents must be rebuilt against the new package, and the tools report it before deploying.
- Major version: for a new generation of the library or a big breaking change. It breaks references like a minor bump does.
- A change in the native code never needs a version bump, because the native code is not allowed to break the contract (see below). A breaking change starts in the C# API and is, therefore, a managed change.
Native code
The native implementation of a library has to keep doing what the managed code, and the applications that use it, expect. The managed code that is already published and deployed can't be told that the native code behaves differently, and nothing in the tools detects it: the native contract hash only covers the signatures of the native methods and the layout of the fields, not what the native code does. If native code was allowed to break the contract, the compatibility between libraries and firmware couldn't be maintained.
| Allowed in the native code | Not allowed in the native code |
|---|---|
| Bug fixes | Changing what an existing native method does so the managed code behaves differently (meaning of the arguments, return values, side effects the managed code relies on) |
| Improvements: performance, memory use, code size, robustness | Using the fields of a class in a different way from what the managed code expects |
| Support for more targets or peripherals behind the same managed API | Removing, or replacing by a different one, a native method that published managed assemblies call |
In short, existing managed code must not be able to tell the new native code from the previous one, except that it's better.
When a breaking change is needed
A breaking change has to start from the C# API call. The steps are:
- Add the new API call to the C# library: a new method or a new type. If it needs native code, it gets its own
externmethod, with its own name and signature. The existing native method stays as it is. - Keep the existing API working. If the existing API also has to change or be removed, that's a managed breaking change: bump the minor (or major) version of the library as described above, so the dependents are made aware and are forced to update their libraries and references.
- Implement the new native method in the firmware. The native contract hash changes by itself (there's a new native method), and the new package and the new firmware are released together.
Never change the behavior of an existing native method and rely on the version, or on anything else, to tell the consumers.
What needs a new package or a new firmware
| Change | New package | New firmware |
|---|---|---|
| Managed code only (bug fix, new managed API, refactoring), the hash does not change | yes | no |
| Native code only (bug fix, improvement), the signatures and the fields do not change | no | yes |
| The contract changes (native method or field layout), because of a change in the C# API | yes | yes |
- Static field indexes are assembly-wide: a managed change that adds a static field, or reorders types that have static fields, changes the contract and the hash, even if no native method is touched. See Field constants.
- A bug fix or an improvement in the native code doesn't need a new package, and the packages that already exist keep working with the new firmware.
- A change in the contract needs a new package and a new firmware, which are released together. The native declaration in nf-interpreter is updated before, or together with, the package. The latest firmware always works with the latest packages.
- Enums that are shared with native code are not part of the contract: never renumber existing members, only append.
- When the contract of an assembly changes, check the assemblies that have types deriving from types of that assembly: their stubs have to be regenerated in the same change. See Native Contract Hash.
Firmware and class libraries
A firmware works with a library if both are of the same PE generation (v2 PE files for nanoCLR 2.x and later) and the firmware has the native contract hash that the library requires, or the library has no native code. The hash only tells about the native compatibility within that generation: the v1 checksum and the v2 hash are computed in different ways and are never compared with each other. To find a package version for the firmware you have, look for the hash of the native assembly in the native_assemblies.json that is published with the firmware (or in the Device Capabilities of the Device Explorer) and look for the package that has it in its description and in its nfnc- tag.
PE format
The PE format version is part of the generation of the framework. The nanoCLR 2.x and later only load v2 PE files (NFMRK2) and the tools infer the format to use from the nanoCLR version that the device reports. A v1 PE file can't be loaded by a v2 firmware and the other way around, and a deployment can't mix them.