Skip to content

Latest commit

 

History

History
332 lines (261 loc) · 12.9 KB

README.md

File metadata and controls

332 lines (261 loc) · 12.9 KB

Introduction

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.

Table of Content

Requirement

The library is compiled against netcoreapp3.1 and net6.0, so you'll only need:

  • .Net Core 3.1 LTS SDK
  • .Net 6.0 LTS SDK

note: We won't/can't rely on VS 2022 since we want a portable cross-platform dotnet/cli pipeline.

Project Layout

  • 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.

Build

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.

Build Process

To Create a native dependent package we will split it in two parts:

  • A bunch of Google.OrTools.runtime.{rid}.nupkg packages for each Runtime Identifier (RId) targeted.
  • A generic package 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:

  1. 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.

  2. 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.

Local Google.OrTools Package

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: Local Pipeline Legend 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 Google.OrTools Package

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>
  • Once you specify a 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')
  • It is recommended to add the tag native to the nuget package tags
    <PackageTags>native</PackageTags>
  • Specify the output target folder for having the assembly output in runtimes/{rid}/lib/netstandard2.0 in the nupkg
    <BuildOutputTargetFolder>runtimes/$(RuntimeIdentifier)/lib</BuildOutputTargetFolder>
    note: Every files with an extension different from .dll will be filter out by nuget.
    note: dotnet/cli automatically add the $(TargetFramework) (i.e. netstandard2.0) to the output path.
  • Add the native shared library to the nuget package in the repository 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>
  • Generate the runtime package to a defined directory (i.e. so later in Google.OrTools package we will be able to locate it)
    <PackageOutputPath>{...}/packages</PackageOutputPath>
  • Generate the Reference Assembly (but don't include it to this runtime nupkg !, see below for explanation) using:
    <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.

Building local Google.OrTools Package

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.

  • Add the previous package directory:
    <RestoreSources>{...}/packages;$(RestoreSources)</RestoreSources>
  • Add dependency (i.e. 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>
    Thanks to the RestoreSource we can work locally with our just builded package without the need to upload it on nuget.org.
  • To expose the .Net Surface API the 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 net6.0

Complete Google.OrTools Package

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. Full Pipeline Legend

Building All runtime Google.OrTools Package

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...

Building Complete Google.OrTools 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

Examples

The Test projects show examples of building applications with net6.0.

The F# example folder shows how to compile against the typical .NET Framework installed on machine.

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

Resources

Target Framework Moniker (TFM)

.Net:Runtime IDentifier (RID)

Reference on .csproj format

Issues

Some issue related to this process

Misc

Image has been generated using plantuml:

plantuml -Tpng docs/{file}.dot

So you can find the dot source files in docs.