Add MSBuildProjectFinder and update ProjectModel to handle latest MSBuild updates

This commit is contained in:
Nate McMaster 2016-10-05 10:08:37 -07:00
Родитель 40ff75d536
Коммит dde022961c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: BD729980AA6A21BD
6 изменённых файлов: 223 добавлений и 29 удалений

Просмотреть файл

@ -9,9 +9,10 @@ using System.Xml;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Build.Framework;
using Microsoft.Extensions.ProjectModel.Internal;
using NuGet.Frameworks;
namespace Microsoft.Extensions.ProjectModel
@ -22,6 +23,7 @@ namespace Microsoft.Extensions.ProjectModel
private IFileInfo _fileInfo;
private string[] _buildTargets;
private Dictionary<string, string> _globalProperties = new Dictionary<string, string>();
private bool _explicitMsBuild;
public MsBuildProjectContextBuilder()
{
@ -46,7 +48,7 @@ namespace Microsoft.Extensions.ProjectModel
return this;
}
public MsBuildProjectContextBuilder WithDesignTimeBuild()
public MsBuildProjectContextBuilder AsDesignTimeBuild()
{
// don't to expensive things
WithProperty("DesignTimeBuild", "true");
@ -55,16 +57,12 @@ namespace Microsoft.Extensions.ProjectModel
return this;
}
public MsBuildProjectContextBuilder WithMsBuild(MsBuildContext context)
// should be needed in most cases, but can be used to override
public MsBuildProjectContextBuilder UseMsBuild(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);
return WithProperty("MSBuildExtensionsPath", context.ExtensionsPath);
_explicitMsBuild = true;
SetMsBuildContext(context);
return this;
}
public MsBuildProjectContextBuilder WithProperty(string property, string value)
@ -86,6 +84,13 @@ namespace Microsoft.Extensions.ProjectModel
public MsBuildProjectContextBuilder WithProjectFile(IFileInfo fileInfo)
{
if (!_explicitMsBuild)
{
var projectDir = Path.GetDirectoryName(fileInfo.PhysicalPath);
var sdk = DotNetCoreSdkResolver.DefaultResolver.ResolveProjectSdk(projectDir);
SetMsBuildContext(MsBuildContext.FromDotNetSdk(sdk));
}
_fileInfo = fileInfo;
return this;
}
@ -109,7 +114,6 @@ namespace Microsoft.Extensions.ProjectModel
protected virtual void Initialize()
{
WithBuildTargets(new[] { "ResolveReferences" });
WithMsBuild(MsBuildContext.FromCurrentDotNetSdk());
WithProperty("_ResolveReferenceDependencies", "true");
}
@ -163,6 +167,18 @@ namespace Microsoft.Extensions.ProjectModel
throw new InvalidOperationException(sb.ToString());
}
private void SetMsBuildContext(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);
}
private class InMemoryLogger : ILogger
{
private readonly Stack<Action> _onShutdown = new Stack<Action>();

Просмотреть файл

@ -0,0 +1,72 @@
// 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.Collections.Generic;
using System.IO;
using System.Linq;
// TODO if this becomes a true API instead of .Sources package, put strings into resource file
namespace Microsoft.Extensions.ProjectModel
{
internal class MsBuildProjectFinder
{
private readonly string _directory;
public MsBuildProjectFinder(string directory)
{
if (string.IsNullOrEmpty(directory))
{
throw new ArgumentException("Value cannot be null or empty", nameof(directory));
}
_directory = directory;
}
public string FindMsBuildProject(string project = null)
{
var projectPath = project ?? _directory;
if (!Path.IsPathRooted(projectPath))
{
projectPath = Path.Combine(_directory, projectPath);
}
if (Directory.Exists(projectPath))
{
var projects = FindProjectFiles(projectPath).ToList();
if (projects.Count > 1)
{
throw MultipleProjectsFound(projectPath);
}
if (projects.Count == 0)
{
throw NoProjectsFound(projectPath);
}
return projects[0];
}
if (!File.Exists(projectPath))
{
throw FileDoesNotExist(projectPath);
}
return projectPath;
}
protected virtual Exception FileDoesNotExist(string filePath)
=> new InvalidOperationException($"No file was found at '{filePath}'.");
protected virtual Exception MultipleProjectsFound(string directory)
=> new InvalidOperationException($"Multiple MSBuild project files found in '{directory}'.");
protected virtual Exception NoProjectsFound(string directory)
=> new InvalidOperationException($"Could not find a MSBuild project file in '{directory}'.");
protected virtual IEnumerable<string> FindProjectFiles(string directory)
=> Directory.EnumerateFileSystemEntries(directory, "*.*proj", SearchOption.TopDirectoryOnly);
}
}

Просмотреть файл

@ -3,8 +3,11 @@
using System.IO;
using System.Linq;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.ProjectModel.Tests;
using NuGet.Frameworks;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.Extensions.ProjectModel
{
@ -13,10 +16,12 @@ namespace Microsoft.Extensions.ProjectModel
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;
public MsBuildProjectContextBuilderTest(MsBuildFixture fixture)
public MsBuildProjectContextBuilderTest(MsBuildFixture fixture, ITestOutputHelper output)
{
_fixture = fixture;
_output = output;
}
[Fact(Skip = SkipReason)]
@ -24,8 +29,17 @@ namespace Microsoft.Extensions.ProjectModel
{
using (var fileProvider = new TemporaryFileProvider())
{
// TODO When .NET Core SDK is available, detect and add to this test project
// fileProvider.Add("test.nuget.targets", "Import .NET Core SDK here");
// TODO remove when SDK becomes available on other feeds
fileProvider.Add("NuGet.config", @"
<configuration>
<packageSources>
<clear />
<add key=""dotnet-core"" value=""https://dotnet.myget.org/F/dotnet-core/api/v3/index.json"" />
<add key=""dotnet-buildtools"" value=""https://dotnet.myget.org/F/dotnet-buildtools/api/v3/index.json"" />
<add key=""nugetbuild"" value=""https://www.myget.org/F/nugetbuild/api/v3/index.json"" />
</packageSources>
</configuration>");
fileProvider.Add("test.csproj", @"
<Project ToolsVersion=""14.0"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<Import Project=""$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"" />
@ -34,14 +48,23 @@ namespace Microsoft.Extensions.ProjectModel
<RootNamespace>Microsoft.TestProject</RootNamespace>
<ProjectName>TestProject</ProjectName>
<OutputType>Library</OutputType>
<TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v1.0</TargetFrameworkVersion>
<TargetFrameworks>netcoreapp1.0</TargetFrameworks>
<OutputPath>bin\$(Configuration)</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include=""**\*.cs"" Exclude=""Excluded.cs"" />
</ItemGroup>
<ItemGroup>
<PackageReference Include=""Microsoft.NETCore.Sdk"">
<Version>1.0.0-*</Version>
</PackageReference>
<PackageReference Include=""Microsoft.NETCore.App"">
<Version>1.0.1</Version>
</PackageReference>
</ItemGroup>
<Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />
</Project>
");
@ -51,22 +74,28 @@ namespace Microsoft.Extensions.ProjectModel
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 expectedCompileItems = new[] { "One.cs", "Two.cs" }.Select(p => Path.Combine(fileProvider.Root, p)).ToArray();
var builder = new MsBuildProjectContextBuilder()
.WithMsBuild(testContext)
.WithDesignTimeBuild()
// In latest version of MSBuild, setting this property causes evaluation errors when SDK is not available
//.WithConfiguration("Debug")
.AsDesignTimeBuild()
.UseMsBuild(testContext)
.WithTargetFramework(FrameworkConstants.CommonFrameworks.NetCoreApp10)
.WithConfiguration("Debug")
.WithProjectFile(fileProvider.GetFileInfo("test.csproj"));
// TODO remove ignoreBuildErrors flag
// this always throws because Microsoft.NETCore.SDK is not available.
var context = builder.Build(ignoreBuildErrors: true);
var context = builder.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", "test.dll"), context.AssemblyFullPath);
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);

Просмотреть файл

@ -0,0 +1,73 @@
// 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 Xunit;
namespace Microsoft.Extensions.ProjectModel.MsBuild
{
public class MsBuildProjectFinderTest
{
[Theory]
[InlineData(".csproj")]
[InlineData(".vbproj")]
[InlineData(".fsproj")]
public void FindsSingleProject(string extension)
{
using (var files = new TemporaryFileProvider())
{
var filename = "TestProject" + extension;
files.Add(filename, "");
var finder = new MsBuildProjectFinder(files.Root);
Assert.Equal(Path.Combine(files.Root, filename), finder.FindMsBuildProject());
}
}
[Fact]
public void ThrowsWhenNoFile()
{
using (var files = new TemporaryFileProvider())
{
var finder = new MsBuildProjectFinder(files.Root);
Assert.Throws<InvalidOperationException>(() => finder.FindMsBuildProject());
}
}
[Fact]
public void ThrowsWhenMultipleFile()
{
using (var files = new TemporaryFileProvider())
{
files.Add("Test1.csproj", "");
files.Add("Test2.csproj", "");
var finder = new MsBuildProjectFinder(files.Root);
Assert.Throws<InvalidOperationException>(() => finder.FindMsBuildProject());
}
}
[Fact]
public void ThrowsWhenFileDoesNotExist()
{
using (var files = new TemporaryFileProvider())
{
var finder = new MsBuildProjectFinder(files.Root);
Assert.Throws<InvalidOperationException>(() => finder.FindMsBuildProject("test.csproj"));
}
}
[Fact]
public void ThrowsWhenRootDoesNotExist()
{
var files = new TemporaryFileProvider();
var finder = new MsBuildProjectFinder(files.Root);
files.Dispose();
Assert.Throws<InvalidOperationException>(() => finder.FindMsBuildProject());
}
}
}

Просмотреть файл

@ -1,7 +1,10 @@
{
"buildOptions": {
"warningsAsErrors": true,
"keyFile": "../../tools/Key.snk"
"keyFile": "../../tools/Key.snk",
"compile": {
"include": "../Shared/*.cs"
}
},
"dependencies": {
"NuGet.Frameworks": "3.5.0-*",
@ -13,7 +16,6 @@
"type": "build",
"version": "1.0.0-*"
},
"System.Runtime.InteropServices.RuntimeInformation": "4.0.0",
"dotnet-test-xunit": "2.2.0-*",
"xunit": "2.2.0-*"
},

Просмотреть файл

@ -6,12 +6,14 @@ using System.IO;
using Microsoft.Extensions.ProjectModel.Internal;
using NuGet.Versioning;
namespace Microsoft.Extensions.ProjectModel
namespace Microsoft.Extensions.ProjectModel.Tests
{
public class MsBuildFixture
{
private readonly SemanticVersion _minMsBuildVersion = SemanticVersion.Parse("1.0.0-preview3-00000");
// TODO remove this when preview3 stabilizies
private readonly SemanticVersion _minMsBuildVersion = SemanticVersion.Parse("1.0.0-preview3-003748");
// TODO remove when our CI updates to using MSBuild or when Microsoft/msbuild#4213 is resolved
internal MsBuildContext GetMsBuildContext()
{
// for CI