Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for referencing NuGet packages in C# scripts #813

Merged
merged 22 commits into from
Jun 15, 2017
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
02c7124
Added support for referencing NuGet packages in C# scripts
seesharper Apr 4, 2017
ad90c6f
Merge branch 'dev' into dev
seesharper Apr 4, 2017
f1d6dd7
Fixed coding style issues
seesharper Apr 4, 2017
6252c0d
Added ScriptProjectProvider
seesharper Apr 21, 2017
c3bbfcc
Merge branch 'dev' of github.com:OmniSharp/omnisharp-roslyn into dev
seesharper Apr 21, 2017
0155e96
Fixed reference to DotNet.Script.NuGetMetadataResolver
seesharper Apr 24, 2017
63db355
Simplified project system
seesharper Apr 24, 2017
d58e538
Merge remote-tracking branch 'upstream/dev' into dev
seesharper May 8, 2017
a8683af
Fixed NuGet.Config (local references)
seesharper May 8, 2017
0e902d1
Fallback to default behavior if Nuget references failes
seesharper May 8, 2017
4607fbd
Merge branch 'dev' into dev
seesharper May 16, 2017
05756ec
Merge branch 'dev' into dev
seesharper May 16, 2017
5499e1e
Merge remote-tracking branch 'upstream/dev' into dev
seesharper Jun 2, 2017
1511099
Merge branch 'dev' of github.com:seesharper/omnisharp-roslyn into dev
seesharper Jun 2, 2017
d8eef4e
Added feature toggle default 'enableScriptNugetReferences=false'
seesharper Jun 3, 2017
a7f7daf
Fixed review change requests
seesharper Jun 3, 2017
7e52b3c
Update to Dotnet.Script.NuGetMetadataResolver 2.0.3
seesharper Jun 7, 2017
8316f1c
Allow IConfiguration to be optionally passed to ScriptHelper
seesharper Jun 7, 2017
a603bf5
Fixed usings and white removed white space
seesharper Jun 8, 2017
c9e9de7
Merge remote-tracking branch 'upstream/dev' into dev
seesharper Jun 8, 2017
e5a341d
Merge remote-tracking branch 'upstream/dev' into dev
seesharper Jun 12, 2017
ac8974a
Trigger
seesharper Jun 13, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions NuGet.Config
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
<add key="dotnet-core" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
<add key="cli-deps" value="https://dotnet.myget.org/F/cli-deps/api/v3/index.json" />
</packageSources>
</configuration>
<packageSources>
<clear />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
<add key="dotnet-core" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
<add key="cli-deps" value="https://dotnet.myget.org/F/cli-deps/api/v3/index.json" />
</packageSources>
</configuration>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This contains just unnecessary whitespace changes that you probably didn't mean to include.

18 changes: 11 additions & 7 deletions src/OmniSharp.Script/CachingScriptMetadataResolver.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Scripting;

