dotnet / sdk

Core functionality needed to create .NET Core projects, that is shared between Visual Studio and CLI
https://dot.net/core
MIT License
2.66k stars 1.06k forks source link

Build for desktop framework on non-windows platforms #335

Closed dasMulli closed 4 years ago

dasMulli commented 7 years ago

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?

borgdylan commented 7 years ago

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.

dasMulli commented 7 years ago

@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)

borgdylan commented 7 years ago

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

gulbanana commented 7 years ago

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

kjnilsson commented 7 years ago

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?

dasMulli commented 7 years ago

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

kjnilsson commented 7 years ago

@dasMulli thanks - I get:

error MSB4057: The target "Pack" does not exist in the project
dasMulli commented 7 years ago

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

kjnilsson commented 7 years ago

@dasMulli dotnet pack works if I don't multi-target. No idea if it works on Windows. See: https://github.com/rabbitmq/rabbitmq-dotnet-client/blob/rabbitmq-dotnet-client-308/projects/client/RabbitMQ.Client/RabbitMQ.Client.csproj

dasMulli commented 7 years ago

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

kjnilsson commented 7 years ago

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.

dasMulli commented 7 years ago

@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)

kjnilsson commented 7 years ago

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

conniey commented 7 years ago

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

JoseFMP commented 7 years ago

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

Anyone else got this to work?

dasMulli commented 7 years ago

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

JoseFMP commented 7 years ago

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

kekekeks commented 7 years ago

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

DustinCampbell commented 7 years ago

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

JoseFMP commented 7 years ago

@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?

DustinCampbell commented 7 years ago

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.

dasMulli commented 7 years ago

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

DustinCampbell commented 7 years ago

ah yes, that makes sense @dasMulli.

ajiehatajie commented 7 years ago
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

RehanSaeed commented 7 years ago

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.

dasMulli commented 7 years ago

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)

am11 commented 7 years ago

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

ivanatpr commented 7 years ago

@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

dasMulli commented 7 years ago

@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)

ivanatpr commented 7 years ago

@dasMulli yup that fixed it thanks!

kierenj commented 7 years ago

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>
JoseFMP commented 7 years ago

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

matkoch commented 6 years ago

@matkoch

jnm2 commented 6 years ago

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?

DustinCampbell commented 6 years ago

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

jnm2 commented 6 years ago

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.

DustinCampbell commented 6 years ago

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

jnm2 commented 6 years ago

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

DustinCampbell commented 6 years ago

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.

bjorkstromm commented 6 years ago

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

jnm2 commented 6 years ago

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.

patrickjamesbarry commented 6 years ago

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

TheAngryByrd commented 6 years ago

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

dsyme commented 6 years ago

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.

OlegZee commented 6 years ago

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
CIPop commented 6 years ago

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.

nguerrera commented 6 years ago

@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>
cwe1ss commented 6 years ago

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>
CIPop commented 6 years ago

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

jskeet commented 6 years ago

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?