提交 896318fd 编写于 作者: T Tomáš Matoušek

Merge pull request #5636 from tmat/ShadowCopyCulture

Consider the current culture when copying xml doc comments in shadow copy provider.
......@@ -99,6 +99,10 @@ private static class _AppDomain
internal static readonly MethodInfo add_AssemblyResolve = Type
.GetTypeInfo()
.GetDeclaredMethod("add_AssemblyResolve", ResolveEventHandlerType);
internal static readonly MethodInfo remove_AssemblyResolve = Type
.GetTypeInfo()
.GetDeclaredMethod("remove_AssemblyResolve", ResolveEventHandlerType);
}
internal static Assembly LoadAssembly(byte[] peImage)
......@@ -165,7 +169,7 @@ public object GetHandler()
}
}
internal static void AddAssemblyResolveHandler(Func<string, Assembly, Assembly> handler)
internal static object GetCurrentAppDomain()
{
if (_AppDomain.get_CurrentDomain == null ||
_AppDomain.add_AssemblyResolve == null ||
......@@ -175,10 +179,30 @@ internal static void AddAssemblyResolveHandler(Func<string, Assembly, Assembly>
throw new PlatformNotSupportedException();
}
object currentAppDomain = _AppDomain.get_CurrentDomain.Invoke(null, SpecializedCollections.EmptyArray<object>());
return _AppDomain.get_CurrentDomain.Invoke(null, SpecializedCollections.EmptyArray<object>());
}
internal static void GetOrRemoveAssemblyResolveHandler(Func<string, Assembly, Assembly> handler, MethodInfo handlerOperation)
{
if (_AppDomain.add_AssemblyResolve == null)
{
throw new PlatformNotSupportedException();
}
object currentAppDomain = GetCurrentAppDomain();
object resolveEventHandler = new AssemblyResolveWrapper(handler).GetHandler();
_AppDomain.add_AssemblyResolve.Invoke(currentAppDomain, new[] { resolveEventHandler });
handlerOperation.Invoke(currentAppDomain, new[] { resolveEventHandler });
}
internal static void AddAssemblyResolveHandler(Func<string, Assembly, Assembly> handler)
{
GetOrRemoveAssemblyResolveHandler(handler, _AppDomain.add_AssemblyResolve);
}
internal static void RemoveAssemblyResolveHandler(Func<string, Assembly, Assembly> handler)
{
GetOrRemoveAssemblyResolveHandler(handler, _AppDomain.remove_AssemblyResolve);
}
}
}
......
......@@ -510,6 +510,20 @@ internal static AssemblyIdentity FromAssemblyDefinition(AssemblyName name)
isRetargetable: (name.Flags & AssemblyNameFlags.Retargetable) != 0,
contentType: name.ContentType);
}
internal static AssemblyIdentity FromAssemblyReference(AssemblyName name)
{
// AssemblyRef either has PKT or no key:
return new AssemblyIdentity(
name.Name,
name.Version,
name.CultureName,
ImmutableArray.Create(name.GetPublicKeyToken()),
hasPublicKey: false,
isRetargetable: (name.Flags & AssemblyNameFlags.Retargetable) != 0,
contentType: name.ContentType);
}
#endregion
}
}
......@@ -461,7 +461,9 @@ private async Task<ExecutionResult> ResetAsyncWorker(bool initialize = true)
{
try
{
var options = InteractiveHostOptions.Default.WithInitializationFile(initialize ? _responseFilePath : null);
var options = new InteractiveHostOptions(
initializationFile: initialize ? _responseFilePath : null,
culture: CultureInfo.CurrentUICulture);
var result = await _interactiveHost.ResetAsync(options).ConfigureAwait(false);
......
// 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;
using System.Collections.Immutable;
using System.Reflection;
using Microsoft.CodeAnalysis.Scripting;
namespace Microsoft.CodeAnalysis.Editor.Interactive
{
#if TODO
internal sealed class InteractiveMetadataReferenceResolver : MetadataReferencePathResolver
{
private readonly GacFileResolver gacResolver;
internal InteractiveMetadataReferenceResolver(ImmutableArray<string> searchPaths, string baseDirectory)
{
this.gacResolver = new GacFileResolver(
searchPaths,
baseDirectory: baseDirectory,
architectures: GacFileResolver.Default.Architectures, // TODO (tomat)
preferredCulture: System.Globalization.CultureInfo.CurrentCulture); // TODO (tomat)
}
public override string ResolveReference(string reference, string baseFilePath)
{
return gacResolver.ResolveReference(reference, baseFilePath);
}
public override bool Equals(object obj)
{
var other = obj as InteractiveMetadataReferenceResolver;
return other != null &&
this.gacResolver.Equals(other.gacResolver);
}
public override int GetHashCode()
{
return gacResolver.GetHashCode();
}
public ImmutableArray<string> SearchPaths
{
get { return gacResolver.SearchPaths; }
}
public string BaseDirectory
{
get { return gacResolver.BaseDirectory; }
}
}
#endif
}
......@@ -129,7 +129,6 @@
<Compile Include="Extensibility\Interactive\CSharpVBResetCommand.cs" />
<Compile Include="Extensibility\Interactive\InteractiveCommandHandler.cs" />
<Compile Include="Extensibility\Interactive\InteractiveEvaluator.cs" />
<Compile Include="Extensibility\Interactive\InteractiveMetadataReferenceResolver.cs" />
<Compile Include="Extensibility\Interactive\CSharpVBInteractiveCommandContentTypes.cs" />
<Compile Include="Implementation\Completion\InteractiveCommandCompletionService.cs" />
<Compile Include="Implementation\Completion\Presentation\CompletionPresenter.cs" />
......
// 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;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
......@@ -53,7 +54,7 @@ private async Task<InitializedRemoteService> TryStartAndInitializeProcessAsync(C
notification(this.Options.InitializationFile != null);
}
var remoteService = await TryStartProcessAsync(cancellationToken).ConfigureAwait(false);
var remoteService = await TryStartProcessAsync(Options.Culture, cancellationToken).ConfigureAwait(false);
if (remoteService == null)
{
return default(InitializedRemoteService);
......@@ -103,9 +104,9 @@ private async Task<InitializedRemoteService> TryStartAndInitializeProcessAsync(C
}
}
private Task<RemoteService> TryStartProcessAsync(CancellationToken cancellationToken)
private Task<RemoteService> TryStartProcessAsync(CultureInfo culture, CancellationToken cancellationToken)
{
return Task.Run(() => Host.TryStartProcess(cancellationToken));
return Task.Run(() => Host.TryStartProcess(culture, cancellationToken));
}
}
}
......
......@@ -40,8 +40,8 @@ internal sealed class Service : MarshalByRefObject, IDisposable
private static TaskScheduler s_UIThreadScheduler;
private readonly InteractiveAssemblyLoader _assemblyLoader;
private readonly MetadataShadowCopyProvider _metadataFileProvider;
private InteractiveAssemblyLoader _assemblyLoader;
private MetadataShadowCopyProvider _metadataFileProvider;
private ReplServiceProvider _replServiceProvider;
private readonly InteractiveHostObject _hostObject;
......@@ -107,13 +107,6 @@ internal EvaluationState WithOptions(ScriptOptions options)
public Service()
{
// TODO (tomat): we should share the copied files with the host
_metadataFileProvider = new MetadataShadowCopyProvider(
Path.Combine(Path.GetTempPath(), "InteractiveHostShadow"),
noShadowCopyDirectories: s_systemNoShadowCopyDirectories);
_assemblyLoader = new InteractiveAssemblyLoader(_metadataFileProvider);
_formattingOptions = new ObjectFormattingOptions(
memberFormat: MemberDisplayFormat.Inline,
quoteStrings: true,
......@@ -157,9 +150,23 @@ public override object InitializeLifetimeService()
return null;
}
public void Initialize(Type replServiceProviderType)
public void Initialize(Type replServiceProviderType, string cultureName)
{
Contract.ThrowIfNull(replServiceProviderType);
Debug.Assert(replServiceProviderType != null);
Debug.Assert(cultureName != null);
Debug.Assert(_metadataFileProvider == null);
Debug.Assert(_assemblyLoader == null);
Debug.Assert(_replServiceProvider == null);
// TODO (tomat): we should share the copied files with the host
_metadataFileProvider = new MetadataShadowCopyProvider(
Path.Combine(Path.GetTempPath(), "InteractiveHostShadow"),
noShadowCopyDirectories: s_systemNoShadowCopyDirectories,
documentationCommentsCulture: new CultureInfo(cultureName));
_assemblyLoader = new InteractiveAssemblyLoader(_metadataFileProvider);
_replServiceProvider = (ReplServiceProvider)Activator.CreateInstance(replServiceProviderType);
}
......@@ -174,7 +181,7 @@ private MetadataReferenceResolver CreateMetadataReferenceResolver(ImmutableArray
new RelativePathResolver(searchPaths, baseDirectory),
string.IsNullOrEmpty(packagesDirectory) ? null : new NuGetPackageResolverImpl(packagesDirectory),
GacFileResolver.IsAvailable ? new GacFileResolver(preferredCulture: CultureInfo.CurrentCulture) : null,
(path, properties) => _metadataFileProvider.GetReference(path, properties));
(path, properties) => new ShadowCopyReference(_metadataFileProvider, path, properties));
}
private SourceReferenceResolver CreateSourceReferenceResolver(ImmutableArray<string> searchPaths, string baseDirectory)
......
// 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.Diagnostics;
using Microsoft.CodeAnalysis.Scripting.Hosting;
namespace Microsoft.CodeAnalysis.Interactive
{
partial class InteractiveHost
{
/// <summary>
/// Specialize <see cref="PortableExecutableReference"/> with path being the original path of the copy.
/// Logically this reference represents that file, the fact that we load the image from a copy is an implementation detail.
/// </summary>
private sealed class ShadowCopyReference : PortableExecutableReference
{
private readonly MetadataShadowCopyProvider _provider;
public ShadowCopyReference(MetadataShadowCopyProvider provider, string originalPath, MetadataReferenceProperties properties)
: base(properties, originalPath)
{
Debug.Assert(originalPath != null);
Debug.Assert(provider != null);
_provider = provider;
}
protected override DocumentationProvider CreateDocumentationProvider()
{
// TODO (tomat): use file next to the dll (or shadow copy)
return DocumentationProvider.Default;
}
protected override Metadata GetMetadataImpl()
{
return _provider.GetMetadata(FilePath, Properties.Kind);
}
protected override PortableExecutableReference WithPropertiesImpl(MetadataReferenceProperties properties)
{
return new ShadowCopyReference(_provider, this.FilePath, properties);
}
}
}
}
......@@ -2,6 +2,7 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
......@@ -105,7 +106,7 @@ public override object InitializeLifetimeService()
return null;
}
private RemoteService TryStartProcess(CancellationToken cancellationToken)
private RemoteService TryStartProcess(CultureInfo culture, CancellationToken cancellationToken)
{
Process newProcess = null;
int newProcessId = -1;
......@@ -191,7 +192,7 @@ private RemoteService TryStartProcess(CancellationToken cancellationToken)
cancellationToken.ThrowIfCancellationRequested();
newService.Initialize(_replServiceProviderType);
newService.Initialize(_replServiceProviderType, culture.Name);
}
catch (RemotingException) when (!CheckAlive(newProcess))
{
......@@ -432,8 +433,10 @@ public async Task<RemoteExecutionResult> ResetAsync(InteractiveHostOptions optio
{
try
{
var options = optionsOpt ?? _lazyRemoteService?.Options ?? new InteractiveHostOptions(null, CultureInfo.CurrentUICulture);
// replace the existing service with a new one:
var newService = CreateRemoteService(optionsOpt ?? _lazyRemoteService?.Options ?? InteractiveHostOptions.Default, skipInitialization: false);
var newService = CreateRemoteService(options, skipInitialization: false);
LazyRemoteService oldService = Interlocked.Exchange(ref _lazyRemoteService, newService);
if (oldService != 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.Globalization;
namespace Microsoft.CodeAnalysis.Interactive
{
/// <summary>
/// Settings that affect InteractiveHost process and initialization.
/// </summary>
public sealed class InteractiveHostOptions
internal sealed class InteractiveHostOptions
{
public static readonly InteractiveHostOptions Default = new InteractiveHostOptions(null);
/// <summary>
/// Optional path to the .rsp file to process when initializing context of the process.
/// </summary>
public string InitializationFile { get; }
private InteractiveHostOptions(string initializationFile)
/// <summary>
/// Host culture used for localization of doc comments, errors.
/// </summary>
public CultureInfo Culture { get; }
public InteractiveHostOptions(
string initializationFile = null,
CultureInfo culture = null)
{
this.InitializationFile = initializationFile;
InitializationFile = initializationFile;
Culture = culture ?? CultureInfo.CurrentUICulture;
}
public InteractiveHostOptions WithInitializationFile(string initializationFile)
{
if (this.InitializationFile == initializationFile)
if (InitializationFile == initializationFile)
{
return this;
}
return new InteractiveHostOptions(initializationFile, Culture);
}
public InteractiveHostOptions WithCulture(CultureInfo culture)
{
if (Culture == culture)
{
return this;
}
return new InteractiveHostOptions(initializationFile);
return new InteractiveHostOptions(InitializationFile, culture);
}
}
}
......@@ -85,6 +85,7 @@
<Compile Include="Interactive\Core\InteractiveHost.cs" />
<Compile Include="Interactive\Core\InteractiveHost.InitializedRemoteService.cs" />
<Compile Include="Interactive\Core\InteractiveHost.Service.cs" />
<Compile Include="Interactive\Core\InteractiveHost.ShadowCopyReference.cs" />
<Compile Include="Interactive\Core\InteractiveLanguageNames.cs" />
<Compile Include="Interactive\Core\ReplServiceProvider.cs" />
<Compile Include="Interactive\Core\InteractiveHost.RemoteAsyncOperation.cs" />
......
......@@ -85,16 +85,6 @@
<Project>{2523D0E6-DF32-4A3E-8AE0-A19BFFAE2EF6}</Project>
<Name>BasicCodeAnalysis</Name>
</ProjectReference>
<ProjectReference Include="..\csi\csi.csproj">
<Project>{14118347-ED06-4608-9C45-18228273C712}</Project>
<Name>csi</Name>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
<ProjectReference Include="..\vbi\vbi.vbproj">
<Project>{6E62A0FF-D0DC-4109-9131-AB8E60CDFF7B}</Project>
<Name>vbi</Name>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
</PropertyGroup>
......
......@@ -3,6 +3,7 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
......@@ -40,7 +41,7 @@ public InteractiveHostTests()
RedirectOutput();
Host.ResetAsync(InteractiveHostOptions.Default).Wait();
Host.ResetAsync(new InteractiveHostOptions(initializationFile: null, culture: CultureInfo.InvariantCulture)).Wait();
var remoteService = Host.TryGetService();
Assert.NotNull(remoteService);
......@@ -127,7 +128,7 @@ public void RestartHost(string rspFile = null)
{
ClearOutput();
var initTask = Host.ResetAsync(InteractiveHostOptions.Default.WithInitializationFile(rspFile));
var initTask = Host.ResetAsync(new InteractiveHostOptions(initializationFile: rspFile, culture: CultureInfo.InvariantCulture));
initTask.Wait();
}
......@@ -524,7 +525,8 @@ public void AddReference_Path()
Assert.True(Execute("System.Diagnostics.Process.GetCurrentProcess().HasExited"));
}
[Fact(Skip = "530414")]
// Caused by submission not inheriting references.
[Fact(Skip = "101161")]
public void AddReference_ShadowCopy()
{
var dir = Temp.CreateDirectory();
......@@ -649,7 +651,7 @@ public void AddReference_AlreadyLoadedDependencies()
Assert.Equal("1", ReadOutputToEnd().Trim());
}
[Fact(Skip = "530414")]
[Fact(Skip = "101161")]
public void AddReference_LoadUpdatedReference()
{
var dir = Temp.CreateDirectory();
......@@ -659,10 +661,9 @@ public void AddReference_LoadUpdatedReference()
var file = dir.CreateFile("c.dll").WriteAllBytes(c1.EmitToArray());
// use:
Execute(@"
#r """ + file.Path + @"""
C foo() { return new C(); }
Execute($@"
#r ""{file.Path}""
C foo() => new C();
new C().X
");
......@@ -672,18 +673,21 @@ public void AddReference_LoadUpdatedReference()
file.WriteAllBytes(c2.EmitToArray());
// add the reference again:
Execute(@"
#r """ + file.Path + @"""
Execute($@"
#r ""{file.Path}""
new D().Y
");
// TODO: We should report an error that assembly named 'a' was already loaded with different content.
// In future we can let it load and improve error reporting around type conversions.
Assert.Equal("", ReadErrorOutputToEnd().Trim());
Assert.Equal(
@"1
2", ReadOutputToEnd().Trim());
}
[Fact(Skip = "987032")]
[Fact(Skip = "129388")]
public void AddReference_MultipleReferencesWithSameWeakIdentity()
{
var dir = Temp.CreateDirectory();
......@@ -699,13 +703,16 @@ public void AddReference_MultipleReferencesWithSameWeakIdentity()
var c2 = CreateCompilationWithMscorlib(source2, assemblyName: "C");
var file2 = dir2.CreateFile("c.dll").WriteAllBytes(c2.EmitToArray());
Execute(@"
#r """ + file1.Path + @"""
#r """ + file2.Path + @"""
Execute($@"
#r ""{file1.Path}""
#r ""{file2.Path}""
");
Execute("new C1()");
Execute("new C2()");
// TODO: We should report an error that assembly named 'c' was already loaded with different content.
// In future we can let it load and let the compiler report the error CS1704: "An assembly with the same simple name 'C' has already been imported".
Assert.Equal(
@"(2,1): error CS1704: An assembly with the same simple name 'C' has already been imported. Try removing one of the references (e.g. '" + file1.Path + @"') or sign them to enable side-by-side.
(1,5): error CS0246: The type or namespace name 'C1' could not be found (are you missing a using directive or an assembly reference?)
......@@ -714,6 +721,36 @@ public void AddReference_MultipleReferencesWithSameWeakIdentity()
Assert.Equal("", ReadOutputToEnd().Trim());
}
[Fact(Skip = "129388")]
public void AddReference_MultipleReferencesWeakVersioning()
{
var dir = Temp.CreateDirectory();
var dir1 = dir.CreateDirectory("1");
var dir2 = dir.CreateDirectory("2");
var source1 = @"[assembly: System.Reflection.AssemblyVersion(""1.0.0.0"")] public class C1 { }";
var c1 = CreateCompilationWithMscorlib(source1, assemblyName: "C");
var file1 = dir1.CreateFile("c.dll").WriteAllBytes(c1.EmitToArray());
var source2 = @"[assembly: System.Reflection.AssemblyVersion(""2.0.0.0"")] public class C2 { }";
var c2 = CreateCompilationWithMscorlib(source2, assemblyName: "C");
var file2 = dir2.CreateFile("c.dll").WriteAllBytes(c2.EmitToArray());
Execute($@"
#r ""{file1.Path}""
#r ""{file2.Path}""
");
Execute("new C1()");
Execute("new C2()");
// TODO: We should report an error that assembly named 'c' was already loaded with different content.
// In future we can let it load and improve error reporting around type conversions.
Assert.Equal("TODO: error", ReadErrorOutputToEnd().Trim());
Assert.Equal("", ReadOutputToEnd().Trim());
}
//// TODO (987032):
//// [Fact]
//// public void AsyncInitializeContextWithDotNETLibraries()
......@@ -804,7 +841,7 @@ public void ReferencePaths()
CompileLibrary(directory, assemblyName + ".dll", assemblyName, @"public class C { }");
var rspFile = Temp.CreateFile();
rspFile.WriteAllText("/rp:" + directory.Path);
var task = Host.ResetAsync(InteractiveHostOptions.Default.WithInitializationFile(rspFile.Path));
var task = Host.ResetAsync(new InteractiveHostOptions(initializationFile: rspFile.Path, culture: CultureInfo.InvariantCulture));
task.Wait();
Execute(
$@"#r ""{assemblyName}.dll""
......
......@@ -75,7 +75,7 @@ private void TestKillAfter(int milliseconds)
t.Start();
});
p.ResetAsync(InteractiveHostOptions.Default).Wait();
p.ResetAsync(new InteractiveHostOptions()).Wait();
for (int j = 0; j < 10; j++)
{
......
......@@ -6,6 +6,7 @@
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Scripting.CSharp;
using Microsoft.CodeAnalysis.Scripting.Test;
......@@ -19,7 +20,7 @@ namespace Microsoft.CodeAnalysis.Scripting.CSharpTest
public class InteractiveSessionTests : TestBase
{
[Fact]
public async void CompilationChain_GlobalImportsRebinding()
public async Task CompilationChain_GlobalImportsRebinding()
{
var options = ScriptOptions.Default.AddNamespaces("System.Diagnostics");
......@@ -36,7 +37,7 @@ public async void CompilationChain_GlobalImportsRebinding()
}
[Fact]
public async void CompilationChain_UsingRebinding_AddReference()
public async Task CompilationChain_UsingRebinding_AddReference()
{
var s0 = await CSharpScript.RunAsync("using System.Diagnostics;");
......@@ -48,7 +49,7 @@ public async void CompilationChain_UsingRebinding_AddReference()
}
[Fact]
public async void CompilationChain_UsingRebinding_Directive()
public async Task CompilationChain_UsingRebinding_Directive()
{
var s0 = await CSharpScript.RunAsync("using System.Diagnostics;");
......@@ -139,7 +140,7 @@ public void SearchPaths_RemoveDefault()
/// Look at base directory (or directory containing #r) before search paths.
/// </summary>
[Fact]
public async void SearchPaths_BaseDirectory()
public async Task SearchPaths_BaseDirectory()
{
var options = ScriptOptions.Default.
WithCustomMetadataResolution(new TestMetadataReferenceResolver(
......@@ -159,7 +160,7 @@ public async void SearchPaths_BaseDirectory()
}
[Fact]
public async void References1()
public async Task References1()
{
var options0 = ScriptOptions.Default.AddReferences(
typeof(Process).Assembly,
......@@ -195,7 +196,7 @@ public async void References1()
var options3 = options2.AddReferences(typeof(System.Windows.Forms.Form).Assembly.Location);
var s3 = await s2.ContinueWithAsync<System.Windows.Forms.Form>(@"
new System.Windows.Forms.Form();
new System.Windows.Forms.Form()
", options3);
Assert.NotNull(s3.ReturnValue);
......@@ -289,7 +290,7 @@ public void ReferenceToInvalidType()
public class C { public int x = 1; }
[Fact]
public async void HostObjectBinding_DuplicateReferences()
public async Task HostObjectBinding_DuplicateReferences()
{
var options = ScriptOptions.Default.
AddReferences(typeof(C).Assembly, typeof(C).Assembly);
......
// 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;
using System.Diagnostics;
using System.Reflection;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Scripting.Hosting
{
internal struct AssemblyAndLocation : IEquatable<AssemblyAndLocation>
{
public Assembly Assembly { get; }
public string Location { get; }
public bool GlobalAssemblyCache { get; }
internal AssemblyAndLocation(Assembly assembly, string location, bool fromGac)
{
Debug.Assert(assembly != null && location != null);
Assembly = assembly;
Location = location;
GlobalAssemblyCache = fromGac;
}
public bool IsDefault => Assembly == null;
public bool Equals(AssemblyAndLocation other) =>
Assembly == other.Assembly && Location == other.Location && GlobalAssemblyCache == other.GlobalAssemblyCache;
public override int GetHashCode() =>
Hash.Combine(Assembly, Hash.Combine(Location, Hash.Combine(GlobalAssemblyCache, 0)));
public override bool Equals(object obj) =>
obj is AssemblyAndLocation && Equals((AssemblyAndLocation)obj);
public override string ToString() =>
Assembly + " @ " + (GlobalAssemblyCache ? "<GAC>" : Location);
}
}
// 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;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Scripting.Hosting
{
internal abstract class AssemblyLoaderImpl : IDisposable
{
internal readonly InteractiveAssemblyLoader Loader;
protected AssemblyLoaderImpl(InteractiveAssemblyLoader loader)
{
Loader = loader;
}
public static AssemblyLoaderImpl Create(InteractiveAssemblyLoader loader)
{
if (CoreClrShim.AssemblyLoadContext.Type != null)
{
return CreateCoreImpl(loader);
}
else
{
return new DesktopAssemblyLoaderImpl(loader);
}
}
// NoInlining to avoid loading AssemblyLoadContext if not available.
[MethodImpl(MethodImplOptions.NoInlining)]
private static AssemblyLoaderImpl CreateCoreImpl(InteractiveAssemblyLoader loader)
{
return new CoreAssemblyLoaderImpl(loader);
}
public abstract Assembly LoadFromStream(Stream peStream, Stream pdbStream);
public abstract AssemblyAndLocation LoadFromPath(string path);
public abstract void Dispose();
}
}
// 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;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
namespace Microsoft.CodeAnalysis.Scripting.Hosting
{
internal sealed class CoreAssemblyLoaderImpl : AssemblyLoaderImpl
{
private readonly LoadContext _inMemoryAssemblyContext;
internal CoreAssemblyLoaderImpl(InteractiveAssemblyLoader loader)
: base(loader)
{
_inMemoryAssemblyContext = new LoadContext(this, null);
}
public override Assembly LoadFromStream(Stream peStream, Stream pdbStream)
{
return _inMemoryAssemblyContext.LoadFromStream(peStream, pdbStream);
}
public override AssemblyAndLocation LoadFromPath(string path)
{
var assembly = new LoadContext(this, Path.GetDirectoryName(path)).LoadFromAssemblyPath(path);
return new AssemblyAndLocation(assembly, path, fromGac: false);
}
public override void Dispose()
{
// nop
}
private sealed class LoadContext : AssemblyLoadContext
{
private readonly string _loadDirectoryOpt;
private readonly CoreAssemblyLoaderImpl _loader;
internal LoadContext(CoreAssemblyLoaderImpl loader, string loadDirectoryOpt)
{
Debug.Assert(loader != null);
_loader = loader;
_loadDirectoryOpt = loadDirectoryOpt;
}
protected override Assembly Load(AssemblyName assemblyName)
{
return _loader.Loader.ResolveAssembly(AssemblyIdentity.FromAssemblyReference(assemblyName), _loadDirectoryOpt) ??
Default.LoadFromAssemblyName(assemblyName);
}
public new Assembly LoadFromStream(Stream assembly, Stream assemblySymbols)
{
return base.LoadFromStream(assembly, assemblySymbols);
}
public new Assembly LoadFromAssemblyPath(string assemblyPath)
{
return base.LoadFromAssemblyPath(assemblyPath);
}
}
}
}
// 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;
using System.IO;
using System.Reflection;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Scripting.Hosting
{
internal sealed class DesktopAssemblyLoaderImpl : AssemblyLoaderImpl
{
private readonly Func<string, Assembly, Assembly> _assemblyResolveHandlerOpt;
public DesktopAssemblyLoaderImpl(InteractiveAssemblyLoader loader)
: base(loader)
{
_assemblyResolveHandlerOpt = loader.ResolveAssembly;
CorLightup.Desktop.AddAssemblyResolveHandler(_assemblyResolveHandlerOpt);
}
public override void Dispose()
{
if (_assemblyResolveHandlerOpt != null)
{
CorLightup.Desktop.RemoveAssemblyResolveHandler(_assemblyResolveHandlerOpt);
}
}
public override Assembly LoadFromStream(Stream peStream, Stream pdbStream)
{
byte[] peImage = new byte[peStream.Length];
peStream.TryReadAll(peImage, 0, peImage.Length);
return CorLightup.Desktop.LoadAssembly(peImage);
}
public override AssemblyAndLocation LoadFromPath(string path)
{
// An assembly is loaded into CLR's Load Context if it is in the GAC, otherwise it's loaded into No Context via Assembly.LoadFile(string).
// Assembly.LoadFile(string) automatically redirects to GAC if the assembly has a strong name and there is an equivalent assembly in GAC.
var assembly = CorLightup.Desktop.LoadAssembly(path);
var location = CorLightup.Desktop.GetAssemblyLocation(assembly);
var fromGac = CorLightup.Desktop.IsAssemblyFromGlobalAssemblyCache(assembly);
return new AssemblyAndLocation(assembly, location, fromGac);
}
}
}
......@@ -6,8 +6,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Runtime.CompilerServices;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Scripting.Hosting
......@@ -20,7 +19,7 @@ namespace Microsoft.CodeAnalysis.Scripting.Hosting
/// The class is thread-safe.
/// </para>
/// </remarks>
public sealed class InteractiveAssemblyLoader
public sealed partial class InteractiveAssemblyLoader : IDisposable
{
private class LoadedAssembly
{
......@@ -33,6 +32,8 @@ private class LoadedAssembly
public string OriginalPath { get; set; }
}
private readonly AssemblyLoaderImpl _runtimeAssemblyLoader;
private readonly MetadataShadowCopyProvider _shadowCopyProvider;
// Synchronizes assembly reference tracking.
......@@ -82,36 +83,6 @@ public override string ToString()
}
}
private struct AssemblyAndLocation : IEquatable<AssemblyAndLocation>
{
public Assembly Assembly { get; }
public string Location { get; }
public bool GlobalAssemblyCache { get; }
internal AssemblyAndLocation(Assembly assembly, string location, bool fromGac)
{
Debug.Assert(assembly != null && location != null);
Assembly = assembly;
Location = location;
GlobalAssemblyCache = fromGac;
}
public bool IsDefault => Assembly == null;
public bool Equals(AssemblyAndLocation other) =>
Assembly == other.Assembly && Location == other.Location && GlobalAssemblyCache == other.GlobalAssemblyCache;
public override int GetHashCode() =>
Hash.Combine(Assembly, Hash.Combine(Location, Hash.Combine(GlobalAssemblyCache, 0)));
public override bool Equals(object obj) =>
obj is AssemblyAndLocation && Equals((AssemblyAndLocation)obj);
public override string ToString() =>
Assembly + " @ " + (GlobalAssemblyCache ? "<GAC>" : Location);
}
public InteractiveAssemblyLoader(MetadataShadowCopyProvider shadowCopyProvider = null)
{
_shadowCopyProvider = shadowCopyProvider;
......@@ -121,32 +92,22 @@ public InteractiveAssemblyLoader(MetadataShadowCopyProvider shadowCopyProvider =
_loadedAssembliesBySimpleName = new Dictionary<string, List<Assembly>>(AssemblyIdentityComparer.SimpleNameComparer);
_dependenciesWithLocationBySimpleName = new Dictionary<string, List<AssemblyIdentityAndLocation>>();
CorLightup.Desktop.AddAssemblyResolveHandler(AssemblyResolve);
_runtimeAssemblyLoader = AssemblyLoaderImpl.Create(this);
}
internal Assembly Load(Stream peStream, Stream pdbStream)
public void Dispose()
{
byte[] peImage = new byte[peStream.Length];
peStream.TryReadAll(peImage, 0, peImage.Length);
var assembly = CorLightup.Desktop.LoadAssembly(peImage);
RegisterDependency(assembly);
return assembly;
_runtimeAssemblyLoader.Dispose();
}
private static AssemblyAndLocation LoadAssembly(string path)
internal Assembly LoadAssemblyFromStream(Stream peStream, Stream pdbStream)
{
// An assembly is loaded into CLR's Load Context if it is in the GAC, otherwise it's loaded into No Context via Assembly.LoadFile(string).
// Assembly.LoadFile(string) automatically redirects to GAC if the assembly has a strong name and there is an equivalent assembly in GAC.
var assembly = CorLightup.Desktop.LoadAssembly(path);
var location = CorLightup.Desktop.GetAssemblyLocation(assembly);
var fromGac = CorLightup.Desktop.IsAssemblyFromGlobalAssemblyCache(assembly);
return new AssemblyAndLocation(assembly, location, fromGac);
Assembly assembly = _runtimeAssemblyLoader.LoadFromStream(peStream, pdbStream);
RegisterDependency(assembly);
return assembly;
}
private AssemblyAndLocation LoadAssemblyImpl(string reference)
private AssemblyAndLocation Load(string reference)
{
MetadataShadowCopy copy = null;
try
......@@ -160,7 +121,7 @@ private AssemblyAndLocation LoadAssemblyImpl(string reference)
copy = _shadowCopyProvider.GetMetadataShadowCopy(reference, MetadataImageKind.Assembly);
}
var result = LoadAssembly((copy != null) ? copy.PrimaryModule.FullPath : reference);
var result = _runtimeAssemblyLoader.LoadFromPath((copy != null) ? copy.PrimaryModule.FullPath : reference);
if (_shadowCopyProvider != null && result.GlobalAssemblyCache)
{
......@@ -169,14 +130,15 @@ private AssemblyAndLocation LoadAssemblyImpl(string reference)
return result;
}
catch (FileNotFoundException)
{
return default(AssemblyAndLocation);
}
finally
{
// copy holds on the file handle, we need to keep the handle
// open until the file is locked by the CLR assembly loader:
if (copy != null)
{
copy.DisposeFileHandles();
}
copy?.DisposeFileHandles();
}
}
......@@ -323,19 +285,7 @@ private void RegisterDependencyNoLock(AssemblyIdentityAndLocation dependency)
}
}
private AssemblyAndLocation Load(string reference)
{
try
{
return LoadAssemblyImpl(reference);
}
catch (FileNotFoundException)
{
return default(AssemblyAndLocation);
}
}
private Assembly AssemblyResolve(string assemblyDisplayName, Assembly requestingAssemblyOpt)
internal Assembly ResolveAssembly(string assemblyDisplayName, Assembly requestingAssemblyOpt)
{
AssemblyIdentity identity;
if (!AssemblyIdentity.TryParseDisplayName(assemblyDisplayName, out identity))
......@@ -343,57 +293,60 @@ private Assembly AssemblyResolve(string assemblyDisplayName, Assembly requesting
return null;
}
return AssemblyResolve(identity, requestingAssemblyOpt);
string loadDirectoryOpt;
lock (_referencesLock)
{
LoadedAssembly loadedAssembly;
if (requestingAssemblyOpt != null &&
_assembliesLoadedFromLocation.TryGetValue(requestingAssemblyOpt, out loadedAssembly))
{
loadDirectoryOpt = Path.GetDirectoryName(loadedAssembly.OriginalPath);
}
else
{
loadDirectoryOpt = null;
}
}
return ResolveAssembly(identity, loadDirectoryOpt);
}
private Assembly AssemblyResolve(AssemblyIdentity identity, Assembly requestingAssemblyOpt)
internal Assembly ResolveAssembly(AssemblyIdentity identity, string loadDirectoryOpt)
{
if (requestingAssemblyOpt != null)
// if the referring assembly is already loaded by our loader, load from its directory:
if (loadDirectoryOpt != null)
{
string originalDllPath = null, originalExePath = null;
string pathWithoutExtension = Path.Combine(loadDirectoryOpt, identity.Name);
string originalDllPath = pathWithoutExtension + ".dll";
string originalExePath = pathWithoutExtension + ".exe";
string originalPath = null;
lock (_referencesLock)
{
LoadedAssembly loadedAssembly;
if (_assembliesLoadedFromLocation.TryGetValue(requestingAssemblyOpt, out loadedAssembly))
AssemblyAndLocation assembly;
if (_assembliesLoadedFromLocationByFullPath.TryGetValue(originalDllPath, out assembly) ||
_assembliesLoadedFromLocationByFullPath.TryGetValue(originalExePath, out assembly))
{
originalPath = loadedAssembly.OriginalPath;
string pathWithoutExtension = Path.Combine(Path.GetDirectoryName(originalPath), identity.Name);
originalDllPath = pathWithoutExtension + ".dll";
originalExePath = pathWithoutExtension + ".exe";
AssemblyAndLocation assembly;
if (_assembliesLoadedFromLocationByFullPath.TryGetValue(originalDllPath, out assembly) ||
_assembliesLoadedFromLocationByFullPath.TryGetValue(originalExePath, out assembly))
{
return assembly.Assembly;
}
return assembly.Assembly;
}
}
// if the referring assembly is already loaded by our loader, load from its directory:
if (originalPath != null)
{
// Copy & load both .dll and .exe, this is not a common scenario we would need to optimize for.
// Remember both loaded assemblies for the next time, even though their versions might not match the current request
// they might match the next one and we don't want to load them again.
Assembly dll = ShadowCopyAndLoadDependency(originalDllPath).Assembly;
Assembly exe = ShadowCopyAndLoadDependency(originalExePath).Assembly;
// Copy & load both .dll and .exe, this is not a common scenario we would need to optimize for.
// Remember both loaded assemblies for the next time, even though their versions might not match the current request
// they might match the next one and we don't want to load them again.
Assembly dll = ShadowCopyAndLoadDependency(originalDllPath).Assembly;
Assembly exe = ShadowCopyAndLoadDependency(originalExePath).Assembly;
if (dll == null ^ exe == null)
{
return dll ?? exe;
}
if (dll == null ^ exe == null)
{
return dll ?? exe;
}
if (dll != null && exe != null)
{
// .dll and an .exe of the same name might have different versions,
// one of which might match the requested version.
// Prefer .dll if they are both the same.
return FindHighestVersionOrFirstMatchingIdentity(identity, new[] { dll, exe });
}
if (dll != null && exe != null)
{
// .dll and an .exe of the same name might have different versions,
// one of which might match the requested version.
// Prefer .dll if they are both the same.
return FindHighestVersionOrFirstMatchingIdentity(identity, new[] { dll, exe });
}
}
......
......@@ -17,19 +17,22 @@ public sealed class MetadataShadowCopy
/// <summary>
/// Documentation file copy or null if there is none.
/// </summary>
/// <remarks>
/// Documentation files are currently only supported for manifest modules, not modules included in an assembly.
/// </remarks>
public ShadowCopy DocumentationFile { get; }
// this instance doesn't own the image
public Metadata Metadata { get; }
internal MetadataShadowCopy(ShadowCopy primaryModule, ShadowCopy documentationFile, Metadata metadataCopy)
internal MetadataShadowCopy(ShadowCopy primaryModule, ShadowCopy documentationFileOpt, Metadata metadataCopy)
{
Debug.Assert(primaryModule != null);
Debug.Assert(metadataCopy != null);
////Debug.Assert(!metadataCopy.IsImageOwner); property is now internal
PrimaryModule = primaryModule;
DocumentationFile = documentationFile;
DocumentationFile = documentationFileOpt;
Metadata = metadataCopy;
}
......@@ -37,11 +40,7 @@ internal MetadataShadowCopy(ShadowCopy primaryModule, ShadowCopy documentationFi
internal void DisposeFileHandles()
{
PrimaryModule.DisposeFileStream();
if (DocumentationFile != null)
{
DocumentationFile.DisposeFileStream();
}
DocumentationFile?.DisposeFileStream();
}
}
}
......@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using Roslyn.Utilities;
......@@ -15,40 +16,8 @@ namespace Microsoft.CodeAnalysis.Scripting.Hosting
/// </summary>
public sealed class MetadataShadowCopyProvider : IDisposable
{
/// <summary>
/// Specialize <see cref="PortableExecutableReference"/> with path being the original path of the copy.
/// Logically this reference represents that file, the fact that we load the image from a copy is an implementation detail.
/// </summary>
private sealed class ShadowCopyReference : PortableExecutableReference
{
private readonly MetadataShadowCopyProvider _provider;
public ShadowCopyReference(MetadataShadowCopyProvider provider, string originalPath, MetadataReferenceProperties properties)
: base(properties, originalPath)
{
Debug.Assert(originalPath != null);
Debug.Assert(provider != null);
_provider = provider;
}
protected override DocumentationProvider CreateDocumentationProvider()
{
// TODO (tomat): use file next to the dll (or shadow copy)
return DocumentationProvider.Default;
}
protected override Metadata GetMetadataImpl()
{
return _provider.GetMetadata(FilePath, Properties.Kind);
}
protected override PortableExecutableReference WithPropertiesImpl(MetadataReferenceProperties properties)
{
return new ShadowCopyReference(_provider, this.FilePath, properties);
}
}
private readonly CultureInfo _documentationCommentsCulture;
// normalized absolute path
private readonly string _baseDirectory;
......@@ -92,9 +61,10 @@ public CacheEntry(TPublic @public, Metadata @private)
/// </summary>
/// <param name="directory">The directory to use to store file copies.</param>
/// <param name="noShadowCopyDirectories">Directories to exclude from shadow-copying.</param>
/// <param name="documentationCommentsCulture">Culture of documentation comments to copy. If not specified no doc comment files are going to be copied.</param>
/// <exception cref="ArgumentNullException"><paramref name="directory"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="directory"/> is not an absolute path.</exception>
public MetadataShadowCopyProvider(string directory = null, IEnumerable<string> noShadowCopyDirectories = null)
public MetadataShadowCopyProvider(string directory = null, IEnumerable<string> noShadowCopyDirectories = null, CultureInfo documentationCommentsCulture = null)
{
if (directory != null)
{
......@@ -128,6 +98,8 @@ public MetadataShadowCopyProvider(string directory = null, IEnumerable<string> n
{
_noShadowCopyDirectories = ImmutableArray<string>.Empty;
}
_documentationCommentsCulture = documentationCommentsCulture;
}
private static void RequireAbsolutePath(string path, string argumentName)
......@@ -375,14 +347,6 @@ private bool CopyExistsOrIsSuppressed(FileKey key, out CacheEntry<MetadataShadow
return _shadowCopies.TryGetValue(key, out existing);
}
/// <exception cref="ArgumentNullException"><paramref name="fullPath"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="fullPath"/> is not an absolute path.</exception>
public PortableExecutableReference GetReference(string fullPath, MetadataReferenceProperties properties = default(MetadataReferenceProperties))
{
RequireAbsolutePath(fullPath, nameof(fullPath));
return new ShadowCopyReference(this, fullPath, properties);
}
/// <summary>
/// Suppresses shadow-copying of specified path.
/// </summary>
......@@ -444,21 +408,10 @@ private CacheEntry<MetadataShadowCopy> CreateMetadataShadowCopy(string originalP
// Create directory for the assembly.
// If the assembly has any modules they have to be copied to the same directory
// and have the same names as specified in metadata.
string assemblyDir = CreateUniqueDirectory(ShadowCopyDirectory);
string shadowCopyPath = Path.Combine(assemblyDir, Path.GetFileName(originalPath));
string assemblyCopyDir = CreateUniqueDirectory(ShadowCopyDirectory);
string shadowCopyPath = Path.Combine(assemblyCopyDir, Path.GetFileName(originalPath));
ShadowCopy documentationFileCopy = null;
string xmlOriginalPath;
if (XmlFileResolverForAssemblies.TryFindXmlDocumentationFile(originalPath, out xmlOriginalPath))
{
// TODO (tomat): how do doc comments work for multi-module assembly?
var xmlCopyPath = Path.ChangeExtension(shadowCopyPath, ".xml");
var xmlStream = CopyFile(xmlOriginalPath, xmlCopyPath, fileMayNotExist: true);
if (xmlStream != null)
{
documentationFileCopy = new ShadowCopy(xmlStream, xmlOriginalPath, xmlCopyPath);
}
}
ShadowCopy documentationFileCopy = TryCopyDocumentationFile(originalPath, assemblyCopyDir, _documentationCommentsCulture);
var manifestModuleCopyStream = CopyFile(originalPath, shadowCopyPath);
var manifestModuleCopy = new ShadowCopy(manifestModuleCopyStream, originalPath, shadowCopyPath);
......@@ -590,7 +543,78 @@ private string CreateUniqueDirectory(string basePath)
}
}
private FileStream CopyFile(string originalPath, string shadowCopyPath, bool fileMayNotExist = false)
private static ShadowCopy TryCopyDocumentationFile(string originalAssemblyPath, string assemblyCopyDirectory, CultureInfo docCultureOpt)
{
// Note: Doc comments are not supported for netmodules.
string assemblyDirectory = Path.GetDirectoryName(originalAssemblyPath);
string assemblyFileName = Path.GetFileName(originalAssemblyPath);
string xmlSubdirectory;
string xmlFileName;
if (docCultureOpt == null ||
!TryFindCollocatedDocumentationFile(assemblyDirectory, assemblyFileName, docCultureOpt, out xmlSubdirectory, out xmlFileName))
{
return null;
}
if (!xmlSubdirectory.IsEmpty())
{
try
{
Directory.CreateDirectory(Path.Combine(assemblyCopyDirectory, xmlSubdirectory));
}
catch
{
return null;
}
}
string xmlCopyPath = Path.Combine(assemblyCopyDirectory, xmlSubdirectory, xmlFileName);
string xmlOriginalPath = Path.Combine(assemblyDirectory, xmlSubdirectory, xmlFileName);
var xmlStream = CopyFile(xmlOriginalPath, xmlCopyPath, fileMayNotExist: true);
return (xmlStream != null) ? new ShadowCopy(xmlStream, xmlOriginalPath, xmlCopyPath) : null;
}
private static bool TryFindCollocatedDocumentationFile(
string assemblyDirectory,
string assemblyFileName,
CultureInfo culture,
out string docSubdirectory,
out string docFileName)
{
Debug.Assert(assemblyDirectory != null);
Debug.Assert(assemblyFileName != null);
Debug.Assert(culture != null);
// 1. Look in subdirectories based on the current culture
docFileName = Path.ChangeExtension(assemblyFileName, ".xml");
while (culture != CultureInfo.InvariantCulture)
{
docSubdirectory = culture.Name;
if (File.Exists(Path.Combine(assemblyDirectory, docSubdirectory, docFileName)))
{
return true;
}
culture = culture.Parent;
}
// 2. Look in the same directory as the assembly itself
docSubdirectory = string.Empty;
if (File.Exists(Path.Combine(assemblyDirectory, docFileName)))
{
return true;
}
docFileName = null;
return false;
}
private static FileStream CopyFile(string originalPath, string shadowCopyPath, bool fileMayNotExist = false)
{
try
{
......@@ -598,15 +622,10 @@ private FileStream CopyFile(string originalPath, string shadowCopyPath, bool fil
StripReadOnlyAttributeFromFile(new FileInfo(shadowCopyPath));
return new FileStream(shadowCopyPath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
catch (FileNotFoundException)
catch (Exception e) when (fileMayNotExist && (e is FileNotFoundException || e is DirectoryNotFoundException))
{
if (!fileMayNotExist)
{
throw;
}
return null;
}
return null;
}
#region Test hooks
......
......@@ -10,6 +10,7 @@ Microsoft.CodeAnalysis.Scripting.Hosting.CommandLineHostObject.Args -> string[]
Microsoft.CodeAnalysis.Scripting.Hosting.CommandLineHostObject.ExitCode -> int
Microsoft.CodeAnalysis.Scripting.Hosting.CommandLineHostObject.Print(object value) -> void
Microsoft.CodeAnalysis.Scripting.Hosting.InteractiveAssemblyLoader
Microsoft.CodeAnalysis.Scripting.Hosting.InteractiveAssemblyLoader.Dispose() -> void
Microsoft.CodeAnalysis.Scripting.Hosting.InteractiveAssemblyLoader.InteractiveAssemblyLoader(Microsoft.CodeAnalysis.Scripting.Hosting.MetadataShadowCopyProvider shadowCopyProvider = null) -> void
Microsoft.CodeAnalysis.Scripting.Hosting.InteractiveAssemblyLoader.LoadFromPath(string path) -> Microsoft.CodeAnalysis.Scripting.Hosting.AssemblyLoadResult
Microsoft.CodeAnalysis.Scripting.Hosting.InteractiveAssemblyLoader.RegisterDependency(Microsoft.CodeAnalysis.AssemblyIdentity dependency, string path) -> void
......@@ -22,9 +23,8 @@ Microsoft.CodeAnalysis.Scripting.Hosting.MetadataShadowCopyProvider
Microsoft.CodeAnalysis.Scripting.Hosting.MetadataShadowCopyProvider.Dispose() -> void
Microsoft.CodeAnalysis.Scripting.Hosting.MetadataShadowCopyProvider.GetMetadata(string fullPath, Microsoft.CodeAnalysis.MetadataImageKind kind) -> Microsoft.CodeAnalysis.Metadata
Microsoft.CodeAnalysis.Scripting.Hosting.MetadataShadowCopyProvider.GetMetadataShadowCopy(string fullPath, Microsoft.CodeAnalysis.MetadataImageKind kind) -> Microsoft.CodeAnalysis.Scripting.Hosting.MetadataShadowCopy
Microsoft.CodeAnalysis.Scripting.Hosting.MetadataShadowCopyProvider.GetReference(string fullPath, Microsoft.CodeAnalysis.MetadataReferenceProperties properties = default(Microsoft.CodeAnalysis.MetadataReferenceProperties)) -> Microsoft.CodeAnalysis.PortableExecutableReference
Microsoft.CodeAnalysis.Scripting.Hosting.MetadataShadowCopyProvider.IsShadowCopy(string fullPath) -> bool
Microsoft.CodeAnalysis.Scripting.Hosting.MetadataShadowCopyProvider.MetadataShadowCopyProvider(string directory = null, System.Collections.Generic.IEnumerable<string> noShadowCopyDirectories = null) -> void
Microsoft.CodeAnalysis.Scripting.Hosting.MetadataShadowCopyProvider.MetadataShadowCopyProvider(string directory = null, System.Collections.Generic.IEnumerable<string> noShadowCopyDirectories = null, System.Globalization.CultureInfo documentationCommentsCulture = null) -> void
Microsoft.CodeAnalysis.Scripting.Hosting.MetadataShadowCopyProvider.NeedsShadowCopy(string fullPath) -> bool
Microsoft.CodeAnalysis.Scripting.Hosting.MetadataShadowCopyProvider.SuppressShadowCopy(string originalPath) -> void
Microsoft.CodeAnalysis.Scripting.Hosting.ShadowCopy
......
......@@ -152,7 +152,7 @@ private static void ThrowIfAnyCompilationErrors(DiagnosticBag diagnostics, Diagn
peStream.Position = 0;
var assembly = _assemblyLoader.Load(peStream, pdbStream: null);
var assembly = _assemblyLoader.LoadAssemblyFromStream(peStream, pdbStream: null);
var runtimeEntryPoint = GetEntryPointRuntimeMethod(entryPoint, assembly, cancellationToken);
return runtimeEntryPoint.CreateDelegate<Func<object[], Task<T>>>();
......
......@@ -40,6 +40,10 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<DefineConstants>$(DefineConstants);SCRIPTING</DefineConstants>
</PropertyGroup>
<ItemGroup>
<!-- Workaround for https://github.com/NuGet/Home/issues/1471 -->
<Reference Include="$(NuGetPackageRoot)\System.Runtime.Loader\4.0.0-beta-23321\ref\dotnet\System.Runtime.Loader.dll" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\Compilers\Helpers\GlobalAssemblyCacheHelpers\FusionAssemblyIdentity.cs">
<Link>Resolvers\FusionAssemblyIdentity.cs</Link>
......@@ -50,7 +54,11 @@
<Compile Include="..\..\Compilers\Helpers\GlobalAssemblyCacheHelpers\GlobalAssemblyCache.cs">
<Link>Resolvers\GlobalAssemblyCache.cs</Link>
</Compile>
<Compile Include="AssemblyLoader\AssemblyAndLocation.cs" />
<Compile Include="AssemblyLoader\AssemblyLoadResult.cs" />
<Compile Include="AssemblyLoader\CoreAssemblyLoaderImpl.cs" />
<Compile Include="AssemblyLoader\DesktopAssemblyLoaderImpl.cs" />
<Compile Include="AssemblyLoader\AssemblyLoaderImpl.cs" />
<Compile Include="CommandLine\CommandLineHostObject.cs" />
<Compile Include="CommandLine\CommandLineRunner.cs" />
<Compile Include="CommandLine\ConsoleIO.cs" />
......
......@@ -9,6 +9,7 @@
using System.Collections.Immutable;
using Roslyn.Utilities;
using System.Runtime.InteropServices;
using System.Globalization;
namespace Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests
{
......@@ -26,7 +27,12 @@ public class MetadataShadowCopyProviderTests : TestBase, IDisposable
public MetadataShadowCopyProviderTests()
{
_provider = new MetadataShadowCopyProvider(TempRoot.Root, s_systemNoShadowCopyDirectories);
_provider = CreateProvider(CultureInfo.InvariantCulture);
}
private static MetadataShadowCopyProvider CreateProvider(CultureInfo culture)
{
return new MetadataShadowCopyProvider(TempRoot.Root, s_systemNoShadowCopyDirectories, culture);
}
public override void Dispose()
......@@ -51,12 +57,6 @@ public void Errors()
Assert.Throws<ArgumentException>(() => _provider.SuppressShadowCopy(@"\bar.dll"));
Assert.Throws<ArgumentException>(() => _provider.SuppressShadowCopy(@"../bar.dll"));
Assert.Throws<ArgumentNullException>(() => _provider.GetReference(null));
Assert.Throws<ArgumentException>(() => _provider.GetReference("c:foo.dll"));
Assert.Throws<ArgumentException>(() => _provider.GetReference("bar.dll"));
Assert.Throws<ArgumentException>(() => _provider.GetReference(@"\bar.dll"));
Assert.Throws<ArgumentException>(() => _provider.GetReference(@"../bar.dll"));
Assert.Throws<ArgumentOutOfRangeException>(() => _provider.GetMetadataShadowCopy(@"c:\foo.dll", (MetadataImageKind)Byte.MaxValue));
Assert.Throws<ArgumentNullException>(() => _provider.GetMetadataShadowCopy(null, MetadataImageKind.Assembly));
Assert.Throws<ArgumentException>(() => _provider.GetMetadataShadowCopy("c:foo.dll", MetadataImageKind.Assembly));
......@@ -132,12 +132,7 @@ public void Modules()
string path1 = dir.CreateFile("mod2.netmodule").WriteAllBytes(TestResources.SymbolsTests.MultiModule.mod2).Path;
string path2 = dir.CreateFile("mod3.netmodule").WriteAllBytes(TestResources.SymbolsTests.MultiModule.mod3).Path;
var reference1 = _provider.GetReference(path0);
Assert.NotNull(reference1);
Assert.Equal(0, _provider.CacheSize);
Assert.Equal(path0, reference1.FilePath);
var metadata1 = reference1.GetMetadata() as AssemblyMetadata;
var metadata1 = _provider.GetMetadata(path0, MetadataImageKind.Assembly) as AssemblyMetadata;
Assert.NotNull(metadata1);
Assert.Equal(3, metadata1.GetModules().Length);
......@@ -156,38 +151,20 @@ public void Modules()
}
// should get the same metadata:
var metadata2 = reference1.GetMetadata() as AssemblyMetadata;
var metadata2 = _provider.GetMetadata(path0, MetadataImageKind.Assembly) as AssemblyMetadata;
Assert.Same(metadata1, metadata2);
// a new reference is created:
var reference2 = _provider.GetReference(path0);
Assert.NotNull(reference2);
Assert.Equal(path0, reference2.FilePath);
Assert.NotSame(reference1, reference2);
// the original file wasn't modified so we still get the same metadata:
var metadata3 = reference2.GetMetadata() as AssemblyMetadata;
Assert.Same(metadata3, metadata2);
// modify the file:
File.SetLastWriteTimeUtc(path0, DateTime.Now + TimeSpan.FromHours(1));
// the reference doesn't own the metadata, so we get an updated image if we ask again:
var modifiedMetadata3 = reference2.GetMetadata() as AssemblyMetadata;
// we get an updated image if we ask again:
var modifiedMetadata3 = _provider.GetMetadata(path0, MetadataImageKind.Assembly) as AssemblyMetadata;
Assert.NotSame(modifiedMetadata3, metadata2);
// a new reference is created, again we get the modified image (which is copied to the shadow copy directory):
var reference4 = _provider.GetReference(path0);
Assert.NotNull(reference4);
Assert.Equal(path0, reference4.FilePath);
Assert.NotSame(reference2, reference4);
// the file has been modified - we get new metadata:
var metadata4 = reference4.GetMetadata() as AssemblyMetadata;
Assert.NotSame(metadata4, metadata3);
for (int i = 0; i < metadata4.GetModules().Length; i++)
for (int i = 0; i < metadata2.GetModules().Length; i++)
{
Assert.NotSame(metadata4.GetModules()[i], metadata3.GetModules()[i]);
Assert.NotSame(metadata2.GetModules()[i], modifiedMetadata3.GetModules()[i]);
}
}
......@@ -195,12 +172,10 @@ public void Modules()
public unsafe void DisposalOnFailure()
{
var f0 = Temp.CreateFile().WriteAllText("bogus").Path;
var r0 = _provider.GetReference(f0);
Assert.Throws<BadImageFormatException>(() => r0.GetMetadata());
Assert.Throws<BadImageFormatException>(() => _provider.GetMetadata(f0, MetadataImageKind.Assembly));
string f1 = Temp.CreateFile().WriteAllBytes(TestResources.SymbolsTests.MultiModule.MultiModuleDll).Path;
var r1 = _provider.GetReference(f1);
Assert.Throws<FileNotFoundException>(() => r1.GetMetadata());
Assert.Throws<FileNotFoundException>(() => _provider.GetMetadata(f1, MetadataImageKind.Assembly));
}
[Fact]
......@@ -219,13 +194,48 @@ public void GetMetadata()
// This needs to be in different folder from referencesdir to cause the other code path
// to be triggered for NeedsShadowCopy method
var dir2 = System.IO.Path.GetTempPath();
string dll2 = System.IO.Path.Combine(dir2, "a2.dll");
System.IO.File.WriteAllBytes(dll2, TestResources.MetadataTests.InterfaceAndClass.CSClasses01);
var dir2 = Path.GetTempPath();
string dll2 = Path.Combine(dir2, "a2.dll");
File.WriteAllBytes(dll2, TestResources.MetadataTests.InterfaceAndClass.CSClasses01);
Assert.Equal(1, _provider.CacheSize);
var sc3a = _provider.GetMetadataShadowCopy(dll2, MetadataImageKind.Module);
Assert.Equal(2, _provider.CacheSize);
}
[Fact]
public void XmlDocComments_SpecificCulture()
{
var elGR = CultureInfo.GetCultureInfo("el-GR");
var arMA = CultureInfo.GetCultureInfo("ar-MA");
var dir = Temp.CreateDirectory();
var dll = dir.CreateFile("a.dll").WriteAllBytes(TestResources.MetadataTests.InterfaceAndClass.CSClasses01);
var docInvariant = dir.CreateFile("a.xml").WriteAllText("Invariant");
var docGreek = dir.CreateDirectory(elGR.Name).CreateFile("a.xml").WriteAllText("Greek");
// invariant culture
var provider = CreateProvider(CultureInfo.InvariantCulture);
var sc = provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
Assert.Equal(Path.Combine(Path.GetDirectoryName(sc.PrimaryModule.FullPath), @"a.xml"), sc.DocumentationFile.FullPath);
Assert.Equal("Invariant", File.ReadAllText(sc.DocumentationFile.FullPath));
// greek culture
provider = CreateProvider(elGR);
sc = provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
Assert.Equal(Path.Combine(Path.GetDirectoryName(sc.PrimaryModule.FullPath), @"el-GR\a.xml"), sc.DocumentationFile.FullPath);
Assert.Equal("Greek", File.ReadAllText(sc.DocumentationFile.FullPath));
// arabic culture (culture specific docs not found, use invariant)
provider = CreateProvider(arMA);
sc = provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
Assert.Equal(Path.Combine(Path.GetDirectoryName(sc.PrimaryModule.FullPath), @"a.xml"), sc.DocumentationFile.FullPath);
Assert.Equal("Invariant", File.ReadAllText(sc.DocumentationFile.FullPath));
// no culture:
provider = CreateProvider(null);
sc = provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
Assert.Null(sc.DocumentationFile);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册