Add MSBuildProjectFinder and update ProjectModel to handle latest MSBuild updates
This commit is contained in:
parent
40ff75d536
commit
dde022961c
|
|
@ -9,9 +9,10 @@ using System.Xml;
|
||||||
using Microsoft.Build.Construction;
|
using Microsoft.Build.Construction;
|
||||||
using Microsoft.Build.Evaluation;
|
using Microsoft.Build.Evaluation;
|
||||||
using Microsoft.Build.Execution;
|
using Microsoft.Build.Execution;
|
||||||
|
using Microsoft.Build.Framework;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Microsoft.Extensions.FileProviders.Physical;
|
using Microsoft.Extensions.FileProviders.Physical;
|
||||||
using Microsoft.Build.Framework;
|
using Microsoft.Extensions.ProjectModel.Internal;
|
||||||
using NuGet.Frameworks;
|
using NuGet.Frameworks;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.ProjectModel
|
namespace Microsoft.Extensions.ProjectModel
|
||||||
|
|
@ -22,6 +23,7 @@ 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;
|
||||||
|
|
||||||
public MsBuildProjectContextBuilder()
|
public MsBuildProjectContextBuilder()
|
||||||
{
|
{
|
||||||
|
|
@ -46,7 +48,7 @@ namespace Microsoft.Extensions.ProjectModel
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MsBuildProjectContextBuilder WithDesignTimeBuild()
|
public MsBuildProjectContextBuilder AsDesignTimeBuild()
|
||||||
{
|
{
|
||||||
// don't to expensive things
|
// don't to expensive things
|
||||||
WithProperty("DesignTimeBuild", "true");
|
WithProperty("DesignTimeBuild", "true");
|
||||||
|
|
@ -55,16 +57,12 @@ namespace Microsoft.Extensions.ProjectModel
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MsBuildProjectContextBuilder WithMsBuild(MsBuildContext context)
|
// should be needed in most cases, but can be used to override
|
||||||
|
public MsBuildProjectContextBuilder UseMsBuild(MsBuildContext context)
|
||||||
{
|
{
|
||||||
/*
|
_explicitMsBuild = true;
|
||||||
Workaround https://github.com/Microsoft/msbuild/issues/999
|
SetMsBuildContext(context);
|
||||||
Error: System.TypeInitializationException : The type initializer for 'BuildEnvironmentHelperSingleton' threw an exception.
|
return this;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MsBuildProjectContextBuilder WithProperty(string property, string value)
|
public MsBuildProjectContextBuilder WithProperty(string property, string value)
|
||||||
|
|
@ -86,6 +84,13 @@ namespace Microsoft.Extensions.ProjectModel
|
||||||
|
|
||||||
public MsBuildProjectContextBuilder WithProjectFile(IFileInfo fileInfo)
|
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;
|
_fileInfo = fileInfo;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +114,6 @@ namespace Microsoft.Extensions.ProjectModel
|
||||||
protected virtual void Initialize()
|
protected virtual void Initialize()
|
||||||
{
|
{
|
||||||
WithBuildTargets(new[] { "ResolveReferences" });
|
WithBuildTargets(new[] { "ResolveReferences" });
|
||||||
WithMsBuild(MsBuildContext.FromCurrentDotNetSdk());
|
|
||||||
WithProperty("_ResolveReferenceDependencies", "true");
|
WithProperty("_ResolveReferenceDependencies", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,6 +167,18 @@ namespace Microsoft.Extensions.ProjectModel
|
||||||
throw new InvalidOperationException(sb.ToString());
|
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 class InMemoryLogger : ILogger
|
||||||
{
|
{
|
||||||
private readonly Stack<Action> _onShutdown = new Stack<Action>();
|
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.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
|
using Microsoft.Extensions.ProjectModel.Tests;
|
||||||
using NuGet.Frameworks;
|
using NuGet.Frameworks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.ProjectModel
|
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 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;
|
||||||
|
|
||||||
public MsBuildProjectContextBuilderTest(MsBuildFixture fixture)
|
public MsBuildProjectContextBuilderTest(MsBuildFixture fixture, ITestOutputHelper output)
|
||||||
{
|
{
|
||||||
_fixture = fixture;
|
_fixture = fixture;
|
||||||
|
_output = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = SkipReason)]
|
[Fact(Skip = SkipReason)]
|
||||||
|
|
@ -24,8 +29,17 @@ namespace Microsoft.Extensions.ProjectModel
|
||||||
{
|
{
|
||||||
using (var fileProvider = new TemporaryFileProvider())
|
using (var fileProvider = new TemporaryFileProvider())
|
||||||
{
|
{
|
||||||
// TODO When .NET Core SDK is available, detect and add to this test project
|
// TODO remove when SDK becomes available on other feeds
|
||||||
// fileProvider.Add("test.nuget.targets", "Import .NET Core SDK here");
|
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", @"
|
fileProvider.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"" />
|
||||||
|
|
@ -34,14 +48,23 @@ namespace Microsoft.Extensions.ProjectModel
|
||||||
<RootNamespace>Microsoft.TestProject</RootNamespace>
|
<RootNamespace>Microsoft.TestProject</RootNamespace>
|
||||||
<ProjectName>TestProject</ProjectName>
|
<ProjectName>TestProject</ProjectName>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
|
<TargetFrameworks>netcoreapp1.0</TargetFrameworks>
|
||||||
<TargetFrameworkVersion>v1.0</TargetFrameworkVersion>
|
<OutputPath>bin\$(Configuration)</OutputPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include=""**\*.cs"" Exclude=""Excluded.cs"" />
|
<Compile Include=""**\*.cs"" Exclude=""Excluded.cs"" />
|
||||||
</ItemGroup>
|
</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"" />
|
<Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />
|
||||||
</Project>
|
</Project>
|
||||||
");
|
");
|
||||||
|
|
@ -51,22 +74,28 @@ namespace Microsoft.Extensions.ProjectModel
|
||||||
|
|
||||||
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 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(fileProvider.Root, p)).ToArray();
|
||||||
var builder = new MsBuildProjectContextBuilder()
|
var builder = new MsBuildProjectContextBuilder()
|
||||||
.WithMsBuild(testContext)
|
.AsDesignTimeBuild()
|
||||||
.WithDesignTimeBuild()
|
.UseMsBuild(testContext)
|
||||||
// In latest version of MSBuild, setting this property causes evaluation errors when SDK is not available
|
.WithTargetFramework(FrameworkConstants.CommonFrameworks.NetCoreApp10)
|
||||||
//.WithConfiguration("Debug")
|
.WithConfiguration("Debug")
|
||||||
.WithProjectFile(fileProvider.GetFileInfo("test.csproj"));
|
.WithProjectFile(fileProvider.GetFileInfo("test.csproj"));
|
||||||
|
|
||||||
// TODO remove ignoreBuildErrors flag
|
var context = builder.Build();
|
||||||
// this always throws because Microsoft.NETCore.SDK is not available.
|
|
||||||
var context = builder.Build(ignoreBuildErrors: true);
|
|
||||||
|
|
||||||
Assert.False(fileProvider.GetFileInfo("bin").Exists);
|
Assert.False(fileProvider.GetFileInfo("bin").Exists);
|
||||||
Assert.False(fileProvider.GetFileInfo("obj").Exists);
|
Assert.False(fileProvider.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", "test.dll"), context.AssemblyFullPath);
|
Assert.Equal(Path.Combine(fileProvider.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);
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
"buildOptions": {
|
||||||
"warningsAsErrors": true,
|
"warningsAsErrors": true,
|
||||||
"keyFile": "../../tools/Key.snk"
|
"keyFile": "../../tools/Key.snk",
|
||||||
|
"compile": {
|
||||||
|
"include": "../Shared/*.cs"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"NuGet.Frameworks": "3.5.0-*",
|
"NuGet.Frameworks": "3.5.0-*",
|
||||||
|
|
@ -13,7 +16,6 @@
|
||||||
"type": "build",
|
"type": "build",
|
||||||
"version": "1.0.0-*"
|
"version": "1.0.0-*"
|
||||||
},
|
},
|
||||||
"System.Runtime.InteropServices.RuntimeInformation": "4.0.0",
|
|
||||||
"dotnet-test-xunit": "2.2.0-*",
|
"dotnet-test-xunit": "2.2.0-*",
|
||||||
"xunit": "2.2.0-*"
|
"xunit": "2.2.0-*"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@ using System.IO;
|
||||||
using Microsoft.Extensions.ProjectModel.Internal;
|
using Microsoft.Extensions.ProjectModel.Internal;
|
||||||
using NuGet.Versioning;
|
using NuGet.Versioning;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.ProjectModel
|
namespace Microsoft.Extensions.ProjectModel.Tests
|
||||||
{
|
{
|
||||||
public class MsBuildFixture
|
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()
|
internal MsBuildContext GetMsBuildContext()
|
||||||
{
|
{
|
||||||
// for CI
|
// for CI
|
||||||
Loading…
Reference in New Issue