namespace OmniSharp.Script
{
public class CachingScriptMetadataResolver : MetadataReferenceResolver
{
private readonly MetadataReferenceResolver _defaultReferenceResolver;
private static Dictionary<string, ImmutableArray<PortableExecutableReference>> DirectReferenceCache = new Dictionary<string, ImmutableArray<PortableExecutableReference>>();
private static Dictionary<string, PortableExecutableReference> MissingReferenceCache = new Dictionary<string, PortableExecutableReference>();
private static MetadataReferenceResolver _defaultRuntimeResolver = ScriptMetadataResolver.Default;

public CachingScriptMetadataResolver(MetadataReferenceResolver defaultReferenceResolver)
{
_defaultReferenceResolver = defaultReferenceResolver;
}

public override bool Equals(object other)
{
return _defaultRuntimeResolver.Equals(other);
return _defaultReferenceResolver.Equals(other);
}

public override int GetHashCode()
{
return _defaultRuntimeResolver.GetHashCode();
return _defaultReferenceResolver.GetHashCode();
}

public override bool ResolveMissingAssemblies => _defaultRuntimeResolver.ResolveMissingAssemblies;
public override bool ResolveMissingAssemblies => _defaultReferenceResolver.ResolveMissingAssemblies;

public override PortableExecutableReference ResolveMissingAssembly(MetadataReference definition, AssemblyIdentity referenceIdentity)
{
Expand All @@ -30,7 +34,7 @@ public override PortableExecutableReference ResolveMissingAssembly(MetadataRefer
return MissingReferenceCache[referenceIdentity.Name];
}

var result = _defaultRuntimeResolver.ResolveMissingAssembly(definition, referenceIdentity);
var result = _defaultReferenceResolver.ResolveMissingAssembly(definition, referenceIdentity);
if (result != null)
{
MissingReferenceCache[referenceIdentity.Name] = result;
Expand All @@ -47,7 +51,7 @@ public override ImmutableArray<PortableExecutableReference> ResolveReference(str
return DirectReferenceCache[key];
}

var result = _defaultRuntimeResolver.ResolveReference(reference, baseFilePath, properties);
var result = _defaultReferenceResolver.ResolveReference(reference, baseFilePath, properties);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not visible what happens here for nuget packages, can you explain a little? do they get installed? what if there is no network? what if there is no nuget in the user's path? what's the lock file?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any other feedback to @filipw's question above?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seesharper: did this ever get answered?

if (result.Length > 0)
{
DirectReferenceCache[key] = result;
Expand Down
1 change: 1 addition & 0 deletions src/OmniSharp.Script/OmniSharp.Script.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Dotnet.Script.NuGetMetadataResolver" Version="2.0.3" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="2.1.0" />
<PackageReference Include="Microsoft.DotNet.ProjectModel" Version="1.0.0-rc3-1-003177" />
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
Expand Down
63 changes: 48 additions & 15 deletions src/OmniSharp.Script/ScriptHelper.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Dotnet.Script.NuGetMetadataResolver;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.Scripting.Hosting;
using Microsoft.Extensions.Configuration;

namespace OmniSharp.Script
{
public static class ScriptHelper
public class ScriptHelper
{
private readonly IConfiguration _configuration;

// aligned with CSI.exe
// https://github.com/dotnet/roslyn/blob/version-2.0.0-rc3/src/Interactive/csi/csi.rsp
internal static readonly IEnumerable<string> DefaultNamespaces = new[]
Expand All @@ -28,46 +32,75 @@ public static class ScriptHelper

private static readonly CSharpParseOptions ParseOptions = new CSharpParseOptions(LanguageVersion.Default, DocumentationMode.Parse, SourceCodeKind.Script);

private static readonly Lazy<CSharpCompilationOptions> CompilationOptions = new Lazy<CSharpCompilationOptions>(() =>
private readonly Lazy<CSharpCompilationOptions> _compilationOptions;

public ScriptHelper(IConfiguration configuration = null)
{
this._configuration = configuration;
_compilationOptions = new Lazy<CSharpCompilationOptions>(CreateCompilationOptions);
}

private CSharpCompilationOptions CreateCompilationOptions()
{
var compilationOptions = new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
usings: DefaultNamespaces,
allowUnsafe: true,
metadataReferenceResolver: new CachingScriptMetadataResolver(),
metadataReferenceResolver:
CreateMetadataReferenceResolver(),
sourceReferenceResolver: ScriptSourceResolver.Default,
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default).
WithSpecificDiagnosticOptions(new Dictionary<string, ReportDiagnostic>
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default).WithSpecificDiagnosticOptions(
new Dictionary<string, ReportDiagnostic>
{
// ensure that specific warnings about assembly references are always suppressed
// https://github.com/dotnet/roslyn/issues/5501
{ "CS1701", ReportDiagnostic.Suppress },
{ "CS1702", ReportDiagnostic.Suppress },
{ "CS1705", ReportDiagnostic.Suppress }
});
{"CS1701", ReportDiagnostic.Suppress},
{"CS1702", ReportDiagnostic.Suppress},
{"CS1705", ReportDiagnostic.Suppress}
});

var topLevelBinderFlagsProperty = typeof(CSharpCompilationOptions).GetProperty("TopLevelBinderFlags", BindingFlags.Instance | BindingFlags.NonPublic);
var binderFlagsType = typeof(CSharpCompilationOptions).GetTypeInfo().Assembly.GetType("Microsoft.CodeAnalysis.CSharp.BinderFlags");
var topLevelBinderFlagsProperty =
typeof(CSharpCompilationOptions).GetProperty("TopLevelBinderFlags",
BindingFlags.Instance | BindingFlags.NonPublic);
var binderFlagsType = typeof(CSharpCompilationOptions).GetTypeInfo().Assembly
.GetType("Microsoft.CodeAnalysis.CSharp.BinderFlags");

var ignoreCorLibraryDuplicatedTypesMember = binderFlagsType?.GetField("IgnoreCorLibraryDuplicatedTypes", BindingFlags.Static | BindingFlags.Public);
var ignoreCorLibraryDuplicatedTypesMember =
binderFlagsType?.GetField("IgnoreCorLibraryDuplicatedTypes", BindingFlags.Static | BindingFlags.Public);
var ignoreCorLibraryDuplicatedTypesValue = ignoreCorLibraryDuplicatedTypesMember?.GetValue(null);
if (ignoreCorLibraryDuplicatedTypesValue != null)
{
topLevelBinderFlagsProperty?.SetValue(compilationOptions, ignoreCorLibraryDuplicatedTypesValue);
}

return compilationOptions;
});
}

public static ProjectInfo CreateProject(string csxFileName, IEnumerable<MetadataReference> references, IEnumerable<string> namespaces = null)
private CachingScriptMetadataResolver CreateMetadataReferenceResolver()
{
bool enableScriptNuGetReferences = false;

if (_configuration != null)
{
if (!bool.TryParse(_configuration["enableScriptNuGetReferences"], out enableScriptNuGetReferences))
{
enableScriptNuGetReferences = false;
}
}

return enableScriptNuGetReferences ? new CachingScriptMetadataResolver(new NuGetMetadataReferenceResolver(ScriptMetadataResolver.Default))
: new CachingScriptMetadataResolver(ScriptMetadataResolver.Default);
}

public ProjectInfo CreateProject(string csxFileName, IEnumerable<MetadataReference> references, IEnumerable<string> namespaces = null)
{
var project = ProjectInfo.Create(
id: ProjectId.CreateNewId(),
version: VersionStamp.Create(),
name: csxFileName,
assemblyName: $"{csxFileName}.dll",
language: LanguageNames.CSharp,
compilationOptions: namespaces == null ? CompilationOptions.Value : CompilationOptions.Value.WithUsings(namespaces),
compilationOptions: namespaces == null ? _compilationOptions.Value : _compilationOptions.Value.WithUsings(namespaces),
metadataReferences: references,
parseOptions: ParseOptions,
isSubmission: true,
Expand Down
53 changes: 46 additions & 7 deletions src/OmniSharp.Script/ScriptProjectSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Dotnet.Script.NuGetMetadataResolver;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.DotNet.ProjectModel;
Expand All @@ -15,7 +16,7 @@
using OmniSharp.Services;

namespace OmniSharp.Script
{
{
[Export(typeof(IProjectSystem)), Shared]
public class ScriptProjectSystem : IProjectSystem
{
Expand All @@ -29,6 +30,8 @@ public class ScriptProjectSystem : IProjectSystem
private readonly OmniSharpWorkspace _workspace;
private readonly IOmniSharpEnvironment _env;
private readonly ILogger _logger;
private readonly IScriptProjectProvider _scriptProjectProvider;
private static readonly Lazy<string> _targetFrameWork = new Lazy<string>(ResolveTargetFramework);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to _targetFramework


[ImportingConstructor]
public ScriptProjectSystem(OmniSharpWorkspace workspace, IOmniSharpEnvironment env, ILoggerFactory loggerFactory, MetadataFileReferenceCache metadataFileReferenceCache)
Expand All @@ -38,6 +41,7 @@ public ScriptProjectSystem(OmniSharpWorkspace workspace, IOmniSharpEnvironment e
_env = env;
_logger = loggerFactory.CreateLogger<ScriptProjectSystem>();
_projects = new Dictionary<string, ProjectInfo>();
_scriptProjectProvider = ScriptProjectProvider.Create(loggerFactory);
}

public string Key => "Script";
Expand All @@ -46,6 +50,8 @@ public ScriptProjectSystem(OmniSharpWorkspace workspace, IOmniSharpEnvironment e

public void Initalize(IConfiguration configuration)
{
var scriptHelper = new ScriptHelper(configuration);

_logger.LogInformation($"Detecting CSX files in '{_env.TargetDirectory}'.");

// Nothing to do if there are no CSX files
Expand All @@ -60,15 +66,25 @@ public void Initalize(IConfiguration configuration)

// explicitly inherit scripting library references to all global script object (InteractiveScriptGlobals) to be recognized
var inheritedCompileLibraries = DependencyContext.Default.CompileLibraries.Where(x =>
x.Name.ToLowerInvariant().StartsWith("microsoft.codeanalysis")).ToList();
x.Name.ToLowerInvariant().StartsWith("microsoft.codeanalysis")).ToList();

// explicitly include System.ValueTuple
inheritedCompileLibraries.AddRange(DependencyContext.Default.CompileLibraries.Where(x =>
x.Name.ToLowerInvariant().StartsWith("system.valuetuple")));
x.Name.ToLowerInvariant().StartsWith("system.valuetuple")));

var runtimeContexts = File.Exists(Path.Combine(_env.TargetDirectory, "project.json")) ? ProjectContext.CreateContextForEachTarget(_env.TargetDirectory) : null;

var commonReferences = new HashSet<MetadataReference>();

if (!bool.TryParse(configuration["enableScriptNuGetReferences"], out var enableScriptNuGetReferences))
{
enableScriptNuGetReferences = false;
}

if (enableScriptNuGetReferences && (runtimeContexts == null || runtimeContexts.Any() == false))
{
runtimeContexts = TryCreateRuntimeContextsFromScriptFiles();
}

// if we have no context, then we also have no dependencies
// we can assume desktop framework
Expand All @@ -81,7 +97,7 @@ public void Initalize(IConfiguration configuration)
AddMetadataReference(commonReferences, typeof(Enumerable).GetTypeInfo().Assembly.Location);

inheritedCompileLibraries.AddRange(DependencyContext.Default.CompileLibraries.Where(x =>
x.Name.ToLowerInvariant().StartsWith("system.runtime")));
x.Name.ToLowerInvariant().StartsWith("system.runtime")));
}
// otherwise we will grab dependencies for the script from the runtime context
else
Expand All @@ -106,7 +122,7 @@ public void Initalize(IConfiguration configuration)
{

inheritedCompileLibraries.AddRange(DependencyContext.Default.CompileLibraries.Where(x =>
x.Name.ToLowerInvariant().StartsWith("system.runtime")));
x.Name.ToLowerInvariant().StartsWith("system.runtime")));
}
}

Expand All @@ -124,7 +140,7 @@ public void Initalize(IConfiguration configuration)
try
{
var csxFileName = Path.GetFileName(csxPath);
var project = ScriptHelper.CreateProject(csxFileName, commonReferences);
var project = scriptHelper.CreateProject(csxFileName, commonReferences);

// add CSX project to workspace
_workspace.AddProject(project);
Expand All @@ -139,6 +155,21 @@ public void Initalize(IConfiguration configuration)
}
}

private IEnumerable<ProjectContext> TryCreateRuntimeContextsFromScriptFiles()
{
_logger.LogInformation($"Attempting to create runtime context from script files. Default target framework {_targetFrameWork.Value}");
try
{
var scriptProjectInfo = _scriptProjectProvider.CreateProject(_env.TargetDirectory, _targetFrameWork.Value);
return ProjectContext.CreateContextForEachTarget(Path.GetDirectoryName(scriptProjectInfo.PathToProjectJson));
}
catch (Exception exception)
{
_logger.LogError(exception, "Unable to create runtime context from script files.");
}
return null;
}

private void AddMetadataReference(ISet<MetadataReference> referenceCollection, string fileReference)
{
if (!File.Exists(fileReference))
Expand Down Expand Up @@ -201,5 +232,13 @@ Task<object> IProjectSystem.GetWorkspaceModelAsync(WorkspaceInformationRequest r
}
return Task.FromResult<object>(new ScriptContextModelCollection(scriptContextModels));
}

private static string ResolveTargetFramework()
{
return Assembly.GetEntryAssembly().GetCustomAttributes()
.OfType<System.Runtime.Versioning.TargetFrameworkAttribute>()
.Select(x => x.FrameworkName)
.FirstOrDefault();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that, in VS Code, this will always be net46.

}
}
}
3 changes: 3 additions & 0 deletions src/OmniSharp/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@
"useTabs": false,
"tabSize": 4,
"indentationSize": 4
},
"script": {
"enableScriptNuGetReferences" : false
}
}
3 changes: 2 additions & 1 deletion tests/TestUtility/TestHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public static OmniSharpWorkspace CreateCsxWorkspace(TestFile testFile)
public static void AddCsxProjectToWorkspace(OmniSharpWorkspace workspace, TestFile testFile)
{
var references = GetReferences();
var project = ScriptHelper.CreateProject(testFile.FileName, references.Union(new[] { MetadataReference.CreateFromFile(typeof(CommandLineScriptGlobals).GetTypeInfo().Assembly.Location) }), Enumerable.Empty<string>());
var scriptHelper = new ScriptHelper();
var project = scriptHelper.CreateProject(testFile.FileName, references.Union(new[] { MetadataReference.CreateFromFile(typeof(CommandLineScriptGlobals).GetTypeInfo().Assembly.Location) }), Enumerable.Empty<string>());
workspace.AddProject(project);

var documentInfo = DocumentInfo.Create(
Expand Down