From 92a8d6d3bd44265ebf1741bf106b938b452ace79 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 6 Oct 2016 13:03:05 -0700 Subject: [PATCH] Add API for multi-tfm projects (#184) Project builder supports auto-detecting the framework --- .../MsBuild/MsBuildProjectContext.cs | 14 ++- .../MsBuild/MsBuildProjectContextBuilder.cs | 101 +++++++++++++++--- .../MsBuildProjectContextBuilderTest.cs | 100 +++++++++++------ 3 files changed, 165 insertions(+), 50 deletions(-) diff --git a/src/Microsoft.Extensions.ProjectModel.Sources/MsBuild/MsBuildProjectContext.cs b/src/Microsoft.Extensions.ProjectModel.Sources/MsBuild/MsBuildProjectContext.cs index d8ca3020bb..b11c6b48d9 100644 --- a/src/Microsoft.Extensions.ProjectModel.Sources/MsBuild/MsBuildProjectContext.cs +++ b/src/Microsoft.Extensions.ProjectModel.Sources/MsBuild/MsBuildProjectContext.cs @@ -35,7 +35,19 @@ namespace Microsoft.Extensions.ProjectModel public string ProjectName => FindProperty("ProjectName") ?? _name; 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); // TODO get from actual properties according to TFM diff --git a/src/Microsoft.Extensions.ProjectModel.Sources/MsBuild/MsBuildProjectContextBuilder.cs b/src/Microsoft.Extensions.ProjectModel.Sources/MsBuild/MsBuildProjectContextBuilder.cs index 2216860816..20d1f6cdc4 100644 --- a/src/Microsoft.Extensions.ProjectModel.Sources/MsBuild/MsBuildProjectContextBuilder.cs +++ b/src/Microsoft.Extensions.ProjectModel.Sources/MsBuild/MsBuildProjectContextBuilder.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders.Physical; using Microsoft.Extensions.ProjectModel.Internal; using NuGet.Frameworks; +using System.Linq; namespace Microsoft.Extensions.ProjectModel { @@ -23,13 +24,37 @@ namespace Microsoft.Extensions.ProjectModel private IFileInfo _fileInfo; private string[] _buildTargets; private Dictionary _globalProperties = new Dictionary(); - private bool _explicitMsBuild; + private MsBuildContext _msbuildContext; public MsBuildProjectContextBuilder() { 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) { if (targets == null) @@ -60,8 +85,27 @@ namespace Microsoft.Extensions.ProjectModel // should be needed in most cases, but can be used to override public MsBuildProjectContextBuilder UseMsBuild(MsBuildContext context) { - _explicitMsBuild = true; - SetMsBuildContext(context); + _msbuildContext = 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 properties) + { + foreach (var prop in properties) + { + _globalProperties[prop.Key] = prop.Value; + } + return this; } @@ -84,11 +128,11 @@ namespace Microsoft.Extensions.ProjectModel public MsBuildProjectContextBuilder WithProjectFile(IFileInfo fileInfo) { - if (!_explicitMsBuild) + if (_msbuildContext == null) { var projectDir = Path.GetDirectoryName(fileInfo.PhysicalPath); var sdk = DotNetCoreSdkResolver.DefaultResolver.ResolveProjectSdk(projectDir); - SetMsBuildContext(MsBuildContext.FromDotNetSdk(sdk)); + UseMsBuild(MsBuildContext.FromDotNetSdk(sdk)); } _fileInfo = fileInfo; @@ -105,12 +149,41 @@ namespace Microsoft.Extensions.ProjectModel { var projectCollection = CreateProjectCollection(); 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 name = Path.GetFileNameWithoutExtension(_fileInfo.Name); return new MsBuildProjectContext(name, _configuration, projectInstance); } + public IEnumerable 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() { WithBuildTargets(new[] { "ResolveReferences" }); @@ -167,22 +240,20 @@ namespace Microsoft.Extensions.ProjectModel throw new InvalidOperationException(sb.ToString()); } - private void SetMsBuildContext(MsBuildContext context) + private IEnumerable GetAvailableTargetFrameworks(Project project) { - /* - 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); + var frameworks = project.GetProperty("TargetFrameworks")?.EvaluatedValue; + if (string.IsNullOrEmpty(frameworks)) + { + return Enumerable.Empty(); + } + return frameworks.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); } private class InMemoryLogger : ILogger { private readonly Stack _onShutdown = new Stack(); - + internal IList Errors = new List(); public string Parameters { get; set; } diff --git a/test/Microsoft.Extensions.ProjectModel.Tests/MsBuild/MsBuildProjectContextBuilderTest.cs b/test/Microsoft.Extensions.ProjectModel.Tests/MsBuild/MsBuildProjectContextBuilderTest.cs index 118842ebc3..1f840b9be6 100644 --- a/test/Microsoft.Extensions.ProjectModel.Tests/MsBuild/MsBuildProjectContextBuilderTest.cs +++ b/test/Microsoft.Extensions.ProjectModel.Tests/MsBuild/MsBuildProjectContextBuilderTest.cs @@ -1,6 +1,7 @@ // 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. +using System; using System.IO; using System.Linq; using Microsoft.DotNet.Cli.Utils; @@ -11,26 +12,59 @@ using Xunit.Abstractions; namespace Microsoft.Extensions.ProjectModel { - public class MsBuildProjectContextBuilderTest : IClassFixture + public class MsBuildProjectContextBuilderTest : IClassFixture, IDisposable { private const string SkipReason = "CI doesn't yet have a new enough version of .NET Core SDK"; private readonly MsBuildFixture _fixture; private readonly ITestOutputHelper _output; + private readonly TemporaryFileProvider _files; public MsBuildProjectContextBuilderTest(MsBuildFixture fixture, ITestOutputHelper output) { _fixture = fixture; _output = output; + _files = new TemporaryFileProvider(); + } + + public void Dispose() + { + _files.Dispose(); + } + + [Fact(Skip = SkipReason)] + public void BuildsAllTargetFrameworks() + { + + _files.Add("test.proj", @" + + + net451;netstandard1.3 + + +"); + var contexts = new MsBuildProjectContextBuilder() + .WithBuildTargets(Array.Empty()) + .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)] public void ExecutesDesignTimeBuild() { - using (var fileProvider = new TemporaryFileProvider()) - { - // TODO remove when SDK becomes available on other feeds - fileProvider.Add("NuGet.config", @" + // TODO remove when SDK becomes available on other feeds + _files.Add("NuGet.config", @" @@ -40,7 +74,7 @@ namespace Microsoft.Extensions.ProjectModel "); - fileProvider.Add("test.csproj", @" + _files.Add("test.csproj", @" @@ -68,39 +102,37 @@ namespace Microsoft.Extensions.ProjectModel "); - fileProvider.Add("One.cs", "public class Abc {}"); - fileProvider.Add("Two.cs", "public class Abc2 {}"); - fileProvider.Add("Excluded.cs", "public class Abc {}"); + _files.Add("One.cs", "public class Abc {}"); + _files.Add("Two.cs", "public class Abc2 {}"); + _files.Add("Excluded.cs", "public class Abc {}"); - var testContext = _fixture.GetMsBuildContext(); + var testContext = _fixture.GetMsBuildContext(); - var muxer = Path.Combine(testContext.ExtensionsPath, "../..", "dotnet.exe"); - var result = Command - .Create(muxer, new[] { "restore3", Path.Combine(fileProvider.Root, "test.csproj") }) - .OnErrorLine(l => _output.WriteLine(l)) - .OnOutputLine(l => _output.WriteLine(l)) - .Execute(); - Assert.Equal(0, result.ExitCode); + var muxer = Path.Combine(testContext.ExtensionsPath, "../..", "dotnet.exe"); + var result = Command + .Create(muxer, new[] { "restore3", Path.Combine(_files.Root, "test.csproj") }) + .OnErrorLine(l => _output.WriteLine(l)) + .OnOutputLine(l => _output.WriteLine(l)) + .Execute(); + Assert.Equal(0, result.ExitCode); - var expectedCompileItems = new[] { "One.cs", "Two.cs" }.Select(p => Path.Combine(fileProvider.Root, p)).ToArray(); - var builder = new MsBuildProjectContextBuilder() - .AsDesignTimeBuild() - .UseMsBuild(testContext) - .WithTargetFramework(FrameworkConstants.CommonFrameworks.NetCoreApp10) - .WithConfiguration("Debug") - .WithProjectFile(fileProvider.GetFileInfo("test.csproj")); + var expectedCompileItems = new[] { "One.cs", "Two.cs" }.Select(p => Path.Combine(_files.Root, p)).ToArray(); - 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(fileProvider.GetFileInfo("obj").Exists); - Assert.Equal(expectedCompileItems, context.CompilationItems.OrderBy(i => i).ToArray()); - Assert.Equal(Path.Combine(fileProvider.Root, "bin", "Debug", "netcoreapp1.0", "test.dll"), context.AssemblyFullPath); - Assert.True(context.IsClassLibrary); - Assert.Equal("TestProject", context.ProjectName); - Assert.Equal(FrameworkConstants.CommonFrameworks.NetCoreApp10, context.TargetFramework); - Assert.Equal("Microsoft.TestProject", context.RootNamespace); - } + Assert.False(_files.GetFileInfo("bin").Exists); + Assert.False(_files.GetFileInfo("obj").Exists); + Assert.Equal(expectedCompileItems, context.CompilationItems.OrderBy(i => i).ToArray()); + Assert.Equal(Path.Combine(_files.Root, "bin", "Debug", "netcoreapp1.0", "test.dll"), context.AssemblyFullPath); + Assert.True(context.IsClassLibrary); + Assert.Equal("TestProject", context.ProjectName); + Assert.Equal(FrameworkConstants.CommonFrameworks.NetCoreApp10, context.TargetFramework); + Assert.Equal("Microsoft.TestProject", context.RootNamespace); } } }