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:
parent
f277816556
commit
d48f2abc7c
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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\" />
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<Project>
|
||||||
|
<Target Name="_ExtractUserSecretsMetadata">
|
||||||
|
<WriteLinesToFile File="$(_UserSecretsMetadataFile)" Lines="$(UserSecretsId)" />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue