提交 5ef9ff2f 编写于 作者: C Charles Stoner

Merge pull request #4975 from cston/nuget

Implement NuGetPackageResolver
......@@ -2,7 +2,6 @@
extern alias WORKSPACES;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
......@@ -29,7 +28,6 @@
using Roslyn.Utilities;
using DesktopMetadataReferenceResolver = WORKSPACES::Microsoft.CodeAnalysis.Scripting.DesktopMetadataReferenceResolver;
using GacFileResolver = WORKSPACES::Microsoft.CodeAnalysis.Scripting.GacFileResolver;
using NuGetPackageResolver = WORKSPACES::Microsoft.CodeAnalysis.Scripting.NuGetPackageResolver;
namespace Microsoft.CodeAnalysis.Editor.Interactive
{
......@@ -248,9 +246,13 @@ private Dispatcher Dispatcher
private static MetadataFileReferenceResolver CreateFileResolver(ImmutableArray<string> referencePaths, string baseDirectory)
{
var userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var packagesDirectory = (userProfilePath == null) ?
null :
PathUtilities.CombineAbsoluteAndRelativePaths(userProfilePath, PathUtilities.CombinePossiblyRelativeAndRelativePaths(".nuget", "packages"));
return new DesktopMetadataReferenceResolver(
new RelativePathReferenceResolver(referencePaths, baseDirectory),
NuGetPackageResolver.Instance,
string.IsNullOrEmpty(packagesDirectory) ? null : new NuGetPackageResolverImpl(packagesDirectory),
new GacFileResolver(
architectures: GacFileResolver.Default.Architectures, // TODO (tomat)
preferredCulture: System.Globalization.CultureInfo.CurrentCulture)); // TODO (tomat)
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
extern alias WORKSPACES;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Roslyn.Utilities;
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Reflection;
namespace Microsoft.CodeAnalysis.Editor.Interactive
{
internal sealed class NuGetPackageResolverImpl : WORKSPACES::Microsoft.CodeAnalysis.Scripting.NuGetPackageResolver
{
private const string ProjectJsonFramework = "net46";
private const string ProjectLockJsonFramework = ".NETFramework,Version=v4.6";
private readonly string _packagesDirectory;
private readonly Action<ProcessStartInfo> _restore;
internal NuGetPackageResolverImpl(string packagesDirectory, Action<ProcessStartInfo> restore = null)
{
Debug.Assert(PathUtilities.IsAbsolute(packagesDirectory));
_packagesDirectory = packagesDirectory;
_restore = restore ?? NuGetRestore;
}
internal override ImmutableArray<string> ResolveNuGetPackage(string reference)
{
string packageName;
string packageVersion;
if (!ParsePackageReference(reference, out packageName, out packageVersion))
{
return default(ImmutableArray<string>);
}
try
{
var tempPath = PathUtilities.CombineAbsoluteAndRelativePaths(Path.GetTempPath(), Guid.NewGuid().ToString("D"));
var tempDir = Directory.CreateDirectory(tempPath);
try
{
// Create project.json.
var projectJson = PathUtilities.CombineAbsoluteAndRelativePaths(tempPath, "project.json");
using (var stream = File.OpenWrite(projectJson))
using (var writer = new StreamWriter(stream))
{
WriteProjectJson(writer, packageName, packageVersion);
}
// Run "nuget.exe restore project.json" to generate project.lock.json.
NuGetRestore(projectJson);
// Read the references from project.lock.json.
var projectLockJson = PathUtilities.CombineAbsoluteAndRelativePaths(tempPath, "project.lock.json");
using (var stream = File.OpenRead(projectLockJson))
using (var reader = new StreamReader(stream))
{
return ReadProjectLockJson(_packagesDirectory, reader);
}
}
finally
{
tempDir.Delete(recursive: true);
}
}
catch (IOException)
{
}
catch (UnauthorizedAccessException)
{
}
return default(ImmutableArray<string>);
}
/// <summary>
/// Syntax is "id/version", matching references in project.lock.json.
/// </summary>
internal static bool ParsePackageReference(string reference, out string name, out string version)
{
var parts = reference.Split('/');
int n = reference.Length;
if ((parts.Length == 2) &&
(parts[0].Length > 0) &&
(parts[1].Length > 0))
{
name = parts[0];
version = parts[1];
return true;
}
name = null;
version = null;
return false;
}
/// <summary>
/// Generate a project.json file with the packages as "dependencies".
/// </summary>
internal static void WriteProjectJson(TextWriter writer, string packageName, string packageVersion)
{
using (var jsonWriter = new JsonTextWriter(writer))
{
jsonWriter.Formatting = Newtonsoft.Json.Formatting.Indented;
jsonWriter.WriteStartObject();
// "dependencies" : { "packageName" : "packageVersion" }
jsonWriter.WritePropertyName("dependencies");
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName(packageName, escape: true);
jsonWriter.WriteValue(packageVersion);
jsonWriter.WriteEndObject();
// "frameworks" : { "net46" : {} }
jsonWriter.WritePropertyName("frameworks");
jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName(ProjectJsonFramework, escape: true);
jsonWriter.WriteStartObject();
jsonWriter.WriteEndObject();
jsonWriter.WriteEndObject();
jsonWriter.WriteEndObject();
}
}
/// <summary>
/// Read the references from the project.lock.json file.
/// </summary>
internal static ImmutableArray<string> ReadProjectLockJson(string packagesDirectory, TextReader reader)
{
JObject obj;
using (var jsonReader = new JsonTextReader(reader))
{
obj = JObject.Load(jsonReader);
}
var builder = ArrayBuilder<string>.GetInstance();
var targets = (JObject)GetPropertyValue(obj, "targets");
foreach (var target in targets)
{
if (target.Key == ProjectLockJsonFramework)
{
foreach (var package in (JObject)target.Value)
{
var packageRoot = PathUtilities.CombineAbsoluteAndRelativePaths(packagesDirectory, package.Key);
var runtime = (JObject)GetPropertyValue((JObject)package.Value, "runtime");
if (runtime == null)
{
continue;
}
foreach (var item in runtime)
{
var path = PathUtilities.CombinePossiblyRelativeAndRelativePaths(packageRoot, item.Key);
builder.Add(path);
}
}
break;
}
}
return builder.ToImmutableAndFree();
}
private static JToken GetPropertyValue(JObject obj, string propertyName)
{
JToken value;
obj.TryGetValue(propertyName, out value);
return value;
}
private void NuGetRestore(string projectJsonPath)
{
// Load nuget.exe from same directory as current assembly.
var nugetExePath = PathUtilities.CombineAbsoluteAndRelativePaths(
PathUtilities.GetDirectoryName(
CorLightup.Desktop.GetAssemblyLocation(typeof(NuGetPackageResolverImpl).GetTypeInfo().Assembly)),
"nuget.exe");
var startInfo = new ProcessStartInfo()
{
FileName = nugetExePath,
Arguments = $"restore \"{projectJsonPath}\" -PackagesDirectory \"{_packagesDirectory}\"",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
};
_restore(startInfo);
}
private static void NuGetRestore(ProcessStartInfo startInfo)
{
var process = Process.Start(startInfo);
string line;
var reader = process.StandardOutput;
while ((line = reader.ReadLine()) != null)
{
// Should echo output to InteractiveWindow.
}
reader = process.StandardError;
while ((line = reader.ReadLine()) != null)
{
// Should echo errors to InteractiveWindow.
}
process.WaitForExit();
}
}
}
......@@ -110,6 +110,7 @@
<InternalsVisibleTo Include="Microsoft.VisualStudio.VisualBasic.Repl" />
<!-- The rest are for test purposes only. -->
<InternalsVisibleToTest Include="Roslyn.Hosting.Diagnostics" />
<InternalsVisibleToTest Include="Roslyn.InteractiveHost.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.InteractiveWindow.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.CSharp.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.UnitTests" />
......@@ -130,6 +131,7 @@
<Compile Include="Extensibility\Interactive\InteractiveEvaluator.cs" />
<Compile Include="Extensibility\Interactive\InteractiveMetadataReferenceResolver.cs" />
<Compile Include="Extensibility\Interactive\CSharpVBInteractiveCommandContentTypes.cs" />
<Compile Include="Extensibility\Interactive\NuGetPackageResolverImpl.cs" />
<Compile Include="Implementation\Completion\InteractiveCommandCompletionService.cs" />
<Compile Include="Implementation\Completion\Presentation\CompletionPresenter.cs" />
<Compile Include="Implementation\Interactive\InertClassifierProvider.cs" />
......
{
"dependencies": {
"Microsoft.Composition": "1.0.27",
"Newtonsoft.Json": "6.0.4",
"System.Collections": "4.0.10",
"System.Diagnostics.Debug": "4.0.10",
"System.Globalization": "4.0.0",
......
......@@ -45,6 +45,10 @@
<Project>{76C6F005-C89D-4348-BB4A-391898DBEB52}</Project>
<Name>TestUtilities.Desktop</Name>
</ProjectReference>
<ProjectReference Include="..\..\Workspaces\Core\Desktop\Workspaces.Desktop.csproj">
<Project>{2e87fa96-50bb-4607-8676-46521599f998}</Project>
<Name>Workspaces.Desktop</Name>
</ProjectReference>
<ProjectReference Include="..\..\Workspaces\Core\Portable\Workspaces.csproj">
<Project>{5F8D2414-064A-4B3A-9B42-8E2A04246BE5}</Project>
<Name>Workspaces</Name>
......@@ -53,6 +57,10 @@
<Project>{B0CE9307-FFDB-4838-A5EC-CE1F7CDC4AC2}</Project>
<Name>CSharpEditorFeatures</Name>
</ProjectReference>
<ProjectReference Include="..\EditorFeatures\Core\InteractiveEditorFeatures.csproj">
<Project>{92412d1a-0f23-45b5-b196-58839c524917}</Project>
<Name>InteractiveEditorFeatures</Name>
</ProjectReference>
<ProjectReference Include="..\EditorFeatures\CSharp\CSharpInteractiveEditorFeatures.csproj">
<Project>{FE2CBEA6-D121-4FAA-AA8B-FC9900BF8C83}</Project>
<Name>CSharpInteractiveEditorFeatures</Name>
......@@ -107,6 +115,7 @@
<Compile Include="AbstractInteractiveHostTests.cs" />
<Compile Include="CommandLineTests.cs" />
<Compile Include="InteractiveHostTests.cs" />
<Compile Include="NuGetPackageResolverTests.cs" />
<Compile Include="StressTests.cs" />
<Compile Include="SynchronizedTextWriter.cs" />
</ItemGroup>
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.Editor.Interactive;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using System.Collections.Immutable;
using System.IO;
using System.Text;
using Xunit;
namespace Microsoft.CodeAnalysis.UnitTests.Interactive
{
public class NuGetPackageResolverTests : TestBase
{
[ConditionalFact(typeof(WindowsOnly))]
public void ResolveReference()
{
var expectedProjectJson =
@"{
""dependencies"": {
""A.B.C"": ""1.2""
},
""frameworks"": {
""net46"": {}
}
}";
var actualProjectLockJson =
@"{
""locked"": false,
""version"": 1,
""targets"": {
"".NETFramework,Version=v4.5"": { },
"".NETFramework,Version=v4.6"": {
""System.Collections/4.0.10"": {
""dependencies"": {
""System.Runtime"": """"
},
""compile"": {
""ref/dotnet/System.Runtime.dll"": {}
},
""runtime"": {
""ref/dotnet/System.Collections.dll"": {}
}
},
""System.Diagnostics.Debug/4.0.10"": {
""dependencies"": {
""System.Runtime"": """"
},
},
""System.IO/4.0.10"": {
""dependencies"": {},
""runtime"": {
""ref/dotnet/System.Runtime.dll"": {},
""ref/dotnet/System.IO.dll"": {}
}
}
}
}
}";
using (var directory = new DisposableDirectory(Temp))
{
var packagesDirectory = directory.Path;
var resolver = new NuGetPackageResolverImpl(
packagesDirectory,
startInfo =>
{
var arguments = startInfo.Arguments.Split('"');
Assert.Equal(5, arguments.Length);
Assert.Equal("restore ", arguments[0]);
Assert.Equal("project.json", PathUtilities.GetFileName(arguments[1]));
Assert.Equal(" -PackagesDirectory ", arguments[2]);
Assert.Equal(packagesDirectory, arguments[3]);
Assert.Equal("", arguments[4]);
var projectJsonPath = arguments[1];
var actualProjectJson = File.ReadAllText(projectJsonPath);
Assert.Equal(expectedProjectJson, actualProjectJson);
var projectLockJsonPath = PathUtilities.CombineAbsoluteAndRelativePaths(PathUtilities.GetDirectoryName(projectJsonPath), "project.lock.json");
using (var writer = new StreamWriter(projectLockJsonPath))
{
writer.Write(actualProjectLockJson);
}
});
var actualPaths = resolver.ResolveNuGetPackage("A.B.C/1.2");
AssertEx.SetEqual(actualPaths,
PathUtilities.CombineAbsoluteAndRelativePaths(packagesDirectory, PathUtilities.CombinePossiblyRelativeAndRelativePaths("System.Collections/4.0.10", "ref/dotnet/System.Collections.dll")),
PathUtilities.CombineAbsoluteAndRelativePaths(packagesDirectory, PathUtilities.CombinePossiblyRelativeAndRelativePaths("System.IO/4.0.10", "ref/dotnet/System.Runtime.dll")),
PathUtilities.CombineAbsoluteAndRelativePaths(packagesDirectory, PathUtilities.CombinePossiblyRelativeAndRelativePaths("System.IO/4.0.10", "ref/dotnet/System.IO.dll")));
}
}
[ConditionalFact(typeof(WindowsOnly))]
public void ParsePackageNameAndVersion()
{
ParseInvalidPackageReference("A");
ParseInvalidPackageReference("A.B");
ParseInvalidPackageReference("A/");
ParseInvalidPackageReference("A//1.0");
ParseInvalidPackageReference("/1.0.0");
ParseInvalidPackageReference("A/B/2.0.0");
ParseValidPackageReference("A/1", "A", "1");
ParseValidPackageReference("A.B/1.0.0", "A.B", "1.0.0");
ParseValidPackageReference("A/B.C", "A", "B.C");
ParseValidPackageReference(" /1", " ", "1");
ParseValidPackageReference("A\t/\n1.0\r ", "A\t", "\n1.0\r ");
}
private static void ParseValidPackageReference(string reference, string expectedName, string expectedVersion)
{
string name;
string version;
Assert.True(NuGetPackageResolverImpl.ParsePackageReference(reference, out name, out version));
Assert.Equal(expectedName, name);
Assert.Equal(expectedVersion, version);
}
private static void ParseInvalidPackageReference(string reference)
{
string name;
string version;
Assert.False(NuGetPackageResolverImpl.ParsePackageReference(reference, out name, out version));
Assert.Null(name);
Assert.Null(version);
}
[ConditionalFact(typeof(WindowsOnly))]
public void WriteProjectJson()
{
WriteProjectJsonPackageReference("A.B", "4.0.1",
@"{
""dependencies"": {
""A.B"": ""4.0.1""
},
""frameworks"": {
""net46"": {}
}
}");
WriteProjectJsonPackageReference("\n\t", "\"'",
@"{
""dependencies"": {
""\n\t"": ""\""'""
},
""frameworks"": {
""net46"": {}
}
}");
}
private static void WriteProjectJsonPackageReference(string packageName, string packageVersion, string expectedJson)
{
var builder = new StringBuilder();
using (var writer = new StringWriter(builder))
{
NuGetPackageResolverImpl.WriteProjectJson(writer, packageName, packageVersion);
}
var actualJson = builder.ToString();
Assert.Equal(expectedJson, actualJson);
}
}
}
......@@ -50,11 +50,8 @@ public override string ResolveReference(string reference, string baseFilePath)
if (_packageResolver != null)
{
string path = _packageResolver.ResolveNuGetPackage(reference);
if (path != null && PortableShim.File.Exists(path))
{
return path;
}
// TODO: Call _packageResolver.ResolveNuGetPackage when
// this method supports returning a collection of results.
}
if (_gacFileResolver != null)
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Roslyn.Utilities;
using System.Collections.Immutable;
namespace Microsoft.CodeAnalysis.Scripting
{
internal sealed class NuGetPackageResolver
internal abstract class NuGetPackageResolver
{
internal static readonly NuGetPackageResolver Instance = new NuGetPackageResolver();
private NuGetPackageResolver()
{
}
internal string ResolveNuGetPackage(string reference)
{
if (PathUtilities.IsFilePath(reference))
{
return null;
}
var assemblyName = GetPackageAssemblyName(reference);
if (assemblyName == null)
{
return null;
}
// Expecting {package}{version}\lib\{arch}\{package}.dll.
var resolvedPath = PathUtilities.CombineAbsoluteAndRelativePaths(reference, "lib");
if (!PortableShim.Directory.Exists(resolvedPath))
{
return null;
}
// We're not validating the architecture currently
// so fail if there's not exactly one architecture.
resolvedPath = PortableShim.Directory.EnumerateDirectories(resolvedPath, "*", PortableShim.SearchOption.TopDirectoryOnly).SingleOrDefault();
if (resolvedPath == null)
{
return null;
}
return PathUtilities.CombineAbsoluteAndRelativePaths(resolvedPath, assemblyName);
}
private static string GetPackageAssemblyName(string reference)
{
// Assembly name is <id/> in .nuspec file.
// For now, simply strip off any version #, etc.
var name = PathUtilities.GetFileName(reference);
int offset = 0;
while ((offset = name.IndexOf('.', offset)) >= 0)
{
if ((offset < name.Length - 1) && char.IsDigit(name[offset + 1]))
{
return name.Substring(0, offset) + ".dll";
}
offset += 1;
}
return null;
}
internal abstract ImmutableArray<string> ResolveNuGetPackage(string reference);
}
}
{
"dependencies": {
"Microsoft.NETCore.Platforms": "1.0.0",
"Newtonsoft.Json": "6.0.4",
"System.Collections": "4.0.10",
"System.Collections.Immutable": "1.1.36",
"System.Console": "4.0.0-beta-23123",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册