Add API for multi-tfm projects (#184)

Project builder supports auto-detecting the framework
This commit is contained in:
Nate McMaster 2016-10-06 13:03:05 -07:00 committed by GitHub
parent 4214884b3e
commit 92a8d6d3bd
3 changed files with 165 additions and 50 deletions

View File

@ -35,7 +35,19 @@ namespace Microsoft.Extensions.ProjectModel
public string ProjectName => FindProperty("ProjectName") ?? _name; public string ProjectName => FindProperty("ProjectName") ?? _name;
public string Configuration { get; } public string Configuration { get; }
public NuGetFramework TargetFramework => NuGetFramework.Parse(FindProperty("NuGetTargetMoniker")); public NuGetFramework TargetFramework
{
get
{
var tfm = FindProperty("NuGetTargetMoniker") ?? FindProperty("TargetFramework");
if (tfm == null)
{
return null;
}
return NuGetFramework.Parse(tfm);
}
}
public bool IsClassLibrary => FindProperty("OutputType").Equals("Library", StringComparison.OrdinalIgnoreCase); public bool IsClassLibrary => FindProperty("OutputType").Equals("Library", StringComparison.OrdinalIgnoreCase);
// TODO get from actual properties according to TFM // TODO get from actual properties according to TFM

View File

@ -14,6 +14,7 @@ using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical; using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.ProjectModel.Internal; using Microsoft.Extensions.ProjectModel.Internal;
using NuGet.Frameworks; using NuGet.Frameworks;
using System.Linq;
namespace Microsoft.Extensions.ProjectModel namespace Microsoft.Extensions.ProjectModel
{ {
@ -23,13 +24,37 @@ namespace Microsoft.Extensions.ProjectModel
private IFileInfo _fileInfo; private IFileInfo _fileInfo;
private string[] _buildTargets; private string[] _buildTargets;
private Dictionary<string, string> _globalProperties = new Dictionary<string, string>(); private Dictionary<string, string> _globalProperties = new Dictionary<string, string>();
private bool _explicitMsBuild; private MsBuildContext _msbuildContext;
public MsBuildProjectContextBuilder() public MsBuildProjectContextBuilder()
{ {
Initialize(); Initialize();
} }
public virtual MsBuildProjectContextBuilder Clone()
{
var builder = new MsBuildProjectContextBuilder()
.WithProperties(_globalProperties)
.WithBuildTargets(_buildTargets);
if (_msbuildContext != null)
{
builder.UseMsBuild(_msbuildContext);
}
if (_fileInfo != null)
{
builder.WithProjectFile(_fileInfo);
}
if (_configuration != null)
{
builder.WithConfiguration(_configuration);
}
return builder;
}
public MsBuildProjectContextBuilder WithBuildTargets(string[] targets) public MsBuildProjectContextBuilder WithBuildTargets(string[] targets)
{ {
if (targets == null) if (targets == null)
@ -60,8 +85,27 @@ namespace Microsoft.Extensions.ProjectModel
// should be needed in most cases, but can be used to override // should be needed in most cases, but can be used to override
public MsBuildProjectContextBuilder UseMsBuild(MsBuildContext context) public MsBuildProjectContextBuilder UseMsBuild(MsBuildContext context)
{ {
_explicitMsBuild = true; _msbuildContext = context;
SetMsBuildContext(context);
/*
Workaround https://github.com/Microsoft/msbuild/issues/999
Error: System.TypeInitializationException : The type initializer for 'BuildEnvironmentHelperSingleton' threw an exception.
Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio.
*/
Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", context.MsBuildExecutableFullPath);
WithProperty("MSBuildExtensionsPath", context.ExtensionsPath);
return this;
}
public MsBuildProjectContextBuilder WithProperties(IDictionary<string, string> properties)
{
foreach (var prop in properties)
{
_globalProperties[prop.Key] = prop.Value;
}
return this; return this;
} }
@ -84,11 +128,11 @@ namespace Microsoft.Extensions.ProjectModel
public MsBuildProjectContextBuilder WithProjectFile(IFileInfo fileInfo) public MsBuildProjectContextBuilder WithProjectFile(IFileInfo fileInfo)
{ {
if (!_explicitMsBuild) if (_msbuildContext == null)
{ {
var projectDir = Path.GetDirectoryName(fileInfo.PhysicalPath); var projectDir = Path.GetDirectoryName(fileInfo.PhysicalPath);
var sdk = DotNetCoreSdkResolver.DefaultResolver.ResolveProjectSdk(projectDir); var sdk = DotNetCoreSdkResolver.DefaultResolver.ResolveProjectSdk(projectDir);
SetMsBuildContext(MsBuildContext.FromDotNetSdk(sdk)); UseMsBuild(MsBuildContext.FromDotNetSdk(sdk));
} }
_fileInfo = fileInfo; _fileInfo = fileInfo;
@ -105,12 +149,41 @@ namespace Microsoft.Extensions.ProjectModel
{ {
var projectCollection = CreateProjectCollection(); var projectCollection = CreateProjectCollection();
var project = CreateProject(_fileInfo, _configuration, _globalProperties, projectCollection); var project = CreateProject(_fileInfo, _configuration, _globalProperties, projectCollection);
if (project.GetProperty("TargetFramework") == null)
{
var frameworks = GetAvailableTargetFrameworks(project).ToList();
if (frameworks.Count > 1)
{
throw new InvalidOperationException($"Multiple frameworks are available. Either use {nameof(WithTargetFramework)} or {nameof(BuildAllTargetFrameworks)}");
}
if (frameworks.Count == 0)
{
throw new InvalidOperationException($"No frameworks are available. Either use {nameof(WithTargetFramework)} or {nameof(BuildAllTargetFrameworks)}");
}
project.SetGlobalProperty("TargetFramework", frameworks.Single());
}
var projectInstance = CreateProjectInstance(project, _buildTargets, ignoreBuildErrors); var projectInstance = CreateProjectInstance(project, _buildTargets, ignoreBuildErrors);
var name = Path.GetFileNameWithoutExtension(_fileInfo.Name); var name = Path.GetFileNameWithoutExtension(_fileInfo.Name);
return new MsBuildProjectContext(name, _configuration, projectInstance); return new MsBuildProjectContext(name, _configuration, projectInstance);
} }
public IEnumerable<MsBuildProjectContext> BuildAllTargetFrameworks()
{
var projectCollection = CreateProjectCollection();
var project = CreateProject(_fileInfo, _configuration, _globalProperties, projectCollection);
foreach (var framework in GetAvailableTargetFrameworks(project))
{
var builder = Clone();
builder.WithTargetFramework(framework);
yield return builder.Build();
}
}
protected virtual void Initialize() protected virtual void Initialize()
{ {
WithBuildTargets(new[] { "ResolveReferences" }); WithBuildTargets(new[] { "ResolveReferences" });
@ -167,22 +240,20 @@ namespace Microsoft.Extensions.ProjectModel
throw new InvalidOperationException(sb.ToString()); throw new InvalidOperationException(sb.ToString());
} }
private void SetMsBuildContext(MsBuildContext context) private IEnumerable<string> GetAvailableTargetFrameworks(Project project)
{ {
/* var frameworks = project.GetProperty("TargetFrameworks")?.EvaluatedValue;
Workaround https://github.com/Microsoft/msbuild/issues/999 if (string.IsNullOrEmpty(frameworks))
Error: System.TypeInitializationException : The type initializer for 'BuildEnvironmentHelperSingleton' threw an exception. {
Could not determine a valid location to MSBuild. Try running this process from the Developer Command Prompt for Visual Studio. return Enumerable.Empty<string>();
*/ }
return frameworks.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", context.MsBuildExecutableFullPath);
WithProperty("MSBuildExtensionsPath", context.ExtensionsPath);
} }
private class InMemoryLogger : ILogger private class InMemoryLogger : ILogger
{ {
private readonly Stack<Action> _onShutdown = new Stack<Action>(); private readonly Stack<Action> _onShutdown = new Stack<Action>();
internal IList<BuildErrorEventArgs> Errors = new List<BuildErrorEventArgs>(); internal IList<BuildErrorEventArgs> Errors = new List<BuildErrorEventArgs>();
public string Parameters { get; set; } public string Parameters { get; set; }

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils;
@ -11,26 +12,59 @@ using Xunit.Abstractions;
namespace Microsoft.Extensions.ProjectModel namespace Microsoft.Extensions.ProjectModel
{ {
public class MsBuildProjectContextBuilderTest : IClassFixture<MsBuildFixture> public class MsBuildProjectContextBuilderTest : IClassFixture<MsBuildFixture>, IDisposable
{ {
private const string SkipReason = "CI doesn't yet have a new enough version of .NET Core SDK"; private const string SkipReason = "CI doesn't yet have a new enough version of .NET Core SDK";
private readonly MsBuildFixture _fixture; private readonly MsBuildFixture _fixture;
private readonly ITestOutputHelper _output; private readonly ITestOutputHelper _output;
private readonly TemporaryFileProvider _files;
public MsBuildProjectContextBuilderTest(MsBuildFixture fixture, ITestOutputHelper output) public MsBuildProjectContextBuilderTest(MsBuildFixture fixture, ITestOutputHelper output)
{ {
_fixture = fixture; _fixture = fixture;
_output = output; _output = output;
_files = new TemporaryFileProvider();
}
public void Dispose()
{
_files.Dispose();
}
[Fact(Skip = SkipReason)]
public void BuildsAllTargetFrameworks()
{
_files.Add("test.proj", @"
<Project ToolsVersion=""14.0"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<PropertyGroup>
<TargetFrameworks>net451;netstandard1.3</TargetFrameworks>
</PropertyGroup>
</Project>
");
var contexts = new MsBuildProjectContextBuilder()
.WithBuildTargets(Array.Empty<string>())
.WithProjectFile(_files.GetFileInfo("test.proj"))
.BuildAllTargetFrameworks()
.ToList();
Assert.Collection(contexts,
context =>
{
Assert.Equal(FrameworkConstants.CommonFrameworks.Net451, context.TargetFramework);
},
context =>
{
Assert.Equal(FrameworkConstants.CommonFrameworks.NetStandard13, context.TargetFramework);
});
} }
[Fact(Skip = SkipReason)] [Fact(Skip = SkipReason)]
public void ExecutesDesignTimeBuild() public void ExecutesDesignTimeBuild()
{ {
using (var fileProvider = new TemporaryFileProvider()) // TODO remove when SDK becomes available on other feeds
{ _files.Add("NuGet.config", @"
// TODO remove when SDK becomes available on other feeds
fileProvider.Add("NuGet.config", @"
<configuration> <configuration>
<packageSources> <packageSources>
<clear /> <clear />
@ -40,7 +74,7 @@ namespace Microsoft.Extensions.ProjectModel
</packageSources> </packageSources>
</configuration>"); </configuration>");
fileProvider.Add("test.csproj", @" _files.Add("test.csproj", @"
<Project ToolsVersion=""14.0"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003""> <Project ToolsVersion=""14.0"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<Import Project=""$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"" /> <Import Project=""$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"" />
@ -68,39 +102,37 @@ namespace Microsoft.Extensions.ProjectModel
<Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" /> <Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />
</Project> </Project>
"); ");
fileProvider.Add("One.cs", "public class Abc {}"); _files.Add("One.cs", "public class Abc {}");
fileProvider.Add("Two.cs", "public class Abc2 {}"); _files.Add("Two.cs", "public class Abc2 {}");
fileProvider.Add("Excluded.cs", "public class Abc {}"); _files.Add("Excluded.cs", "public class Abc {}");
var testContext = _fixture.GetMsBuildContext(); var testContext = _fixture.GetMsBuildContext();
var muxer = Path.Combine(testContext.ExtensionsPath, "../..", "dotnet.exe"); var muxer = Path.Combine(testContext.ExtensionsPath, "../..", "dotnet.exe");
var result = Command var result = Command
.Create(muxer, new[] { "restore3", Path.Combine(fileProvider.Root, "test.csproj") }) .Create(muxer, new[] { "restore3", Path.Combine(_files.Root, "test.csproj") })
.OnErrorLine(l => _output.WriteLine(l)) .OnErrorLine(l => _output.WriteLine(l))
.OnOutputLine(l => _output.WriteLine(l)) .OnOutputLine(l => _output.WriteLine(l))
.Execute(); .Execute();
Assert.Equal(0, result.ExitCode); Assert.Equal(0, result.ExitCode);
var expectedCompileItems = new[] { "One.cs", "Two.cs" }.Select(p => Path.Combine(fileProvider.Root, p)).ToArray(); var expectedCompileItems = new[] { "One.cs", "Two.cs" }.Select(p => Path.Combine(_files.Root, p)).ToArray();
var builder = new MsBuildProjectContextBuilder()
.AsDesignTimeBuild()
.UseMsBuild(testContext)
.WithTargetFramework(FrameworkConstants.CommonFrameworks.NetCoreApp10)
.WithConfiguration("Debug")
.WithProjectFile(fileProvider.GetFileInfo("test.csproj"));
var context = builder.Build(); var context = new MsBuildProjectContextBuilder()
.AsDesignTimeBuild()
.UseMsBuild(testContext)
.WithConfiguration("Debug")
.WithProjectFile(_files.GetFileInfo("test.csproj"))
.Build();
Assert.False(fileProvider.GetFileInfo("bin").Exists); Assert.False(_files.GetFileInfo("bin").Exists);
Assert.False(fileProvider.GetFileInfo("obj").Exists); Assert.False(_files.GetFileInfo("obj").Exists);
Assert.Equal(expectedCompileItems, context.CompilationItems.OrderBy(i => i).ToArray()); Assert.Equal(expectedCompileItems, context.CompilationItems.OrderBy(i => i).ToArray());
Assert.Equal(Path.Combine(fileProvider.Root, "bin", "Debug", "netcoreapp1.0", "test.dll"), context.AssemblyFullPath); Assert.Equal(Path.Combine(_files.Root, "bin", "Debug", "netcoreapp1.0", "test.dll"), context.AssemblyFullPath);
Assert.True(context.IsClassLibrary); Assert.True(context.IsClassLibrary);
Assert.Equal("TestProject", context.ProjectName); Assert.Equal("TestProject", context.ProjectName);
Assert.Equal(FrameworkConstants.CommonFrameworks.NetCoreApp10, context.TargetFramework); Assert.Equal(FrameworkConstants.CommonFrameworks.NetCoreApp10, context.TargetFramework);
Assert.Equal("Microsoft.TestProject", context.RootNamespace); Assert.Equal("Microsoft.TestProject", context.RootNamespace);
}
} }
} }
} }