Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/mizux/dotnet-native
Template to build a multi-platforms Native Net 6.0 Nuget package using dotnet cli
https://github.com/mizux/dotnet-native
cmake dotnet dotnet-cli mizux native-library netstandard netstandard2 netstandard20 netstandard21 nuget
Last synced: about 2 months ago
JSON representation
Template to build a multi-platforms Native Net 6.0 Nuget package using dotnet cli
- Host: GitHub
- URL: https://github.com/mizux/dotnet-native
- Owner: Mizux
- License: apache-2.0
- Created: 2018-08-08T14:44:45.000Z (over 6 years ago)
- Default Branch: main
- Last Pushed: 2024-01-29T12:48:01.000Z (12 months ago)
- Last Synced: 2024-01-29T14:47:50.270Z (12 months ago)
- Topics: cmake, dotnet, dotnet-cli, mizux, native-library, netstandard, netstandard2, netstandard20, netstandard21, nuget
- Language: C++
- Homepage:
- Size: 1.22 MB
- Stars: 76
- Watchers: 7
- Forks: 14
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Authors: AUTHORS
Awesome Lists containing this project
README
Github-CI:
[![Build Status][amd64_linux_status]][amd64_linux_link]
[![Build Status][amd64_macos_status]][amd64_macos_link]
[![Build Status][amd64_windows_status]][amd64_windows_link][![Build Status][amd64_docker_status]][amd64_docker_link]
[amd64_linux_status]: ./../../actions/workflows/amd64_linux.yml/badge.svg
[amd64_linux_link]: ./../../actions/workflows/amd64_linux.yml
[amd64_macos_status]: ./../../actions/workflows/amd64_macos.yml/badge.svg
[amd64_macos_link]: ./../../actions/workflows/amd64_macos.yml
[amd64_windows_status]: ./../../actions/workflows/amd64_windows.yml/badge.svg
[amd64_windows_link]: ./../../actions/workflows/amd64_windows.yml[amd64_docker_status]: ./../../actions/workflows/amd64_docker.yml/badge.svg
[amd64_docker_link]: ./../../actions/workflows/amd64_docker.yml# Introduction
|
Requirement |
Codemap |
Dependencies |
Build |
CI |
Appendices |
License |This is an example of how to create a Modern [CMake](https://cmake.org/) C++/.Net Project.
This project aim to explain how you build a .NetStandard2.0 native (win-x64,
linux-x64 and osx-x64) nuget multiple package using
[`.NET Core CLI`](https://docs.microsoft.com/en-us/dotnet/core/tools/) and the
[*new* .csproj format](https://docs.microsoft.com/en-us/dotnet/core/tools/csproj).
e.g. You have a cross platform C++ library (using a CMake based build) and a .NetStandard2.0 wrapper on it thanks to SWIG.
Then you want to provide a cross-platform Nuget package to consume it in a .NetCoreApp3.1 project...## Requirement
You'll need:
* "CMake >= 3.18".
* ".Net Core SDK >= 3.1" to get the dotnet cli.note: We won't/can't rely on VS 2019 since we want a portable cross-platform [`dotnet/cli`](https://github.com/dotnet/cli) pipeline.
## Codemap
The project layout is as follow:
* [CMakeLists.txt](CMakeLists.txt) Top-level for [CMake](https://cmake.org/cmake/help/latest/) based build.
* [cmake](cmake) Subsidiary CMake files.
* [dotnet.cmake](cmake/dotnet.cmake) All internall .Net CMake stuff.* [ci](ci) Root directory for continuous integration.
* [Foo](Foo) Root directory for `Foo` library.
* [CMakeLists.txt](Foo/CMakeLists.txt) for `Foo`.
* [include](Foo/include) public folder.
* [foo](Foo/include/foo)
* [Foo.hpp](Foo/include/foo/Foo.hpp)
* [src](Foo/src) private folder.
* [src/Foo.cpp](Foo/src/Foo.cpp)
* [dotnet](Foo/dotnet)
* [CMakeLists.txt](Foo/dotnet/CMakeLists.txt) for `Foo` .Net.
* [foo.i](Foo/dotnet/foo.i) SWIG .Net wrapper.
* [Bar](Bar) Root directory for `Bar` library.
* [CMakeLists.txt](Bar/CMakeLists.txt) for `Bar`.
* [include](Bar/include) public folder.
* [bar](Bar/include/bar)
* [Bar.hpp](Bar/include/bar/Bar.hpp)
* [src](Bar/src) private folder.
* [src/Bar.cpp](Bar/src/Bar.cpp)
* [dotnet](Bar/dotnet)
* [CMakeLists.txt](Bar/dotnet/CMakeLists.txt) for `Bar` .Net.
* [bar.i](Bar/dotnet/bar.i) SWIG .Net wrapper.
* [FooBar](FooBar) Root directory for `FooBar` library.
* [CMakeLists.txt](FooBar/CMakeLists.txt) for `FooBar`.
* [include](FooBar/include) public folder.
* [foobar](FooBar/include/foobar)
* [FooBar.hpp](FooBar/include/foobar/FooBar.hpp)
* [src](FooBar/src) private folder.
* [src/FooBar.cpp](FooBar/src/FooBar.cpp)
* [dotnet](FooBar/dotnet)
* [CMakeLists.txt](FooBar/dotnet/CMakeLists.txt) for `FooBar` .Net.
* [foobar.i](Bar/dotnet/foobar.i) SWIG .Net wrapper.* [dotnet](dotnet) Root directory for .Net template files
* [`Mizux.DotnetNative.runtime.csproj.in`](dotnet/Mizux.DotnetNative.runtime.csproj.in) csproj template for the .Net "native" (i.e. RID dependent) package.
* [`Mizux.DotnetNative.csproj.in`](dotnet/Mizux.DotnetNative.csproj.in) csproj template for the .Net package.
* [`Test.csproj.in`](dotnet/Test.csproj.in) csproj template for .Net test project.
* [`Example.csproj.in`](dotnet/Example.csproj.in) csproj template for .Net example project.* [tests](tests) Root directory for tests
* [CMakeLists.txt](tests/CMakeLists.txt) for `DotnetNative.Test` .Net.
* [`FooTests.cs`](tests/FooTests.cs) Code of the Mizux.DotnetNative.FooTests project.* [examples](examples) Root directory for examples
* [CMakeLists.txt](examples/CMakeLists.txt) for `DotnetNative.FooApp` .Net.
* [`FooApp.cs`](examples/FooApp.cs) Code of the `DotnetNative.FooApp` app.## Dependencies
To complexify a little, the CMake project is composed of three libraries (Foo, Bar and FooBar)
with the following dependencies:
```sh
Foo:
Bar:
FooBar: PUBLIC Foo PRIVATE Bar
```## Build Process
To Create a native dependent package we will split it in two parts:
* A bunch of `Mizux.DotnetNative.runtime.{rid}.nupkg` packages for each
[Runtime Identifier (RId)](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog) targeted containing the native libraries.
* A generic package `Mizux.DotnetNative.nupkg` depending on each runtime packages and
containing the managed .Net code.Actually, You don't need a specific variant of .Net Standard wrapper, simply omit the library extension and .Net magic will pick
the correct native library.
ref: https://www.mono-project.com/docs/advanced/pinvoke/#library-namesnote: [`Microsoft.NetCore.App` packages](https://www.nuget.org/packages?q=Microsoft.NETCore.App)
follow this layout.note: While Microsoft use `runtime-.Company.Project` for native libraries
naming, it is very difficult to get ownership on it, so you should prefer to use
`Company.Project.runtime-` instead since you can have ownership on
`Company.*` prefix more easily.We have two use case scenario:
1. Locally, be able to build a Mizux.DotnetNative package which **only** target the
local `OS Platform`, i.e. building for only one
[Runtime Identifier (RID)](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog).
\
note: This is useful since the C++ build is a complex process for Windows,
Linux and MacOS. i.e. We don't support cross-compilation for the native
library generation.2. Be able to create a complete cross-platform (ed. platform as multiple rid)
Mizux.DotnetNative package. \
i.e. First you generate each native Nuget package
(`Mizux.DotnetNative.runtime.{rid}.nupkg`) on each native architecture, then
copy paste these artifacts on one native machine to generate the
meta-package `Mizux.DotnetNative`.### Local Mizux.DotnetNative Package
Let's start with scenario 1: Create a *Local only* `Mizux.DotnetNative.nupkg` package targeting **one**
[Runtime Identifier (RID)](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog).
We would like to build a `Mizux.DotnetNative.nupkg` package which only depends
on one `Mizux.DotnetNative.runtime.{rid}.nupkg` in order to work locally.The pipeline for `linux-x64` should be as follow:
![Local Pipeline](docs/local_pipeline.svg)
![Legend](docs/legend.svg)
note: The pipeline will be similar for `osx-x64` and `win-x64` architecture,
don't hesitate to look at the CI log.#### Building local runtime Mizux.DotnetNative Package
disclaimer: In this git repository, we use `CMake` and `SWIG`.
Thus we have the C++ shared library `libFoo.so` and the SWIG generated .Net wrapper `Foo.cs`.
note: For a C++ CMake cross-platform project sample, take a look at [Mizux/cmake-cpp](https://github.com/Mizux/cmake-cpp).
note: For a C++/Swig CMake cross-platform project sample, take a look at [Mizux/cmake-swig](https://github.com/Mizux/cmake-swig).So first let's create the local `Mizux.DotnetNative.runtime.{rid}.nupkg` nuget package.
Here some dev-note concerning this `Mizux.DotnetNative.runtime.{rid}.csproj`.
* Once you specify a `RuntimeIdentifier` then `dotnet build` or `dotnet build -r {rid}`
will behave identically (save you from typing it).
* note: it is **NOT** the case if you use `RuntimeIdentifiers` (notice the 's')
* It is [recommended](https://docs.microsoft.com/en-us/nuget/create-packages/native-packages)
to add the tag `native` to the
[nuget package tags](https://docs.microsoft.com/en-us/dotnet/core/tools/csproj#packagetags)
```xml
native
```
* This package is a runtime package so we don't want to ship an empty assembly file:
```xml
false
```
* Add the native (i.e. C++) libraries to the nuget package in the repository `runtimes/{rid}/native`. e.g. for linux-x64:
```xml
runtimes/linux-x64/native/%(Filename)%(Extension)
true
PreserveNewest
```
* Generate the runtime package to a defined directory (i.e. so later in
`Mizux.DotnetNative` package we will be able to locate it)
```xml
{...}/packages
```Then you can generate the package using:
```bash
dotnet pack Mizux.DotnetNative.runtime.{rid}
```
note: this will automatically trigger the `dotnet build`.If everything good the package (located where your `PackageOutputPath` was defined) should have this layout:
```
{...}/packages/Mizux.DotnetNative.runtime.{rid}.nupkg:
\- Mizux.DotnetNative.runtime.{rid}.nuspec
\- runtimes
\- {rid}
\- native
\- *.so / *.dylib / *.dll
...
```
note: `{rid}` could be `linux-x64` and `{framework}` could be `netstandard2.0`tips: since nuget package are zip archive you can use `unzip -l .nupkg`
to study their layout.#### Building local Mizux.DotnetNative Package
So now, let's create the local `Mizux.DotnetNative.nupkg` nuget package which will
depend on our previous runtime package.Here some dev-note concerning this `DotnetNative.csproj`.
* Add the previous package directory:
```xml
{...}/packages;$(RestoreSources)
```
* Add dependency (i.e. `PackageReference`) on each runtime package(s) availabe:
```xml
```
Thanks to the `RestoreSource` we can work locally we our just builded package
without the need to upload it on [nuget.org](https://www.nuget.org/).Then you can generate the package using:
```bash
dotnet pack Mizux.DotnetNative
```If everything good the package (located where your `PackageOutputPath` was
defined) should have this layout:```
{...}/packages/Mizux.DotnetNative.nupkg:
\- Mizux.DotnetNative.nuspec
\- lib
\- {framework}
\- Mizux.DotnetNative.dll
...
```
note: `{framework}` could be `netcoreapp3.1` or/and `net6.0`#### Testing local Mizux.DotnetNative.FooApp Package
We can test everything is working by using the `Mizux.DotnetNative.FooApp` or `Mizux.DotnetNative.FooTests` project.
First you can build it using:
```
dotnet build /dotnet/FooApp
```
note: Since `Mizux.DotnetNative.FooApp` `PackageReference` Mizux.DotnetNative and add `{...}/packages` to the `RestoreSource`.
During the build of DotnetNative.FooApp you can see that `Mizux.DotnetNative` and
`Mizux.DotnetNative.runtime.{rid}` are automatically installed in the nuget cache.Then you can run it using:
```
dotnet run --project /dotnet/FooApp/FooApp.csproj
```
note: Contrary to `dotnet build` and `dotnet pack` you must use `--project`
before the `.csproj` path (let's call it "dotnet cli command consistency")You should see:
```sh
$ dotnet run --project build/dotnet/FooApp/FooApp.csproj
[1] Enter DotnetNativeApp
[2] Enter Foo::staticFunction(int)
[3] Enter freeFunction(int)
[3] Exit freeFunction(int)
[2] Exit Foo::staticFunction(int)
[1] Exit DotnetNativeApp
```### Complete Mizux.DotnetNative Package
Let's start with scenario 2: Create a *Complete* `Mizux.DotnetNative.nupkg` package
targeting multiple
[Runtime Identifier (RID)](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog).
We would like to build a `Mizux.DotnetNative.nupkg` package which depends on several
`Mizux.DotnetNative.runtime.{rid}.nupkg`.The pipeline should be as follow:
note: This pipeline should be run on any architecture,
provided you have generated the three architecture dependent `Mizux.DotnetNative.runtime.{rid}.nupkg`
nuget packages.
![Full Pipeline](docs/full_pipeline.svg)
![Legend](docs/legend.svg)#### Building All runtime Mizux.DotnetNative Package
Like in the previous scenario, on each targeted OS Platform you can build the
coresponding `Mizux.DotnetNative.runtime.{rid}.nupkg` package.Simply run on each platform:
```bash
cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
```
note: replace `{rid}` by the Runtime Identifier associated to the current OS platform.Then on one machine used, you copy all other packages in the `{...}/packages` so
when building `Mizux.DotnetNative.csproj` we can have access to all package...#### Building Complete Mizux.DotnetNative Package
This is the same step than in the previous scenario, since we "see" all runtime
packages in `{...}/packages`, the project will depends on each of them.Once copied all runtime package locally, simply run:
```bash
dotnet build /dotnet/Mizux.DotnetNative
dotnet pack /dotnet/Mizux.DotnetNative
```#### Testing Complete Mizux.DotnetNative Package
We can test everything is working by using the `Mizux.DotnetNative.FooApp` or `Mizux.DotnetNative.FooTests` project.
First you can build it using:
```
dotnet build /dotnet/FooApp
```
note: Since Mizux.DotnetNative.FooApp `PackageReference` Mizux.DotnetNative and add `{...}/packages` to the `RestoreSource`.
During the build of Mizux.DotnetNative.FooApp you can see that `Mizux.DotnetNative` and
`Mizux.DotnetNative.runtime.{rid}` are automatically installed in the nuget cache.Then you can run it using:
```
dotnet run --project /dotnet/FooApp/FooApp.csproj
```You should see something like this
```bash
$ dotnet run --project build/dotnet/FooApp/FooApp.csproj
[1] Enter DotnetNativeApp
[2] Enter Foo::staticFunction(int)
[3] Enter freeFunction(int)
[3] Exit freeFunction(int)
[2] Exit Foo::staticFunction(int)
[1] Exit DotnetNativeApp
```## Appendices
Few links on the subject...
.Net runtime can deduce library extension so don’t use a platform-specific
library name in the `DllImport` statement.
Instead, just use the library name itself, without any prefixes or suffixes,
and rely on the runtime to find the appropriate library at runtime.\
ref: [Mono `pinvoke#libraryname`](https://www.mono-project.com/docs/advanced/pinvoke/#library-names)### Resources
#### Project layout
* [The Pitchfork Layout](https://mizux.github.io/pitchfork/)#### CMake
* [CMake Reference Documentation](https://cmake.org/cmake/help/latest/index.html)
* https://llvm.org/docs/CMakePrimer.html
* https://cliutils.gitlab.io/modern-cmake/
* https://cgold.readthedocs.io/en/latest/#### Target Framework Moniker (TFM)
* [.NET TFM list](https://docs.microsoft.com/en-us/dotnet/standard/frameworks)
* [.NET Standard implementation support](https://docs.microsoft.com/en-us/dotnet/standard/net-standard)#### .Net:Runtime IDentifier (RID)
* [.Net Support Policy](https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core)
* [.NET Core RID Catalog](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog)
* [Creating native packages](https://docs.microsoft.com/en-us/nuget/create-packages/native-packages)
* [Blog on Nuget Rid Graph](https://natemcmaster.com/blog/2016/05/19/nuget3-rid-graph/)#### Reference on .csproj format
* [Common MSBuild project properties](https://docs.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-properties?view=vs-2017)
* [MSBuild well-known item metadata](https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-well-known-item-metadata?view=vs-2017)
* [Additions to the csproj format for .NET Core](https://docs.microsoft.com/en-us/dotnet/core/tools/csproj)### Issues
Some issue related to this process
* [`PackageReference` only support `TargetFramework` condition](https://docs.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#adding-a-packagereference-condition)
* [Nuget needs to support dependencies specific to target runtime #1660](https://github.com/NuGet/Home/issues/1660)
* [Improve documentation on creating native packages #238](https://github.com/NuGet/docs.microsoft.com-nuget/issues/238)
* [Guide for packaging C# library using P/Invoke](https://github.com/NuGet/Home/issues/8623)### Misc
Image has been generated using [plantuml](http://plantuml.com/):
```bash
plantuml -Tsvg docs/{file}.dot
```
So you can find the dot source files in [docs](docs).## License
Apache 2. See the LICENSE file for details.
## Disclaimer
This is not an official Google product, it is just code that happens to be
owned by Google.