Use MSBuild project extensions instead of importing the users project

Implicit imports prevents using <Import> on a project file that has the Sdk attribute. This change instead generates a file in the MSBuildProjectExtensionsPath to inject targets require to find the UserSecretsId property in a project.

Resolves #242
This commit is contained in:
Nate McMaster 2016-12-07 14:32:41 -08:00
parent f277816556
commit d48f2abc7c
7 changed files with 70 additions and 83 deletions

View File

@ -5,19 +5,16 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.Extensions.Tools.Internal; using Microsoft.Extensions.Tools.Internal;
namespace Microsoft.Extensions.SecretManager.Tools.Internal namespace Microsoft.Extensions.SecretManager.Tools.Internal
{ {
public class ProjectIdResolver : IDisposable public class ProjectIdResolver
{ {
private const string TargetsFileName = "FindUserSecretsProperty.targets";
private const string DefaultConfig = "Debug"; private const string DefaultConfig = "Debug";
private readonly IReporter _reporter; private readonly IReporter _reporter;
private readonly string _workingDirectory; private readonly string _workingDirectory;
private readonly List<string> _tempFiles = new List<string>();
public ProjectIdResolver(IReporter reporter, string workingDirectory) public ProjectIdResolver(IReporter reporter, string workingDirectory)
{ {
@ -29,83 +26,82 @@ namespace Microsoft.Extensions.SecretManager.Tools.Internal
{ {
var finder = new MsBuildProjectFinder(_workingDirectory); var finder = new MsBuildProjectFinder(_workingDirectory);
var projectFile = finder.FindMsBuildProject(project); var projectFile = finder.FindMsBuildProject(project);
EnsureProjectExtensionTargetsExist(projectFile);
_reporter.Verbose(Resources.FormatMessage_Project_File_Path(projectFile)); _reporter.Verbose(Resources.FormatMessage_Project_File_Path(projectFile));
var targetFile = GetTargetFile();
var outputFile = Path.GetTempFileName();
_tempFiles.Add(outputFile);
configuration = !string.IsNullOrEmpty(configuration) configuration = !string.IsNullOrEmpty(configuration)
? configuration ? configuration
: DefaultConfig; : DefaultConfig;
var args = new[] var outputFile = Path.GetTempFileName();
try
{ {
"msbuild", var args = new[]
targetFile, {
"/nologo", "msbuild",
"/t:_FindUserSecretsProperty", projectFile,
$"/p:Project={projectFile}", "/nologo",
$"/p:OutputFile={outputFile}", "/t:_ExtractUserSecretsMetadata", // defined in ProjectIdResolverTargets.xml
$"/p:Configuration={configuration}" $"/p:_UserSecretsMetadataFile={outputFile}",
}; $"/p:Configuration={configuration}"
var psi = new ProcessStartInfo };
{ var psi = new ProcessStartInfo
FileName = DotNetMuxer.MuxerPathOrDefault(), {
Arguments = ArgumentEscaper.EscapeAndConcatenate(args), FileName = DotNetMuxer.MuxerPathOrDefault(),
RedirectStandardOutput = true, Arguments = ArgumentEscaper.EscapeAndConcatenate(args),
RedirectStandardError = true RedirectStandardOutput = true,
}; RedirectStandardError = true
};
#if DEBUG #if DEBUG
_reporter.Verbose($"Invoking '{psi.FileName} {psi.Arguments}'"); _reporter.Verbose($"Invoking '{psi.FileName} {psi.Arguments}'");
#endif #endif
var process = Process.Start(psi); var process = Process.Start(psi);
process.WaitForExit(); process.WaitForExit();
if (process.ExitCode != 0)
{
_reporter.Verbose(process.StandardOutput.ReadToEnd());
_reporter.Verbose(process.StandardError.ReadToEnd());
throw new InvalidOperationException(Resources.FormatError_ProjectFailedToLoad(projectFile));
}
var id = File.ReadAllText(outputFile)?.Trim();
if (string.IsNullOrEmpty(id))
{
throw new InvalidOperationException(Resources.FormatError_ProjectMissingId(projectFile));
}
return id;
if (process.ExitCode != 0)
{
_reporter.Verbose(process.StandardOutput.ReadToEnd());
_reporter.Verbose(process.StandardError.ReadToEnd());
throw new InvalidOperationException(Resources.FormatError_ProjectFailedToLoad(projectFile));
} }
finally
var id = File.ReadAllText(outputFile)?.Trim();
if (string.IsNullOrEmpty(id))
{ {
throw new InvalidOperationException(Resources.FormatError_ProjectMissingId(projectFile)); TryDelete(outputFile);
} }
return id;
} }
private string GetTargetFile() private void EnsureProjectExtensionTargetsExist(string projectFile)
{ {
var assemblyDir = Path.GetDirectoryName(GetType().GetTypeInfo().Assembly.Location); // relies on MSBuildProjectExtensionsPath and Microsoft.Common.targets to import this file
// into the target project
var projectExtensionsPath = Path.Combine(
Path.GetDirectoryName(projectFile),
"obj",
$"{Path.GetFileName(projectFile)}.usersecrets.targets");
// targets should be in one of these locations, depending on test setup and tools installation Directory.CreateDirectory(Path.GetDirectoryName(projectExtensionsPath));
var searchPaths = new[]
// should overwrite the file always. Hypothetically, another version of the user-secrets tool
// could have already put a file here. We want to ensure the target file matches the currently
// running tool
using (var resource = GetType().GetTypeInfo().Assembly.GetManifestResourceStream("ProjectIdResolverTargets.xml"))
using (var stream = new FileStream(projectExtensionsPath, FileMode.Create))
using (var writer = new StreamWriter(stream))
{ {
AppContext.BaseDirectory, writer.WriteLine("<!-- Auto-generated by dotnet-user-secrets. This file can be deleted and should not be commited to source control. -->");
assemblyDir, // next to assembly resource.CopyTo(stream);
Path.Combine(assemblyDir, "../../toolassets"), // inside the nupkg
Path.Combine(assemblyDir, "toolassets"), // for local builds
Path.Combine(AppContext.BaseDirectory, "../../toolassets"), // relative to packaged deps.json
};
return searchPaths
.Select(dir => Path.Combine(dir, TargetsFileName))
.Where(File.Exists)
.First();
}
public void Dispose()
{
foreach (var file in _tempFiles)
{
TryDelete(file);
} }
} }

View File

@ -23,7 +23,6 @@
</metadata> </metadata>
<files> <files>
<file src="prefercliruntime" target="\prefercliruntime" /> <file src="prefercliruntime" target="\prefercliruntime" />
<file src="toolassets\FindUserSecretsProperty.targets" target="toolassets\" />
<file src="dotnet-user-secrets.dll" target="lib\netcoreapp1.0\" /> <file src="dotnet-user-secrets.dll" target="lib\netcoreapp1.0\" />
<file src="dotnet-user-secrets.deps.json" target="lib\netcoreapp1.0\" /> <file src="dotnet-user-secrets.deps.json" target="lib\netcoreapp1.0\" />
<file src="dotnet-user-secrets.runtimeconfig.json" target="lib\netcoreapp1.0\" /> <file src="dotnet-user-secrets.runtimeconfig.json" target="lib\netcoreapp1.0\" />

View File

@ -124,10 +124,8 @@ namespace Microsoft.Extensions.SecretManager.Tools
return options.Id; return options.Id;
} }
using (var resolver = new ProjectIdResolver(reporter, _workingDirectory)) var resolver = new ProjectIdResolver(reporter, _workingDirectory);
{ return resolver.Resolve(options.Project, options.Configuration);
return resolver.Resolve(options.Project, options.Configuration);
}
} }
} }
} }

