dotnet / sdk

Core functionality needed to create .NET Core projects, that is shared between Visual Studio and CLI

Home Page:https://dot.net/core

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Build for desktop framework on non-windows platforms

dasMulli opened this issue · comments

The dotnet CLI allowed to build net* targets on mac and linux using the reference assemblies installed with mono. It seems this is not supported by the targets in Microsoft.NET.Sdk. For fun, I also tried referencing the targeting pack NuGets which also didn't work.

Is there a plan for bringing back support for building net* projects on linux/mac? Or should it already work?

We would like this to work, but haven't had the time yet to invest in it.

I would prefer that msbuild common targets could resolve the mono targeting pack directory, rather than special work in the SDK to locate it.

Here's the current code that resolves the Mono reference assemblies:

https://github.com/dotnet/core-setup/blob/master/src/Microsoft.Extensions.DependencyModel/Resolution/DotNetReferenceAssembliesPathResolver.cs

Someone (dotnet/SDK or core MSBuild) could call into this code to get the path. (There is also an environment variable available to override the path).

Aside: That class has a super general name, no docs, and only actually finds the directory on non-Windows.

I think there should be a root reference assembly directory resolved by msbuild that works on all platforms, and we should use that throughout. It is super strange to me that when we call this, we then have to handle windows separately:

var programFiles = Environment.GetEnvironmentVariable("ProgramFiles(x86)");

This should all be in one place.

