ortools/dotnet/README.md
This is the documentation page for the .NET wrapper of OR-Tools.
This project aim to explain how you build a .Net native (for win-x64, linux-x64
and osx-x64) nuget package using dotnet and few .csproj.
The library is compiled against netcoreapp3.1 and net8.0, so you'll only
need:
note: We won't/can't rely on VS 2022 since we want a portable cross-platform
dotnet/cli pipeline.
Google.OrTools.runtime.linux-x64: Contains the .Net Standard 2.1 native
project for the rid linux-x64.Google.OrTools.runtime.linux-arm64: Contains the .Net Standard 2.1 native
project for the rid linux-arm64.Google.OrTools.runtime.osx-x64: Contains the .Net Standard 2.1 native
project for the rid osx-x64.Google.OrTools.runtime.osx-arm64: Contains the .Net Standard 2.1 native
project for the rid osx-arm64.Google.OrTools.runtime.win-x64: Contains the .Net Standard 2.1 native
project for the rid win-x64.Google.OrTools Is the .Net Standard 2.1 meta-package which should depends on
all previous available packages and contains the Reference Assembly.note: While Microsoft use runtime-<rid>.Company.Project for native libraries
naming, it is very difficult to get ownership on it, so you should prefer to use
Company.Project.runtime-<rid> instead since you can have ownership on
Company.* prefix more easily.
Either use the CMake base build or the Makefile based build. The workflow is
typically make dotnet which will build both C# and F# libraries package. The
output will be placed in <build_dir>/dotnet/packages folder. All tests will be
run based on this folder.
To Create a native dependent package we will split it in two parts:
Google.OrTools.runtime.{rid}.nupkg packages for each
Runtime Identifier (RId)
targeted.Google.OrTools.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-names
note: Microsoft.NetCore.App packages
follow this layout.
We have two use case scenario:
Locally, be able to build a Google.OrTools package which only target the
local OS Platform, i.e. building for only one
Runtime Identifier (RID).
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.
Be able to create a complete cross-platform (ed. platform as multiple rid)
Google.OrTools package.
i.e. First you generate each native Nuget package
(Google.OrTools.runtime.{rid}.nupkg) on each native architecture, then
copy paste these artifacts on one native machine to generate the
meta-package Google.OrTools.
Let's start with scenario 1: Create a Local Google.OrTools package targeting one
Runtime Identifier (RID).
We would like to build a Google.OrTools.nupkg package which only depends
on one Google.OrTools.runtime.{rid}.nupkg in order to work locally.
The pipeline for linux-x64 should be as follow:
note: The pipeline will be similar for osx-x64 and win-x64 architecture,
don't hesitate to look at the CI log.
disclaimer: We won't cover the C++ ortools library build. So first let's create
the local Google.OrTools.runtime.{rid}.nupkg nuget package.
Here some dev-note concerning this Google.OrTools.runtime.{rid}.csproj.
AssemblyName must be Google.OrTools.dll i.e. all {rid} projects must
generate an assembly with the same name (i.e. no {rid} in the name).
On the other hand package identifier will contain the {rid}...
<RuntimeIdentifier>{rid}</RuntimeIdentifier>
<AssemblyName>Google.OrTools</AssemblyName>
<PackageId>Google.OrTools.runtime.{rid}</PackageId>
RuntimeIdentifier then dotnet build or dotnet build -r {rid}
will behave identically (save you from typing it).
note: not the case if you use RuntimeIdentifiers (notice the 's')native to the
nuget package tags
<PackageTags>native</PackageTags>
runtimes/{rid}/lib/netstandard2.0 in the nupkg
<BuildOutputTargetFolder>runtimes/$(RuntimeIdentifier)/lib</BuildOutputTargetFolder>
.dll will be filter out
by nuget. $(TargetFramework) (i.e.
netstandard2.0) to the output path.runtimes/{rib}/native. e.g. for linux-x64:
<Content Include="*.so">
<PackagePath>runtimes/linux-x64/native/%(Filename)%(Extension)</PackagePath>
<Pack>true</Pack>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Google.OrTools package we will be able to locate it)
<PackageOutputPath>{...}/packages</PackageOutputPath>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
Then you can generate the package using:
dotnet pack Google.OrTools.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/Google.OrTools.runtime.{rid}.nupkg:
\- Google.OrTools.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 <package>.nupkg
to study their layout.
So now, let's create the local Google.OrTools.nupkg nuget package which will
depend on our previous runtime package.
Here some dev-note concerning this Google.OrTools.csproj.
<RestoreSources>{...}/packages;$(RestoreSources)</RestoreSources>
PackageReference) on each runtime package(s) available:
<ItemGroup Condition="Exists('{...}/packages/Google.OrTools.runtime.linux-x64.1.0.0.nupkg')">
<PackageReference Include="Google.OrTools.runtime.linux-x64" Version="1.0.0" />
</ItemGroup>
RestoreSource we can work locally with our just
builded package without the need to upload it on
nuget.org.Google.OrTools.csproj must contains at
least one
Reference Assembly
of the previously rumtime package. xml <Content Include="../Google.OrTools.runtime.{rid}/bin/$(Configuration)/$(TargetFramework)/{rid}/ref/*.dll"> <PackagePath>ref/$(TargetFramework)/%(Filename)%(Extension)</PackagePath> <Pack>true</Pack> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content>Then you can generate the package using:
dotnet pack Google.OrTools
If everything good the package (located where your PackageOutputPath was
defined) should have this layout:
{...}/packages/Google.OrTools.nupkg:
\- Google.OrTools.nuspec
\- lib
\- {framework}
\- Google.OrTools.dll
...
note: {framework} could be netcoreapp3.1 or/and net8.0
Let's start with scenario 2: Create a Complete Google.OrTools.nupkg package
targeting multiple
Runtime Identifier (RID).
We would like to build a Google.OrTools.nupkg package which depends on several
Google.OrTools.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 Google.OrTools.runtime.{rid}.nupkg
nuget packages.
Like in the previous scenario, on each targeted OS Platform you can build the
corresponding Google.OrTools.runtime.{rid}.nupkg package.
Simply run on each platform:
dotnet build <build_dir>/dotnet/Google.OrTools.runtime.{rid}
dotnet pack <build_dir>/dotnet/Google.OrTools.runtime.{rid}
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 Google.OrTools.csproj we can have access to all 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:
dotnet build <build_dir>/dotnet/Google.OrTools
dotnet pack <build_dir>/dotnet/Google.OrTools
The Test projects show examples of building applications with net8.0.
The F# example folder shows how to compile against the typical .NET Framework installed on machine.
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
dotnet seems to use a previous package version ?
You can clear your local cache using: dotnet nuget locals all --clear
Some issue related to this process
PackageReference only support TargetFramework conditionImage has been generated using plantuml:
plantuml -Tpng docs/{file}.dot
So you can find the dot source files in docs.