View File

@ -5,16 +5,17 @@
"emitEntryPoint": true, "emitEntryPoint": true,
"warningsAsErrors": true, "warningsAsErrors": true,
"keyFile": "../../tools/Key.snk", "keyFile": "../../tools/Key.snk",
"copyToOutput": "toolassets/*.targets", "embed": {
"mappings": {
"ProjectIdResolverTargets.xml": "./resources/ProjectIdResolverTargets.xml"
}
},
"compile": { "compile": {
"include": "../Shared/**/*.cs" "include": "../Shared/**/*.cs"
} }
}, },
"publishOptions": { "publishOptions": {
"include": [ "include": "prefercliruntime"
"toolassets/*.targets",
"prefercliruntime"
]
}, },
"dependencies": { "dependencies": {
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0", "Microsoft.Extensions.Configuration.UserSecrets": "1.0.0",

View File

@ -0,0 +1,5 @@
<Project>
<Target Name="_ExtractUserSecretsMetadata">
<WriteLinesToFile File="$(_UserSecretsMetadataFile)" Lines="$(UserSecretsId)" />
</Target>
</Project>

View File

@ -1,6 +0,0 @@
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(Project)" />
<Target Name="_FindUserSecretsProperty">
<WriteLinesToFile File="$(OutputFile)" Lines="$(UserSecretsId)" />
</Target>
</Project>

View File

@ -32,9 +32,7 @@ namespace Microsoft.Extensions.Configuration.UserSecrets.Tests
return GetTempSecretProject(out userSecretsId); return GetTempSecretProject(out userSecretsId);
} }
private const string ProjectTemplate = @"<Project ToolsVersion=""14.0"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003""> private const string ProjectTemplate = @"<Project ToolsVersion=""15.0"" Sdk=""Microsoft.NET.Sdk"">
<Import Project=""$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"" />
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp1.0</TargetFrameworks> <TargetFrameworks>netcoreapp1.0</TargetFrameworks>
@ -43,12 +41,8 @@ namespace Microsoft.Extensions.Configuration.UserSecrets.Tests
<ItemGroup> <ItemGroup>
<Compile Include=""**\*.cs"" Exclude=""Excluded.cs"" /> <Compile Include=""**\*.cs"" Exclude=""Excluded.cs"" />
<PackageReference Include=""Microsoft.NET.Sdk"" Version=""1.0.0-*"" PrivateAssets=""All"" />
<PackageReference Include=""Microsoft.NETCore.App"" Version=""1.0.1"" /> <PackageReference Include=""Microsoft.NETCore.App"" Version=""1.0.1"" />
</ItemGroup> </ItemGroup>
<Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />
</Project>"; </Project>";
public string GetTempSecretProject(out string userSecretsId) public string GetTempSecretProject(out string userSecretsId)