So it looks like msbuild already has $(TargetFrameworkRootPath) which the user can override (but we wouldn't respect). However, it seems to not set it to non-empty when the task uses a default value.

Here's how I'd like things to work:

  1. MSBuild can also find Mono's xbuild-framework dir on non-Windows and use that as $(TargetFrameworkRootPath) without user intervention.
  2. MSBuild sets $(TargetFrameworkRootPath) when it uses a default location on any platform.
  3. SDK uses $(TargetFrameworkRootPath) anywhere it needs this path outside of what RAR does for us.

@rainersigwald @jeffkl What do you think?

Agreed that ALL the logic should exist in one place.

Another aside here is that this code needs to run/work at runtime of the app because things like ASP.NET MVC Razor View compilation needs to find these assemblies.

That's the reason at least part of this code (the non-windows part) is contained in DependencyModel - which is used both at build and run time.

If msbuild can safely take a dependency on the same thing that finds it at runtime, then we can truly have only one place.

Regardless of duplication, though, everything running in the build to resolve this directory the same way GetReferenceAssemblyPaths does. Otherwise, there's a mismatch where RAR can respect $(TargetFrameworkRootPath) set by user, but other code in the SDK cannot, etc.

Maybe the most appropriate thing to do here is for the tooling (MSBuild, SDK, etc) to write the location into the .deps.json file during "build" so it can be used by "run". The DependencyModel can use that value as one more place to look for ref assemblies.

During "publish" the value wouldn't get written into the .deps.json file because the ref assemblies get copied to the "refs" folder. Thus a published app doesn't need to get the location Reference Assemlies folder, since it won't exist on the end-users machine.

@eerhardt That sounds like the right place to land indeed.

Wouldn't it be simpler - from a end-user perspective - to pull the complete set of reference assemblies as a NuGet package? Similar to referencing Microsoft.NETCore.App..
dotnet run and dotnet test would still have to figure out where the mono executable is but just building an application would not rely on a system-wide install of another component. That way using a docker container with just the CLI installed would be enough to produce a runnable output.

We have talked informally about having classic targeting packs available as nuget packages before. @terrajobst, any plans there?

We've had https://dotnet.myget.org/feed/dotnet-core/package/nuget/Microsoft.TargetingPack.NETFramework.v4.6.2 (and other .NET framework versions) for a while, but they're only used for internal builds and aren't published to nuget.org. I think they contain the same assemblies as the real targeting pack.

@mellinoe IIRC I tried to use those (for a different, internal project) and they were missing some key parts.

I once talked about this offline with @akoeplinger, but didn't reach a conclusion.

MSBuild on .NET Core doesn't support finding references in all the same ways we do on Mono/Desktop framework. Most relevant is probably GAC resolution, but targeting packs are also important. As mentioned above, those should be nugetizable--in which case you could probably get stuff built pretty well. There are some other task differences though (GenerateResources springs to mind with the different possibilities of embedding for .NET Core and desktop).

The deps-file approach for run does seem reasonable to me too (given the current CLI run behavior).

I don't care at all about GAC resolution. If your build depends on it, it's busted as far as I'm concerned. We never should have gone to the GAC for compilation references. I've removed GAC from the default RAR search paths in the SDK.

Targeting packs via nuget would certainly be nice, but do we need to block on it? What's the harm in probing for xbuild-frameworks?

As far as other differences (GenerateResources), we should distinguish between what are temporary gaps and what is fundamentally never going to work on Core.

I hope that this gets resolved by RTM. Currently this makes the csproj variant of the .NET CLI a non-starter for most of my projects.

this feature would allow roundtripping projects between vs and vs4mac. right now, you can't do that if they have TargetFrameworks like net461;netcoreapp1.0

Has this been worked on for preview 4 of the CLI?

After checking I confirm that this is still an issue with preview 4. Are there any known workarounds?

I'm taking a look at doing something simple to unblock this.

Thanks, even passing in some magic property would help.

So as a workaround, explicitly passing/setting TargetFrameworkRootPath with path to xbuild-frameworks (e.g. /p:TargetFrameworkRootPath=/usr/lib/mono/xbuild-frameworks) should work, but doesn't.

The issue is that the mono redist list files have a redirect of the TargetFrameworkDirectory to somewhere else. msbuild understands this redirect, but currently only if it's actually running on Mono:

https://github.com/Microsoft/msbuild/blob/cb8c727a898e4439a3cd18f329504e436b0b7c00/src/Utilities/ToolLocationHelper.cs#L3106-L3111

:(

I did try that workaround and it did not work as you mentioned. Mono happens to redirect 4.6.x down to 4.5 to the 4.0 directory for example. It also does not list all the assemblies, relying on the automatic selection of all assemblies in the specified directory instead.

Can we open an issue against msbuild to have the behaviour change? Or have they mentioned that this will not change?

Any progress here?

I asked on the msbuild repo but no one answered so far.

fyi I've had success using the targeting pack nuget (adding the dotnet-core feed to my NuGet.Config). Not using this for production though but.. works on my machine ™️
This works with the latest source build of the cli:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>netcoreapp1.0;net461</TargetFrameworks>
    <RuntimeIdentifiers>osx.10.11-x64;win7-x64</RuntimeIdentifiers>
  </PropertyGroup>

  <PropertyGroup Condition=" '$(TargetFramework)' == 'net461' ">
    <RuntimeIdentifier>win7-x64</RuntimeIdentifier>
    <FrameworkPathOverride>$(NuGetPackageRoot)microsoft.targetingpack.netframework.v4.6.1\1.0.1\lib\net461\</FrameworkPathOverride>
  </PropertyGroup>

  <ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
    <PackageReference Include="Microsoft.TargetingPack.NETFramework.v4.6.1" Version="1.0.1" ExcludeAssets="All" PrivateAssets="All" />
  </ItemGroup>
</Project>

I just tried migrating OmniSharp to .csproj and this is a super-painful problem. First of all, there's no guarantee of where Mono might be installed to set the $(TargetFrameworkRootPath). For example, Mono might be in different locations on OSX depending on whether it was installed via Homebrew or through the Mono pkg.

The old CLI superseded that issue with environment variables. People may still have those set. The solution is to use those.

that's not a great solution. This is a major regression for existing customers. We should raise this issue up.

But the same environment variable had been working since the KRE and DNX days.

Yes, but MSBuild has changed a lot recently in support of Mono. However, many of those changes only work when MSBuild is actually running on Mono via runtime checks. I looked at this today and found that the tasks will override any environment variables with incorrect values. Setting TargetFrameworkRootPath is simply not enough without running MSBuild on Mono.

The high-order bit here is that cross-gen is broken for .NET Framework projects on all platforms but Windows.

I heard in another github issue, that the CLI will check for MSBuild compiled for .NET proper on Windows so that that version gets used instead of the bundled .NET Core version of MSBuild. Could that happen for mono as well? The mono team has been mentioning for a long time that they want xbuild out and MSBUild running on mono in the mono distribution itself. People like me that need to target mono would have the MSBuild that runs on mono installed with mono itself when that becomes real.

I believe there are a few architectural issues to decide on related to MSBuild and Mono:

  1. Should MSBuild be able to cross-compile from (mono, full, core) surface / runtime to any other platform? (I'm in favour but that would need some rework from MSBuild afaik)
  2. Should the CLI or MSBuild figure out what components are installed locally? (e.g. find mono)
  3. Should Mono ship a version of MSBuild with SDKs installed that should be used for compiling and running multi-targeting apps? If so, should it have its own CLI similar to dotnet or integrate with the existing CLI by making it forward to mono's MSBuild?
  4. Should there be public targeting pack NuGet packages that enable compiling out of the box? (probably useless since you typically would want to run tests during a build as well..)

IMO, #1 is infeasible without the CLI running MSBuild on Mono. This is what I ended up doing for OmniSharp in order to handle .NET Framework TFMs and .NET Core TFMs in a single solution.

Other possible mitigations:

  • Should 'dotnet build' succeed for the "buildable" projects (e.g. those projects targeting netcoreappX.X or netstandardX.X)? In such cases, I would assume that we'd also display a warning to let the user know that not everything was compiled.
  • 'dotnet build' should provide error messages with better guidance. The error message that is currently displayed to the user tells them to go install a targeting pack, which really isn't all that helpful on a Mac. 😄

I may take a look at creating a "mono-build" CLI verb at some point in the near future. For OmniSharp, I'll either need to do this or move it off of the CLI entirely. This is a blocker for that project.

ping

@dasMulli Is your workaround works fine in dotnet CLI rc3?

@alexvaluyskiy haven't had any problems yet..

I've got the error
CSC : error CS0006: Metadata file '/mscorlib.dll' could not be found [/home/alexvaluyskiy/MonoTest/MonoTest.csproj]

@alexvaluyskiy sry I completely forgot that macOS is now case insensitive by default.. changed the path in my hack to be lowercase since NuGet works around case sensitivity by lowercasing. (just gave it a try with a docker container)

@dasMulli Thanks, it works for me too. But it is not really a workaround. For example, this code could be compiled only on Mono+net46, but not on Mono+net45 (Mono bug)

IPAddress address = IPAddress.Parse("127.0.0.1");
var newAddressIPv4 = address.MapToIPv4();
var newAddressIPv6 = address.MapToIPv6();

But I can build it with the targeting packs

This issue has been assigned a milestone, but what will the resolution be? Will we get the mono variant of MSBuild or will the sdk infrasture change how i functions to all targeting desktop profiles unconditionally?

I just updated https://github.com/dotnet/netcorecli-fsc/wiki/.NET-Core-SDK-rc4#using-net-framework-as-targets-framework-the-osxunix-build-fails to show how to use set FrameworkPathOverride as an environment variable if you already have mono installed so you don't have to grab Microsoft.TargetingPack.NETFramework.v4.6.1 package

I have the same issue when building my solution in AppVeyor when referencing .NETFramework,Version=v2.0.

C:\projects\simmetrics-net\.dotnetcli\sdk\1.0.1\Microsoft.Common.CurrentVersion.targets(1111,5): error MSB3644: The reference assemblies for framework ".NETFramework,Version=v2.0" were not found.

More details at https://ci.appveyor.com/project/StefH/simmetrics-net/build/1.0.2.5

FYI, this is also blocking gRPC C# from migrating to the new .csproj project format grpc/grpc#10441

I solved this issue for OmniSharp by using the msbuild that ships with Mono 4.8.0 after setting a few environment variables to point to the .NET Core SDK: https://github.com/OmniSharp/omnisharp-roslyn/blob/dev/msbuild.sh. Note: On Linux, this requires installing the "msbuild" package as well: https://github.com/OmniSharp/omnisharp-roslyn/blob/dev/BUILD.md#linux.

Also, it looks like a future mono msbuild will carry the SDKs: https://github.com/mono/msbuild/tree/xplat-master/sdks

Yup. That's correct.

So the mono 5 beta is able to build SDK projects using its version of msbuild (which is option nr 3 from one of my above comments). I feel like this issue can be closed since there is nothing to do on the SDK side, the remaining work is probably UX only - like making the CLI forward to / use the system msbuild (https://github.com/dotnet/cli/issues/5723) and making the vstest targets run on mono.
Any objections / remaining work?

Closing since resolving reference assemblies is now clearly a responsibility of the hosting MSBuild distribution.

We would still like for the CLI to be able to build for .NET Framework out of the box. To do this we are thinking of shipping the reference assemblies as NuGet packages.

Would the SDK allow running of xunit tests against desktop .NET/Mono when hosted under MSBuild 15 (the one that runs on the latter CLRs)? AFAIK this is only supported under the CLI as things stand.

@borgdylan this is best filed at https://github.com/Microsoft/vstest for dotnet test or https://github.com/xunit/xunit for dotnet xunit (which already has an option to use the system msbuild)

I asked on the xunit repo, since I use linux and therefore use VS Code not VS proper.

despite the confusing name, vstest isn't part of visual studio - it's used by "dotnet test"

Although I can get around normal building by using msbuild from mono 5.0 directly is there a work-around for this issue when using dotnet pack? Even if I pass --no-build it complains about the missing reference assemblies?

@kjnilsson you can use a call like msbuild /t:Pack /p:Configuration=Release

@dasMulli thanks - I get:

error MSB4057: The target "Pack" does not exist in the project

Then i believe this is not an SDK-based project? (<Project Sdk="...">) This would mean that dotnet pack also wouldn't work on windows.. (or the same msbuild invocation).

Looks like the stable mono 5 doesn't have a NuGet.Build.Tasks.Pack "Sdk"

Looks like it. I probably need to look at alternative solutions for now until it can be "properly" supported by the dotnet core tooling. Perhaps paket would work.

@kjnilsson you can add a <PackageReference Include="NuGet.Build.Tasks.Pack" Version="4.0.0" PrivateAssets="All" /> in the meantime. This will make it work with mono msbuild but you should pass /p:NuGetBuildTasksPackTargets=junk-value to avoid conflicts with the sdk-imported one on windows when using dotnet pack.

Or fail-proof your project like this:

<Project>
  <PropertyGroup>
    <NuGetBuildTasksPackTargets>junk-value-to-avoid-conflicts</NuGetBuildTasksPackTargets>
  </PropertyGroup>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

  <!-- All your project's other content here -->

  <ItemGroup>
    <PackageReference Include="NuGet.Build.Tasks.Pack" Version="4.0.0" PrivateAssets="All" />
  </ItemGroup>
  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>

(i cloned your branch to make sure this works)

Thank you for your help - that does appear to work.

error MSB4057: The target "Pack" does not exist in the project

I filed a bug here and it appears to be fixed now: https://bugzilla.xamarin.com/show_bug.cgi?id=57463

commented

The error still there.
@dasMulli I tried the two solutions you claim to work. None works.

Anyone else got this to work?

@jose-cf which error?
I'm also on the ASP.NET / .NET Core community slack (http://tattoocoder.com/aspnet-slack-sign-up/) so we could chat about your setup.

@akoeplinger informed me that mono 5.2 is going to add pack support.
And AFAIK mono 5.2 will also use the installed .NET Core SDK (>= 2.0) when installed by means of the same MSBuild SDK resolver that VS 2017 15.3 is going to ship.

commented

@dasMulli and whoever this might help:
In my systems I got to solve this issue by issuing:
export FrameworkPathOverride=$(dirname $(which mono))/../lib/mono/4.5/ #this is just a workaround for a bug that dotnet cli has

So, for me that works everywhere. Dudes I hope you can fix this bug because it is terrible, even with the workaround it is quite bad bug hanging around. So many people losing so many hours of works because of that. And wondering if should still continue using dotnet or use other alternatives. I hope that helps.

/lib/mono/4.5/ doesn't always work. For me it fails when project tries to reference a netstandard lib. I had to set it to /lib/mono/4.6.1-api. Obviously, that doesn't properly work if solution has projects targeting different framework versions.

You could try setting TargetFrameworkRootPath to /lib/mono/xbuild-frameworks. That's where the target frameworks are actually defined.

commented

@kekekeks thank you dude! That will be for sure helpful when I will build NetStandard libs because this bug seems to not to be going to fix.

@DustinCampbell sounds good, but did you try if that really works?

I don't know if it will work being set as an environment variable, but it's what we do in OmniSharp to support Xamarin and .NET Framework projects on OSX/Linux.

@DustinCampbell but I guess that only works for msbuild running on mono since the ToolLocationHelper only resolves the mono ref assemblies correctly in a few scenarios when actually running on mono and not from the dotnet CLI.. https://github.com/Microsoft/msbuild/blob/2d8a4341875e532bdf25a14f208f62a7bb547774/src/Utilities/ToolLocationHelper.cs#L3204-L3209

In the meantime, mono 5.2 has been released with support for resolving installed .NET Core SDKs so building directly from mono's msbuild is probably the safest method at the moment.

ah yes, that makes sense @dasMulli.

dotnet run
/usr/local/share/dotnet/sdk/2.0.0/Microsoft.Common.CurrentVersion.targets(1122,5): error MSB3644: The reference assemblies for framework ".NETFramework,Version=v4.5" were not found. To resolve this, install the SDK or Targeting Pack for this framework version or retarget your application to a version of the framework for which you have the SDK or Targeting Pack installed. Note that assemblies will be resolved from the Global Assembly Cache (GAC) and will be used in place of reference assemblies. Therefore your assembly may not be correctly targeted for the framework you intend. [/Users/hatajie/Projects/crud/crud/crud.csproj]

The build failed. Please fix the build errors and run again.

this run in osx 10.12.6

Just tried mono 5.2.0 and .NET Core 2.0 but still cannot build using dotnet build. Am I correct in thinking that I need to use msbuild and the dotnet cli is not yet shipping support.

The dotnet CLI uses its bundled .NET Core build of MSBuild. Mono now ships its own build of MSBuild which contains the necessary components to discover and use the MSBuild targets and tasks that ship with the dotnet CLI ("SDKs").

@RehanSaeed do msbuild /t:Restore, msbuild /t:Build etc work for you? (assuming msbuild /version shows a 15.3 version)

Thanks @TheAngryByrd et al. for FrameworkPathOverride solution! Took a while to assembly the whole set of TFMs work with dotnet build on TravisCI: https://github.com/neris/NGettext/blob/a701ac6/.travis.yml#L37. Note that for the PCL targets, the catch was to use TargetFrameworkRootPath instead of FrameworkPathOverride. Now it works with Mono 5.2 and dotnet 2.0.0 on both Ubuntu and macOS. (with the exception of net35, which was working with 2.0-api but not its own dir, so we skip it).

@dasMulli not sure why, but on Ubuntu 17.04 I could only get your workaround to work if I hardcoded the full path for instead of using the $(NuGetPackageFolders) variable

@ivanatpr can you try $(NuGetPackageRoot) instead? There are more than 1 package folders by default on 2.0 AFAIK so it will have pathA;pathB in it. (updated previous post)

@dasMulli yup that fixed it thanks!

Can confirm I have net461 and netstandard2.0 building on Linux (Travis CI) via a mix of approaches in this issue. If it's helpful for anyone to have a reference, here's a (modified for confidentiality) csproj which builds for the two targets, and includes some packages/references conditionally (eg EventLog for net461). For netstandard2.0 I really only needed to change to $(NuGetPackageRoot) from $(NuGetPackageFolders) as dasMulli indicated.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <Description>Best thing ever</Description>
    <VersionPrefix>2.0.0</VersionPrefix>
    <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
    <AssemblyName>Best.ThingEver</AssemblyName>
    <PackageId>Best.ThingEver</PackageId>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.0.0" />
    <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
    <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="2.0.0" />
    <PackageReference Include="Microsoft.Extensions.FileProviders.Composite" Version="2.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
  </ItemGroup>

  <PropertyGroup Condition=" '$(TargetFramework)' == 'net461' ">
    <DefineConstants>$(DefineConstants);BESTTHINGEVER_ON_WINDOWS</DefineConstants>
    <RuntimeIdentifier>win7-x64</RuntimeIdentifier>
    <FrameworkPathOverride>$(NuGetPackageRoot)microsoft.targetingpack.netframework.v4.6.1\1.0.1\lib\net461\</FrameworkPathOverride>
  </PropertyGroup>

  <ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
    <PackageReference Include="Microsoft.TargetingPack.NETFramework.v4.6.1" Version="1.0.1" ExcludeAssets="All" PrivateAssets="All" />
    <PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="2.0.0" />
    <Reference Include="System" />
    <Reference Include="System.Runtime" />
    <Reference Include="System.ComponentModel" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.XML" />
  </ItemGroup>
</Project>
commented

@kierenj Thank you man... really useful for me.

I'm reading through and having a hard time judging because @DustinCampbell's solution for OmniSharp is... a lot to take in. Is there a way to build a new-SDK csproj targeting net20;net35;net40;net45;netstandard1.6 on Linux, given some minimum version of Mono and some workaround paste in the .csproj?

@jnm2 : The minimum version of Mono is likely around 5.2.0. What distro are you using? Mono installation instructions for various distros are available here: http://www.mono-project.com/download/#download-lin. (Note that the CentOS instructions work pretty well for Fedora as well.)

Ubuntu Trusty (it's Travis). Last night I got partway there with Mono 4.6.2 by using:

  <PropertyGroup Condition="'$(BaseFrameworkPathOverrideForNetfx)' != ''">
    <FrameworkPathOverride Condition="'$(TargetFramework)' == 'net20'">$(BaseFrameworkPathOverrideForNetfx)/2.0-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(TargetFramework)' == 'net35'">$(BaseFrameworkPathOverrideForNetfx)/2.0-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(TargetFramework)' == 'net40'">$(BaseFrameworkPathOverrideForNetfx)/4.0-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(TargetFramework)' == 'net45'">$(BaseFrameworkPathOverrideForNetfx)/4.5-api</FrameworkPathOverride>
  </PropertyGroup>

  <PropertyGroup Condition="'$(BaseFrameworkPathOverrideForNetfx)' == ''">
    <SignAssembly>true</SignAssembly>
    <AssemblyOriginatorKeyFile>..\..\nunit.snk</AssemblyOriginatorKeyFile>
  </PropertyGroup>

(I'm forced to use the 2.0-api folder for both net20 and net35.)

And build script:

if (IsRunningOnWindows())
{
    MSBuild(SOLUTION_FILE, new MSBuildSettings
    {
        Verbosity = Verbosity.Minimal,
        ToolVersion = MSBuildToolVersion.VS2017,
        Configuration = configuration
    });
}
else
{
    var currentFrameworkPath = System.IO.Path.GetDirectoryName(typeof(object).Assembly.Location);
    var frameworksBasePath = System.IO.Path.GetDirectoryName(currentFrameworkPath);

    DotNetCoreMSBuild(SOLUTION_FILE, new DotNetCoreMSBuildSettings()
        .SetConfiguration(configuration)
        .WithProperty("BaseFrameworkPathOverrideForNetfx", frameworksBasePath));
}

The only error left is that it's trying to find types in System.Runtime when it should be looking in the referenced System.ValueTuple package.

Another issue now is getting this whole thing to build on OSX because the .NET CLI is refusing to run saying the OS is not supported. Not sure what it needs or if Travis gives me control over that.

In honesty I was expecting the new csproj SDK to be more of a slam dunk given my experiences with it on Windows.

@jnm2 : I suspect that Mono 4.6.2 is a bit too early for this. Using a newer Mono version, you can install both the mono-complete and msbuild packages. Once that's done, you can just run msbuild from the terminal to build your project.

@DustinCampbell And that allows me to drop the FrameworkPathOverride workaround? Right now Mono latest is crashing when Cake 0.22.2 tries to compile the build script but I'll see what I can do then from the angle of testing the newest releases.

The OmniSharp CI uses Travis's Trusty Ubuntu images without any changes to our csproj files. Project files look like this. Prior to recent changes, they were multi-targeting net46 and netstandard1.6 on Linux, Mac and Windows.

In addition, OmniSharp uses Cake 0.22, so I'm not sure what the trouble is there. However, I've noticed significant differences in some of the Cake semantics on Mono vs. Windows (e.g. Mono scripting turns script variables into local variables of the Main function, whereas Roslyn fields make them fields). @mholo65 or one of the other Cake folks might be able to help out.

Well Cake 0.22 dropped the Mono Scripting engine and uses Roslyn on all platforms. We use Mono 5.2 to build Cake on Linux/MacOS and have worked around the multitarget issue in dotnet by specifying the FrameworkPathOverride. Since Cake runs on Mono, it’s a piece of Cake (pun intended) to resolve the path to the Mono libs. This is how we solve this issue: https://github.com/cake-build/cake/blob/develop/build.cake#L54

Using Mono 5.2, I don't need any workaround other than MSBuildSettings.ToolPath = Context.Tools.Resolve("msbuild")! Unlike the FrameworkPathOverride approach, this time I don't get ValueTuple/System.Runtime compilation errors. Thanks both of you for the tips; I learned a lot.

I did the quick start tutorial https://www.microsoft.com/net/learn/get-started/macos and changed the TargetFramework to net451. That is when the quick start became rabbit hole. Alot of the information that inched me forward I got from this ticket, or the linked tickets. However, I don't know if this is really all relevant or all these manual workarounds are still needed. Just to get it to build, I had to export FrameworkPathOverride=/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.5/. That allowed me to dotnet build. However, when I do dotnet run, I got a permission issue. To get past that and to see the real error, I had to sudo dotnet run. Then I get "The reference assemblies for framework ".NETFramework,Version=v4.5.1" were not found". Why can I build this library and it sees the assemblies, and yet, dotnet run does not? Finally, the other pieces of information is that Visual Studio Community for Mac runs this app just fine when I hit 'run' AND I can run the generated exe by typing mono bin/Debug/net451/myApp.exe.

I know this ticket started in 2016 and now its 2018. Is this where we are at?

myApp: dotnet --info
.NET Command Line Tools (2.1.4)

Runtime Environment:
OS Name: Mac OS X
OS Version: 10.13
OS Platform: Darwin
RID: osx.10.12-x64
Base Path: /usr/local/share/dotnet/sdk/2.1.4/

Microsoft .NET Core Shared Framework Host

Version : 2.0.5
Build : 17373eb129b3b05aa18ece963f8795d65ef8ea54

@patrickjamesbarry I ended making a dotnet tool to help run mono apps: https://github.com/TheAngryByrd/dotnet-mono

I got mileage from this, placed in a netfx.props and imported in each relevant project.

It looks for Mono reference assemblies in the usual Xamarin and /usr install locations

  <PropertyGroup>
    <!-- When compiling .NET SDK 2.0 projects targeting .NET 4.x on Mono using 'dotnet build' you -->
    <!-- have to teach MSBuild where the Mono copy of the reference asssemblies is -->
    <TargetIsMono Condition="$(TargetFramework.StartsWith('net4')) and '$(OS)' == 'Unix'">true</TargetIsMono>
    
    <!-- Look in the standard install locations -->
    <BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/Library/Frameworks/Mono.framework/Versions/Current/lib/mono')">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono</BaseFrameworkPathOverrideForMono>
    <BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/usr/lib/mono')">/usr/lib/mono</BaseFrameworkPathOverrideForMono>
    <BaseFrameworkPathOverrideForMono Condition="'$(BaseFrameworkPathOverrideForMono)' == '' AND '$(TargetIsMono)' == 'true' AND EXISTS('/usr/local/lib/mono')">/usr/local/lib/mono</BaseFrameworkPathOverrideForMono>

    <!-- If we found Mono reference assemblies, then use them -->
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net45'">$(BaseFrameworkPathOverrideForMono)/4.5-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net451'">$(BaseFrameworkPathOverrideForMono)/4.5.1-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net452'">$(BaseFrameworkPathOverrideForMono)/4.5.2-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net46'">$(BaseFrameworkPathOverrideForMono)/4.6-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net461'">$(BaseFrameworkPathOverrideForMono)/4.6.1-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net462'">$(BaseFrameworkPathOverrideForMono)/4.6.2-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net47'">$(BaseFrameworkPathOverrideForMono)/4.7-api</FrameworkPathOverride>
    <FrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != '' AND '$(TargetFramework)' == 'net471'">$(BaseFrameworkPathOverrideForMono)/4.7.1-api</FrameworkPathOverride>
    <EnableFrameworkPathOverride Condition="'$(BaseFrameworkPathOverrideForMono)' != ''">true</EnableFrameworkPathOverride>

    <!-- Add the Facades directory.  Not sure how else to do this. Necessary at least for .NET 4.5 -->
    <AssemblySearchPaths Condition="'$(BaseFrameworkPathOverrideForMono)' != ''">$(FrameworkPathOverride)/Facades;$(AssemblySearchPaths)</AssemblySearchPaths>
  </PropertyGroup>

I also had to add a couple of explicit references to Facade assemblies such as System.Runtime and System.IO.

commented

I'm not sure if I met the same problem here, but I solved it as follows.

Here's the content of my paket.references

group Reference
    source https://dotnet.myget.org/F/dotnet-core/api/v3/index.json

    nuget Microsoft.TargetingPack.NETFramework.v4.6.1

then (after paket install) build the project by:

dotnet build MyProject.csproj -c Release /p:FrameworkPathOverride=${PROJECT_DIR}/packages/reference/Microsoft.TargetingPack.NETFramework.v4.6.1/lib/net461 /p:RuntimeIdentifier=win7-x64 --output $BUILD_OUT

See @cwe1ss's better solution below

I've reached this issue searching for a way to build the same multi-target csproj file on Windows and Linux skipping unsupported targets.
If you need to build on Linux/Mac but avoid building net4* targets, the solution is:

    <TargetFrameworks Condition="'$(OS)' == 'Windows_NT'">netstandard1.3;netstandard2.0;net451;net47</TargetFrameworks>
    <TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netstandard1.3;netstandard2.0</TargetFrameworks>

Kudos goes to @nguerrera for helping with this.

@CIPop. Not sure if it's still true but at one point VS was not happy with not having at least one TargetFrameworks without conditions. I worked around that by having the first one be unconditional (Windows case) and then overwriting it in non Windows case:

    <TargetFrameworks>netstandard1.3;netstandard2.0;net451;net47</TargetFrameworks>
    <TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netstandard1.3;netstandard2.0</TargetFrameworks>

It's all basically the same but I prefer having the universal targets first and then adding the windows targets. This ensures that targets don't have to be defined multiple times:

<TargetFrameworks>netstandard1.3;netstandard2.0</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' == 'Windows_NT'">$(TargetFrameworks);net451</TargetFrameworks>

VS was not happy with not having at least one TargetFrameworks without conditions

I confirm that it is still not happy at all (and the issue is not obvious at all).
One nit-pick @cwe1ss - looks like VS picks by default the first TFM (in the above case the default would be netstandard1.3). Since I normally want to develop on the netstandard2.0 TFM most of the times, I'm reversing the order in the first TargetFrameworks statement.

[edited]

One other nit-pick: if it's defined as above, you won't be able to use the new VS menu that allows TFM selection in Visual Studio:
image

Side-effect: There's a chance having net47/451 in the default won't play well with VS for Mac...

Just coming to this issue after trying ages ago... it feels to me like Mono is a red herring here, or at least could be.

In my situation, I don't need to execute any code targeting net45; I just want to be able to build it. As such, it feels like I shouldn't need anything other than reference assemblies, and some way of telling the dotnet SDK where they are. Is that a hopelessly naive position?