From 80e9d1b75199aa9caeea8606d490ad0396ff4799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Fri, 17 May 2019 10:57:12 -0700 Subject: [PATCH] Make compilation outputs available via a workspace service (#35659) * Revert "Revert "Make compilation outputs available via a workspace service (#34809)"" This reverts commit f4892bc4f772c0a50c085680af0dae223fb75ca1. * Handle non-absolute path set by VB Web App IntelliSense project --- .../Test/CommandLine/CommandLineTests.cs | 23 ++- .../CommandLine/CommandLineArguments.cs | 45 ++++++ .../Portable/CommandLine/CommonCompiler.cs | 8 +- .../Core/Portable/PublicAPI.Unshipped.txt | 3 + .../DebugInformationReaderProviderTests.cs | 19 +++ ...itAndContinueMethodDebugInfoReaderTests.cs | 51 ++++--- .../Test/Emit/CompilationOutputFilesTests.cs | 55 ++++++++ .../Test/Emit/CompilationOutputsTests.cs | 113 +++++++++++++++ ...deAnalysis.EditorFeatures.UnitTests.csproj | 1 + .../DebugInformationReaderProvider.cs | 132 ++++++++++++++++++ .../EditAndContinueMethodDebugInfoReader.cs | 37 ++++- .../Portable/Emit/CompilationOutputFiles.cs | 51 +++++++ .../Core/Portable/Emit/CompilationOutputs.cs | 131 +++++++++++++++++ .../ICompilationOutputsProviderService.cs | 11 ++ .../Portable/FeaturesResources.Designer.cs | 18 +++ .../Core/Portable/FeaturesResources.resx | 6 + .../Portable/xlf/FeaturesResources.cs.xlf | 10 ++ .../Portable/xlf/FeaturesResources.de.xlf | 10 ++ .../Portable/xlf/FeaturesResources.es.xlf | 10 ++ .../Portable/xlf/FeaturesResources.fr.xlf | 10 ++ .../Portable/xlf/FeaturesResources.it.xlf | 10 ++ .../Portable/xlf/FeaturesResources.ja.xlf | 10 ++ .../Portable/xlf/FeaturesResources.ko.xlf | 10 ++ .../Portable/xlf/FeaturesResources.pl.xlf | 10 ++ .../Portable/xlf/FeaturesResources.pt-BR.xlf | 10 ++ .../Portable/xlf/FeaturesResources.ru.xlf | 10 ++ .../Portable/xlf/FeaturesResources.tr.xlf | 10 ++ .../xlf/FeaturesResources.zh-Hans.xlf | 10 ++ .../xlf/FeaturesResources.zh-Hant.xlf | 10 ++ .../Compilation/CompilationOutputFiles.cs | 18 --- .../Portable/Roslyn.Test.Utilities.csproj | 1 + ...o.LanguageServices.CSharp.UnitTests.csproj | 1 + .../ProjectSystemShim/CPS/AnalyzersTests.cs | 23 ++- .../LegacyProject/AnalyzersTests.cs | 85 ++++------- .../CSharpCompilerOptionsTests.cs | 47 +++++++ ...VisualStudioCompilationOutputFilesTests.cs | 63 +++++++++ ...mpilationOutputFilesWithImplicitPdbPath.cs | 73 ++++++++++ .../Legacy/AbstractLegacyProject.cs | 2 + ...StudioCompilationOutputsProviderService.cs | 20 +++ ...ompilationOutputsProviderServiceFactory.cs | 24 ++++ .../ProjectSystem/VisualStudioProject.cs | 5 + .../VisualStudioProjectOptionsProcessor.cs | 6 +- .../VisualStudioWorkspaceImpl.cs | 24 ++++ .../VisualBasicCompilerOptionsTests.vb | 23 +++ .../Framework/TestEnvironment.vb | 2 +- .../VisualBasicHelpers/VisualBasicHelpers.vb | 9 -- .../ProjectSystemShim/VisualBasicProject.vb | 10 +- 47 files changed, 1131 insertions(+), 139 deletions(-) create mode 100644 src/EditorFeatures/Test/Debugging/DebugInformationReaderProviderTests.cs rename src/EditorFeatures/{CSharpTest => Test}/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs (57%) create mode 100644 src/EditorFeatures/Test/Emit/CompilationOutputFilesTests.cs create mode 100644 src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs create mode 100644 src/Features/Core/Portable/Debugging/DebugInformationReaderProvider.cs create mode 100644 src/Features/Core/Portable/Emit/CompilationOutputFiles.cs create mode 100644 src/Features/Core/Portable/Emit/CompilationOutputs.cs create mode 100644 src/Features/Core/Portable/Emit/ICompilationOutputsProviderService.cs delete mode 100644 src/Test/Utilities/Portable/Compilation/CompilationOutputFiles.cs create mode 100644 src/VisualStudio/CSharp/Test/ProjectSystemShim/VisualStudioCompilationOutputFilesTests.cs create mode 100644 src/VisualStudio/Core/Def/Implementation/ProjectSystem/CompilationOutputFilesWithImplicitPdbPath.cs create mode 100644 src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioCompilationOutputsProviderService.cs create mode 100644 src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioCompilationOutputsProviderServiceFactory.cs diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index bbe929a3df2a..aa27b84e1340 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -1790,30 +1790,35 @@ public void Debug() parsedArgs.Errors.Verify(); Assert.False(parsedArgs.CompilationOptions.DebugPlusMode); Assert.False(parsedArgs.EmitPdb); + Assert.False(parsedArgs.EmitPdbFile); Assert.Equal(parsedArgs.EmitOptions.DebugInformationFormat, platformPdbKind); parsedArgs = DefaultParse(new[] { "/debug-", "a.cs" }, WorkingDirectory); parsedArgs.Errors.Verify(); Assert.False(parsedArgs.CompilationOptions.DebugPlusMode); Assert.False(parsedArgs.EmitPdb); + Assert.False(parsedArgs.EmitPdbFile); Assert.Equal(parsedArgs.EmitOptions.DebugInformationFormat, platformPdbKind); parsedArgs = DefaultParse(new[] { "/debug", "a.cs" }, WorkingDirectory); parsedArgs.Errors.Verify(); Assert.False(parsedArgs.CompilationOptions.DebugPlusMode); Assert.True(parsedArgs.EmitPdb); + Assert.True(parsedArgs.EmitPdbFile); Assert.Equal(parsedArgs.EmitOptions.DebugInformationFormat, platformPdbKind); parsedArgs = DefaultParse(new[] { "/debug+", "a.cs" }, WorkingDirectory); parsedArgs.Errors.Verify(); Assert.True(parsedArgs.CompilationOptions.DebugPlusMode); Assert.True(parsedArgs.EmitPdb); + Assert.True(parsedArgs.EmitPdbFile); Assert.Equal(parsedArgs.EmitOptions.DebugInformationFormat, platformPdbKind); parsedArgs = DefaultParse(new[] { "/debug+", "/debug-", "a.cs" }, WorkingDirectory); parsedArgs.Errors.Verify(); Assert.False(parsedArgs.CompilationOptions.DebugPlusMode); Assert.False(parsedArgs.EmitPdb); + Assert.False(parsedArgs.EmitPdbFile); Assert.Equal(parsedArgs.EmitOptions.DebugInformationFormat, platformPdbKind); parsedArgs = DefaultParse(new[] { "/debug:full", "a.cs" }, WorkingDirectory); @@ -1827,6 +1832,7 @@ public void Debug() Assert.False(parsedArgs.CompilationOptions.DebugPlusMode); Assert.True(parsedArgs.EmitPdb); Assert.Equal(parsedArgs.EmitOptions.DebugInformationFormat, platformPdbKind); + Assert.Equal(Path.Combine(WorkingDirectory, "a.pdb"), parsedArgs.GetPdbFilePath("a.dll")); parsedArgs = DefaultParse(new[] { "/debug:pdbonly", "a.cs" }, WorkingDirectory); parsedArgs.Errors.Verify(); @@ -1839,12 +1845,14 @@ public void Debug() Assert.False(parsedArgs.CompilationOptions.DebugPlusMode); Assert.True(parsedArgs.EmitPdb); Assert.Equal(parsedArgs.EmitOptions.DebugInformationFormat, DebugInformationFormat.PortablePdb); + Assert.Equal(Path.Combine(WorkingDirectory, "a.pdb"), parsedArgs.GetPdbFilePath("a.dll")); parsedArgs = DefaultParse(new[] { "/debug:embedded", "a.cs" }, WorkingDirectory); parsedArgs.Errors.Verify(); Assert.False(parsedArgs.CompilationOptions.DebugPlusMode); Assert.True(parsedArgs.EmitPdb); Assert.Equal(parsedArgs.EmitOptions.DebugInformationFormat, DebugInformationFormat.Embedded); + Assert.Equal(Path.Combine(WorkingDirectory, "a.pdb"), parsedArgs.GetPdbFilePath("a.dll")); parsedArgs = DefaultParse(new[] { "/debug:PDBONLY", "a.cs" }, WorkingDirectory); parsedArgs.Errors.Verify(); @@ -1912,14 +1920,23 @@ public void Pdb() { var parsedArgs = DefaultParse(new[] { "/pdb:something", "a.cs" }, WorkingDirectory); Assert.Equal(Path.Combine(WorkingDirectory, "something.pdb"), parsedArgs.PdbPath); + Assert.Equal(Path.Combine(WorkingDirectory, "something.pdb"), parsedArgs.GetPdbFilePath("a.dll")); + Assert.False(parsedArgs.EmitPdbFile); - // No pdb - parsedArgs = DefaultParse(new[] { @"/debug", "a.cs" }, WorkingDirectory); + parsedArgs = DefaultParse(new[] { "/pdb:something", "/debug:embedded", "a.cs" }, WorkingDirectory); + Assert.Equal(Path.Combine(WorkingDirectory, "something.pdb"), parsedArgs.PdbPath); + Assert.Equal(Path.Combine(WorkingDirectory, "something.pdb"), parsedArgs.GetPdbFilePath("a.dll")); + Assert.False(parsedArgs.EmitPdbFile); + + parsedArgs = DefaultParse(new[] { "/debug", "a.cs" }, WorkingDirectory); parsedArgs.Errors.Verify(); Assert.Null(parsedArgs.PdbPath); + Assert.True(parsedArgs.EmitPdbFile); + Assert.Equal(Path.Combine(WorkingDirectory, "a.pdb"), parsedArgs.GetPdbFilePath("a.dll")); parsedArgs = DefaultParse(new[] { "/pdb", "/debug", "a.cs" }, WorkingDirectory); parsedArgs.Errors.Verify(Diagnostic(ErrorCode.ERR_NoFileSpec).WithArguments("/pdb")); + Assert.Equal(Path.Combine(WorkingDirectory, "a.pdb"), parsedArgs.GetPdbFilePath("a.dll")); parsedArgs = DefaultParse(new[] { "/pdb:", "/debug", "a.cs" }, WorkingDirectory); parsedArgs.Errors.Verify(Diagnostic(ErrorCode.ERR_NoFileSpec).WithArguments("/pdb:")); @@ -3310,6 +3327,7 @@ public void ParseOut() Assert.Equal("MyBinary.dll", parsedArgs.OutputFileName); Assert.Equal("MyBinary.dll", parsedArgs.CompilationOptions.ModuleName); Assert.Equal(@"C:\MyFolder", parsedArgs.OutputDirectory); + Assert.Equal(@"C:\MyFolder\MyBinary.dll", parsedArgs.GetOutputFilePath(parsedArgs.OutputFileName)); // Should handle quotes parsedArgs = DefaultParse(new[] { @"/out:""C:\My Folder\MyBinary.dll""", "a.cs" }, baseDirectory); @@ -3326,6 +3344,7 @@ public void ParseOut() Assert.Equal("MyBinary.dll", parsedArgs.OutputFileName); Assert.Equal("MyBinary.dll", parsedArgs.CompilationOptions.ModuleName); Assert.Equal(baseDirectory, parsedArgs.OutputDirectory); + Assert.Equal(Path.Combine(baseDirectory, "MyBinary.dll"), parsedArgs.GetOutputFilePath(parsedArgs.OutputFileName)); // Should expand partially qualified paths parsedArgs = DefaultParse(new[] { @"/out:..\MyBinary.dll", "a.cs" }, baseDirectory); diff --git a/src/Compilers/Core/Portable/CommandLine/CommandLineArguments.cs b/src/Compilers/Core/Portable/CommandLine/CommandLineArguments.cs index 44dcb5ae3304..c813db227e13 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommandLineArguments.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommandLineArguments.cs @@ -292,6 +292,51 @@ internal CommandLineArguments() { } + /// + /// Returns a full path of the file that the compiler will generate the assembly to if compilation succeeds. + /// + /// + /// The method takes rather than using the value of + /// since the latter might be unspecified, in which case actual output path can't be determined for C# command line + /// without creating a compilation and finding an entry point. VB does not allow to + /// be unspecified. + /// + public string GetOutputFilePath(string outputFileName) + { + if (outputFileName == null) + { + throw new ArgumentNullException(nameof(outputFileName)); + } + + return Path.Combine(OutputDirectory, outputFileName); + } + + /// + /// Returns a full path of the PDB file that the compiler will generate the debug symbols to + /// if is true and the compilation succeeds. + /// + /// + /// The method takes rather than using the value of + /// since the latter might be unspecified, in which case actual output path can't be determined for C# command line + /// without creating a compilation and finding an entry point. VB does not allow to + /// be unspecified. + /// + public string GetPdbFilePath(string outputFileName) + { + if (outputFileName == null) + { + throw new ArgumentNullException(nameof(outputFileName)); + } + + return PdbPath ?? Path.Combine(OutputDirectory, Path.ChangeExtension(outputFileName, ".pdb")); + } + + /// + /// Returns true if the PDB is generated to a PDB file, as opposed to embedded to the output binary and not generated at all. + /// + public bool EmitPdbFile + => EmitPdb && EmitOptions.DebugInformationFormat != DebugInformationFormat.Embedded; + #region Metadata References /// diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs index 778777bcf60b..65cd9834190e 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @@ -882,8 +882,8 @@ private void CompileAndEmit( cancellationToken.ThrowIfCancellationRequested(); string outputName = GetOutputFileName(compilation, cancellationToken); - var finalPeFilePath = Path.Combine(Arguments.OutputDirectory, outputName); - var finalPdbFilePath = Arguments.PdbPath ?? Path.ChangeExtension(finalPeFilePath, ".pdb"); + var finalPeFilePath = Arguments.GetOutputFilePath(outputName); + var finalPdbFilePath = Arguments.GetPdbFilePath(outputName); var finalXmlFilePath = Arguments.DocumentationPath; NoThrowStreamDisposer sourceLinkStreamDisposerOpt = null; @@ -1035,10 +1035,8 @@ private void CompileAndEmit( if (success) { - bool emitPdbFile = Arguments.EmitPdb && emitOptions.DebugInformationFormat != Emit.DebugInformationFormat.Embedded; - var peStreamProvider = new CompilerEmitStreamProvider(this, finalPeFilePath); - var pdbStreamProviderOpt = emitPdbFile ? new CompilerEmitStreamProvider(this, finalPdbFilePath) : null; + var pdbStreamProviderOpt = Arguments.EmitPdbFile ? new CompilerEmitStreamProvider(this, finalPdbFilePath) : null; string finalRefPeFilePath = Arguments.OutputRefFilePath; var refPeStreamProviderOpt = finalRefPeFilePath != null ? new CompilerEmitStreamProvider(this, finalRefPeFilePath) : null; diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 71901ce1fc9e..118f2cd5c6c1 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -1,5 +1,8 @@ *REMOVED*Microsoft.CodeAnalysis.Operations.IEventAssignmentOperation.EventReference.get -> Microsoft.CodeAnalysis.Operations.IEventReferenceOperation *REMOVED*Microsoft.CodeAnalysis.SpecialType.Count = 43 -> Microsoft.CodeAnalysis.SpecialType +Microsoft.CodeAnalysis.CommandLineArguments.EmitPdbFile.get -> bool +Microsoft.CodeAnalysis.CommandLineArguments.GetOutputFilePath(string outputFileName) -> string +Microsoft.CodeAnalysis.CommandLineArguments.GetPdbFilePath(string outputFileName) -> string Microsoft.CodeAnalysis.IArrayTypeSymbol.ElementNullableAnnotation.get -> Microsoft.CodeAnalysis.NullableAnnotation Microsoft.CodeAnalysis.IDiscardSymbol.NullableAnnotation.get -> Microsoft.CodeAnalysis.NullableAnnotation Microsoft.CodeAnalysis.IEventSymbol.NullableAnnotation.get -> Microsoft.CodeAnalysis.NullableAnnotation diff --git a/src/EditorFeatures/Test/Debugging/DebugInformationReaderProviderTests.cs b/src/EditorFeatures/Test/Debugging/DebugInformationReaderProviderTests.cs new file mode 100644 index 000000000000..6efe806a7713 --- /dev/null +++ b/src/EditorFeatures/Test/Debugging/DebugInformationReaderProviderTests.cs @@ -0,0 +1,19 @@ +// 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 Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Debugging.UnitTests +{ + public class DebugInformationReaderProviderTests + { + [Fact] + public void CreateFrom_Errors() + { + Assert.Throws(() => DebugInformationReaderProvider.CreateFromStream(new TestStream(canRead: false, canSeek: true, canWrite: true))); + Assert.Throws(() => DebugInformationReaderProvider.CreateFromStream(new TestStream(canRead: true, canSeek: false, canWrite: true))); + Assert.Throws(() => DebugInformationReaderProvider.CreateFromStream(null)); + Assert.Throws(() => DebugInformationReaderProvider.CreateFromMetadataReader(null)); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs similarity index 57% rename from src/EditorFeatures/CSharpTest/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs rename to src/EditorFeatures/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs index a203ae9712cd..9bca46e32137 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueMethodDebugInfoReaderTests.cs @@ -1,19 +1,20 @@ // 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.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; -using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Debugging; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.UnitTests; using Microsoft.DiaSymReader; using Microsoft.DiaSymReader.PortablePdb; +using Moq; using Roslyn.Test.Utilities; using Xunit; -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.EditAndContinue +namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests { public class EditAndContinueMethodDebugInfoReaderTests { @@ -22,26 +23,24 @@ private class DummyMetadataImportProvider : IMetadataImportProvider public object GetMetadataImport() => throw new NotImplementedException(); } - private class DummySymReaderMetadataProvider : ISymReaderMetadataProvider + [Fact] + public void Create_Errors() { - public unsafe bool TryGetStandaloneSignature(int standaloneSignatureToken, out byte* signature, out int length) - => throw new NotImplementedException(); + Assert.Throws(() => EditAndContinueMethodDebugInfoReader.Create((ISymUnmanagedReader5)null)); + Assert.Throws(() => EditAndContinueMethodDebugInfoReader.Create((MetadataReader)null)); + Assert.Throws(() => EditAndContinueMethodDebugInfoReader.Create(null, 1)); - public bool TryGetTypeDefinitionInfo(int typeDefinitionToken, out string namespaceName, out string typeName, out TypeAttributes attributes) - => throw new NotImplementedException(); - - public bool TryGetTypeReferenceInfo(int typeReferenceToken, out string namespaceName, out string typeName) - => throw new NotImplementedException(); + var mockSymReader = new Mock().Object; + Assert.Throws(() => EditAndContinueMethodDebugInfoReader.Create(mockSymReader, 0)); + Assert.Throws(() => EditAndContinueMethodDebugInfoReader.Create(mockSymReader, -1)); } [Theory] - [InlineData(DebugInformationFormat.PortablePdb)] - [InlineData(DebugInformationFormat.Pdb)] - public void DebugInfo(DebugInformationFormat format) + [InlineData(DebugInformationFormat.PortablePdb, true)] + [InlineData(DebugInformationFormat.PortablePdb, false)] + [InlineData(DebugInformationFormat.Pdb, true)] + public void DebugInfo(DebugInformationFormat format, bool useSymReader) { - var symBinder = new SymBinder(); - var metadataImportProvider = new DummyMetadataImportProvider(); - var source = @" using System; delegate void D(); @@ -61,22 +60,22 @@ public static void Main() compilation.EmitToArray(new EmitOptions(debugInformationFormat: format), pdbStream: pdbStream); pdbStream.Position = 0; - var pdbStreamCom = SymUnmanagedStreamFactory.CreateStream(pdbStream); + DebugInformationReaderProvider provider; + EditAndContinueMethodDebugInfoReader reader; - ISymUnmanagedReader5 symReader5; - if (format == DebugInformationFormat.PortablePdb) + if (format == DebugInformationFormat.PortablePdb && useSymReader) { - int hr = symBinder.GetReaderFromPdbStream(metadataImportProvider, pdbStreamCom, out var symReader); - Assert.Equal(0, hr); - symReader5 = (ISymUnmanagedReader5)symReader; + var pdbStreamCom = SymUnmanagedStreamFactory.CreateStream(pdbStream); + var metadataImportProvider = new DummyMetadataImportProvider(); + Assert.Equal(0, new SymBinder().GetReaderFromPdbStream(metadataImportProvider, pdbStreamCom, out var symReader)); + reader = EditAndContinueMethodDebugInfoReader.Create((ISymUnmanagedReader5)symReader, version: 1); } else { - symReader5 = SymUnmanagedReaderFactory.CreateReader(pdbStream, new DummySymReaderMetadataProvider()); + provider = DebugInformationReaderProvider.CreateFromStream(pdbStream); + reader = provider.CreateEditAndContinueMethodDebugInfoReader(); } - var reader = EditAndContinueMethodDebugInfoReader.Create(symReader5, version: 1); - // Main method var debugInfo = reader.GetDebugInfo(MetadataTokens.MethodDefinitionHandle(5)); Assert.Equal(0, debugInfo.GetMethodOrdinal()); diff --git a/src/EditorFeatures/Test/Emit/CompilationOutputFilesTests.cs b/src/EditorFeatures/Test/Emit/CompilationOutputFilesTests.cs new file mode 100644 index 000000000000..98749344f8d8 --- /dev/null +++ b/src/EditorFeatures/Test/Emit/CompilationOutputFilesTests.cs @@ -0,0 +1,55 @@ +// 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.Metadata.Ecma335; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Emit.UnitTests +{ + public class CompilationOutputFilesTests : TestBase + { + [Fact] + public void OpenStream_Errors() + { + Assert.Throws(() => new CompilationOutputFiles(@"a.dll")); + Assert.Throws(() => new CompilationOutputFiles(@"\a.dll", @"a.dll")); + } + + [Fact] + public void AssemblyAndPdb() + { + var source = @"class C { public static void Main() { int x = 1; } }"; + + var compilation = CSharpTestBase.CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll, assemblyName: "lib"); + var pdbStream = new MemoryStream(); + var peImage = compilation.EmitToArray(new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb), pdbStream: pdbStream); + pdbStream.Position = 0; + + var dir = Temp.CreateDirectory(); + var dllFile = dir.CreateFile("a.dll").WriteAllBytes(peImage); + var pdbFile = dir.CreateFile("a.pdb").WriteAllBytes(pdbStream.ToArray()); + + var outputs = new CompilationOutputFiles(dllFile.Path, pdbFile.Path); + + using (var pdb = outputs.OpenPdb()) + { + var encReader = pdb.CreateEditAndContinueMethodDebugInfoReader(); + Assert.True(encReader.IsPortable); + var localSig = encReader.GetLocalSignature(MetadataTokens.MethodDefinitionHandle(1)); + Assert.Equal(MetadataTokens.StandaloneSignatureHandle(1), localSig); + } + + using (var metadata = outputs.OpenAssemblyMetadata(prefetch: false)) + { + var mdReader = metadata.GetMetadataReader(); + Assert.Equal("lib", mdReader.GetString(mdReader.GetAssemblyDefinition().Name)); + } + + // make sure all files are closed and can be deleted + Directory.Delete(dir.Path, recursive: true); + } + } +} diff --git a/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs b/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs new file mode 100644 index 000000000000..343c6a6bd854 --- /dev/null +++ b/src/EditorFeatures/Test/Emit/CompilationOutputsTests.cs @@ -0,0 +1,113 @@ +// 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.Linq; +using System.Reflection.Metadata.Ecma335; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Emit.UnitTests +{ + public class CompilationOutputsTests + { + private class TestCompilationOutputs : CompilationOutputs + { + private readonly Func _openAssemblyStream; + private readonly Func _openPdbStream; + + public TestCompilationOutputs(Func openAssemblyStream = null, Func openPdbStream = null) + { + _openAssemblyStream = openAssemblyStream; + _openPdbStream = openPdbStream; + } + + public override string AssemblyDisplayPath => "assembly"; + public override string PdbDisplayPath => "pdb"; + protected override Stream OpenAssemblyStream() => (_openAssemblyStream ?? throw new NotImplementedException()).Invoke(); + protected override Stream OpenPdbStream() => (_openPdbStream ?? throw new NotImplementedException()).Invoke(); + } + + [Theory] + [InlineData(true, false)] + [InlineData(false, true)] + public void OpenStream_Errors(bool canRead, bool canSeek) + { + var outputs = new TestCompilationOutputs( + openAssemblyStream: () => new TestStream(canRead, canSeek, canWrite: true), + openPdbStream: () => new TestStream(canRead, canSeek, canWrite: true)); + + Assert.Throws(() => outputs.OpenAssemblyMetadata(prefetch: false)); + Assert.Throws(() => outputs.OpenPdb()); + } + + [Theory] + [InlineData(DebugInformationFormat.PortablePdb)] + [InlineData(DebugInformationFormat.Embedded)] + [InlineData(DebugInformationFormat.Pdb)] + public void AssemblyAndPdb(DebugInformationFormat format) + { + var source = @"class C { public static void Main() { int x = 1; } }"; + var compilation = CSharpTestBase.CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll, assemblyName: "lib"); + + var pdbStream = (format != DebugInformationFormat.Embedded) ? new MemoryStream() : null; + var peImage = compilation.EmitToArray(new EmitOptions(debugInformationFormat: format), pdbStream: pdbStream); + + Stream currentPEStream = null; + Stream currentPdbStream = null; + + var outputs = new TestCompilationOutputs( + openAssemblyStream: () => currentPEStream = new MemoryStream(peImage.ToArray()), + openPdbStream: () => + { + if (pdbStream == null) + { + return null; + } + + currentPdbStream = new MemoryStream(); + pdbStream.Position = 0; + pdbStream.CopyTo(currentPdbStream); + currentPdbStream.Position = 0; + return currentPdbStream; + }); + + using (var pdb = outputs.OpenPdb()) + { + var encReader = pdb.CreateEditAndContinueMethodDebugInfoReader(); + Assert.Equal(format != DebugInformationFormat.Pdb, encReader.IsPortable); + var localSig = encReader.GetLocalSignature(MetadataTokens.MethodDefinitionHandle(1)); + Assert.Equal(MetadataTokens.StandaloneSignatureHandle(1), localSig); + } + + if (format == DebugInformationFormat.Embedded) + { + Assert.Throws(() => currentPEStream.Length); + } + else + { + Assert.Throws(() => currentPdbStream.Length); + } + + using (var metadata = outputs.OpenAssemblyMetadata(prefetch: false)) + { + Assert.NotEqual(0, currentPEStream.Length); + + var mdReader = metadata.GetMetadataReader(); + Assert.Equal("lib", mdReader.GetString(mdReader.GetAssemblyDefinition().Name)); + } + + Assert.Throws(() => currentPEStream.Length); + + using (var metadata = outputs.OpenAssemblyMetadata(prefetch: true)) + { + // the stream has been closed since we prefetched the metadata: + Assert.Throws(() => currentPEStream.Length); + + var mdReader = metadata.GetMetadataReader(); + Assert.Equal("lib", mdReader.GetString(mdReader.GetAssemblyDefinition().Name)); + } + } + } +} diff --git a/src/EditorFeatures/Test/Microsoft.CodeAnalysis.EditorFeatures.UnitTests.csproj b/src/EditorFeatures/Test/Microsoft.CodeAnalysis.EditorFeatures.UnitTests.csproj index a5b6b578d5d2..23f7b14b04b4 100644 --- a/src/EditorFeatures/Test/Microsoft.CodeAnalysis.EditorFeatures.UnitTests.csproj +++ b/src/EditorFeatures/Test/Microsoft.CodeAnalysis.EditorFeatures.UnitTests.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Features/Core/Portable/Debugging/DebugInformationReaderProvider.cs b/src/Features/Core/Portable/Debugging/DebugInformationReaderProvider.cs new file mode 100644 index 000000000000..7533aee2b970 --- /dev/null +++ b/src/Features/Core/Portable/Debugging/DebugInformationReaderProvider.cs @@ -0,0 +1,132 @@ +// 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.Reflection.Metadata; +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.DiaSymReader; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Debugging +{ + /// + /// An abstraction of a symbol reader that provides a reader of Edit and Continue debug information. + /// Owns the underlying PDB reader. + /// + internal abstract class DebugInformationReaderProvider : IDisposable + { + private sealed class DummySymReaderMetadataProvider : ISymReaderMetadataProvider + { + public static readonly DummySymReaderMetadataProvider Instance = new DummySymReaderMetadataProvider(); + + public unsafe bool TryGetStandaloneSignature(int standaloneSignatureToken, out byte* signature, out int length) + => throw ExceptionUtilities.Unreachable; + + public bool TryGetTypeDefinitionInfo(int typeDefinitionToken, out string namespaceName, out string typeName, out TypeAttributes attributes) + => throw ExceptionUtilities.Unreachable; + + public bool TryGetTypeReferenceInfo(int typeReferenceToken, out string namespaceName, out string typeName) + => throw ExceptionUtilities.Unreachable; + } + + private sealed class Portable : DebugInformationReaderProvider + { + private readonly MetadataReaderProvider _pdbReaderProvider; + + public Portable(MetadataReaderProvider pdbReaderProvider) + => _pdbReaderProvider = pdbReaderProvider; + + public override EditAndContinueMethodDebugInfoReader CreateEditAndContinueMethodDebugInfoReader() + => EditAndContinueMethodDebugInfoReader.Create(_pdbReaderProvider.GetMetadataReader()); + + public override void Dispose() + => _pdbReaderProvider.Dispose(); + } + + private sealed class Native : DebugInformationReaderProvider + { + private readonly Stream _stream; + private readonly int _version; + private ISymUnmanagedReader5 _symReader; + + public Native(Stream stream, ISymUnmanagedReader5 symReader, int version) + { + _stream = stream; + _symReader = symReader; + _version = version; + } + + public override EditAndContinueMethodDebugInfoReader CreateEditAndContinueMethodDebugInfoReader() + => EditAndContinueMethodDebugInfoReader.Create(_symReader, _version); + + public override void Dispose() + { + _stream.Dispose(); + + var symReader = Interlocked.Exchange(ref _symReader, null); + if (symReader != null && Marshal.IsComObject(symReader)) + { + Marshal.ReleaseComObject(symReader); + } + } + } + + public abstract void Dispose(); + + /// + /// Creates EnC debug information reader. + /// + public abstract EditAndContinueMethodDebugInfoReader CreateEditAndContinueMethodDebugInfoReader(); + + /// + /// Creates from a stream of Portable or Windows PDB. + /// + /// + /// Provider instance, which keeps the open until disposed. + /// + /// + /// Requires Microsoft.DiaSymReader.Native.{platform}.dll to be available for reading Windows PDB. + /// + /// is null. + /// does not support read and seek operations. + /// Error reading debug information from . + public static DebugInformationReaderProvider CreateFromStream(Stream stream) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!stream.CanRead || !stream.CanSeek) + { + throw new ArgumentException(FeaturesResources.StreamMustSupportReadAndSeek, nameof(stream)); + } + + bool isPortable = stream.ReadByte() == 'B' && stream.ReadByte() == 'S' && stream.ReadByte() == 'J' && stream.ReadByte() == 'B'; + stream.Position = 0; + + if (isPortable) + { + return new Portable(MetadataReaderProvider.FromPortablePdbStream(stream)); + } + + // We can use DummySymReaderMetadataProvider since we do not need to decode signatures, + // which is the only operation SymReader needs the provider for. + return new Native(stream, SymUnmanagedReaderFactory.CreateReader( + stream, DummySymReaderMetadataProvider.Instance, SymUnmanagedReaderCreationOptions.UseAlternativeLoadPath), version: 1); + } + + /// + /// Creates from a Portable PDB metadata reader provider. + /// + /// + /// Provider instance, which takes ownership of the until disposed. + /// + /// is null. + public static DebugInformationReaderProvider CreateFromMetadataReader(MetadataReaderProvider metadataProvider) + => new Portable(metadataProvider ?? throw new ArgumentNullException(nameof(metadataProvider))); + } +} diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs index 9c72308bbe5c..d6caebd48118 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueMethodDebugInfoReader.cs @@ -139,8 +139,32 @@ private static bool TryGetCustomDebugInformation(MetadataReader reader, EntityHa } } - public unsafe static EditAndContinueMethodDebugInfoReader Create(ISymUnmanagedReader5 symReader, int version) + /// + /// Creates backed by a given . + /// + /// SymReader open on a Portable or Windows PDB. + /// The version of the PDB to read. + /// is null. + /// is less than 1. + /// Error reading debug information. + /// + /// The resulting reader does not take ownership of the or the memory it reads. + /// + /// + /// Automatically detects the underlying PDB format and returns the appropriate reader. + /// + public unsafe static EditAndContinueMethodDebugInfoReader Create(ISymUnmanagedReader5 symReader, int version = 1) { + if (symReader == null) + { + throw new ArgumentNullException(nameof(symReader)); + } + + if (version <= 0) + { + throw new ArgumentOutOfRangeException(nameof(version)); + } + int hr = symReader.GetPortableDebugMetadataByVersion(version, metadata: out byte* metadata, size: out int size); Marshal.ThrowExceptionForHR(hr); @@ -153,5 +177,16 @@ public unsafe static EditAndContinueMethodDebugInfoReader Create(ISymUnmanagedRe return new Native(symReader, version); } } + + /// + /// Creates back by a given . + /// + /// open on a Portable PDB. + /// is null. + /// + /// The resulting reader does not take ownership of the or the memory it reads. + /// + public unsafe static EditAndContinueMethodDebugInfoReader Create(MetadataReader pdbReader) + => new Portable(pdbReader ?? throw new ArgumentNullException(nameof(pdbReader))); } } diff --git a/src/Features/Core/Portable/Emit/CompilationOutputFiles.cs b/src/Features/Core/Portable/Emit/CompilationOutputFiles.cs new file mode 100644 index 000000000000..9a29f7f095a9 --- /dev/null +++ b/src/Features/Core/Portable/Emit/CompilationOutputFiles.cs @@ -0,0 +1,51 @@ +// 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 Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Emit +{ + internal sealed class CompilationOutputFiles : CompilationOutputs + { + internal static readonly CompilationOutputFiles None = new CompilationOutputFiles(); + + public override string AssemblyDisplayPath => AssemblyFilePath; + public override string PdbDisplayPath => PdbFilePath; + + public string PdbFilePath { get; } + public string AssemblyFilePath { get; } + + public CompilationOutputFiles(string assemblyFilePath = null, string pdbFilePath = null) + { + if (assemblyFilePath != null) + { + CompilerPathUtilities.RequireAbsolutePath(assemblyFilePath, nameof(assemblyFilePath)); + } + + if (pdbFilePath != null) + { + CompilerPathUtilities.RequireAbsolutePath(pdbFilePath, nameof(pdbFilePath)); + } + + AssemblyFilePath = assemblyFilePath; + PdbFilePath = pdbFilePath; + } + + /// + /// Opens an assembly file produced by the compiler (corresponds to OutputAssembly build task parameter). + /// + protected override Stream OpenAssemblyStream() + => AssemblyFilePath != null ? FileUtilities.OpenRead(AssemblyFilePath) : null; + + /// + /// Opens a PDB file produced by the compiler. + /// Returns null if the compiler generated no PDB (the symbols might be embedded in the assembly). + /// + /// + /// The stream must be readable and seekable. + /// + protected override Stream OpenPdbStream() + => PdbFilePath != null ? FileUtilities.OpenRead(PdbFilePath) : null; + } +} diff --git a/src/Features/Core/Portable/Emit/CompilationOutputs.cs b/src/Features/Core/Portable/Emit/CompilationOutputs.cs new file mode 100644 index 000000000000..7af2a6c86a46 --- /dev/null +++ b/src/Features/Core/Portable/Emit/CompilationOutputs.cs @@ -0,0 +1,131 @@ +// 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.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using Microsoft.CodeAnalysis.Debugging; + +namespace Microsoft.CodeAnalysis.Emit +{ + /// + /// Reads compilation outputs such as output assembly and PDB. + /// + internal abstract class CompilationOutputs + { + /// + /// String describing the assembly to be used in user facing error messages (e.g. file path). + /// + public abstract string AssemblyDisplayPath { get; } + + /// + /// String describing the PDB to be used in user facing error messages (e.g. file path). + /// + public abstract string PdbDisplayPath { get; } + + /// + /// Opens metadata section of the assembly file produced by the compiler. + /// + /// + /// True to prefetch all metadata from the assembly and close the underlying stream on return, + /// otherwise keeps the underlying stream open until the returned is disposed. + /// + /// + /// Instance of , which owns the opened metadata and must be disposed once the caller is done reading the data, + /// or null if the assembly is not available. + /// + /// Invalid format of the assembly data. + /// The stream returned by does not support read and seek operations. + /// Error while reading assembly data. + public virtual MetadataReaderProvider OpenAssemblyMetadata(bool prefetch) + { + var peStream = OpenAssemblyStreamChecked(); + if (peStream != null) + { + PEHeaders peHeaders; + using (var peReader = new PEReader(peStream, PEStreamOptions.LeaveOpen)) + { + peHeaders = peReader.PEHeaders; + } + + peStream.Position = peHeaders.MetadataStartOffset; + return MetadataReaderProvider.FromMetadataStream( + peStream, + prefetch ? MetadataStreamOptions.PrefetchMetadata : MetadataStreamOptions.Default, + size: peHeaders.MetadataSize); + } + + return null; + } + + /// + /// Opens PDB produced by the compiler. + /// The caller must dispose the returned . + /// + /// + /// Instance of , which owns the opened PDB and must be disposed once the caller is done reading the data, + /// or null if PDB is not available. + /// + /// Invalid format of the PDB or assembly data. + /// The stream returned by or does not support read and seek operations. + /// Error while reading assembly data. + /// + /// If a separate PDB stream is not available ( returns null) opens the PDB embedded in the assembly, if present. + /// + public virtual DebugInformationReaderProvider OpenPdb() + { + var stream = OpenPdbStreamChecked(); + if (stream != null) + { + return DebugInformationReaderProvider.CreateFromStream(stream); + } + + // check for embedded PDB + using (var peReader = new PEReader(OpenAssemblyStreamChecked())) + { + var embeddedPdbEntry = peReader.ReadDebugDirectory().FirstOrDefault(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb); + if (embeddedPdbEntry.DataSize != 0) + { + return DebugInformationReaderProvider.CreateFromMetadataReader(peReader.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry)); + } + } + + return null; + } + + private Stream ValidateStream(Stream stream, string methodName) + { + if (stream != null && (!stream.CanRead || !stream.CanSeek)) + { + throw new InvalidOperationException(string.Format(FeaturesResources.MethodMustReturnStreamThatSupportsReadAndSeek, methodName)); + } + + return stream; + } + + private Stream OpenPdbStreamChecked() + => ValidateStream(OpenPdbStream(), nameof(OpenPdbStream)); + + private Stream OpenAssemblyStreamChecked() + => ValidateStream(OpenAssemblyStream(), nameof(OpenAssemblyStream)); + + /// + /// Opens an assembly file produced by the compiler. + /// + /// + /// The stream must be readable and seekable. + /// + /// New instance or null if the assembly is not available. + protected abstract Stream OpenAssemblyStream(); + + /// + /// Opens a PDB file produced by the compiler. + /// + /// + /// The stream must be readable and seekable. + /// + /// New instance or null if the compiler generated no PDB (the symbols might be embedded in the assembly). + protected abstract Stream OpenPdbStream(); + } +} diff --git a/src/Features/Core/Portable/Emit/ICompilationOutputsProviderService.cs b/src/Features/Core/Portable/Emit/ICompilationOutputsProviderService.cs new file mode 100644 index 000000000000..e350e8c3235c --- /dev/null +++ b/src/Features/Core/Portable/Emit/ICompilationOutputsProviderService.cs @@ -0,0 +1,11 @@ +// 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.Host; + +namespace Microsoft.CodeAnalysis.Emit +{ + internal interface ICompilationOutputsProviderService : IWorkspaceService + { + CompilationOutputs GetCompilationOutputs(ProjectId projectId); + } +} diff --git a/src/Features/Core/Portable/FeaturesResources.Designer.cs b/src/Features/Core/Portable/FeaturesResources.Designer.cs index 811de012ceef..042b47f493e7 100644 --- a/src/Features/Core/Portable/FeaturesResources.Designer.cs +++ b/src/Features/Core/Portable/FeaturesResources.Designer.cs @@ -2397,6 +2397,15 @@ internal static string Method_referenced_implicitly { } } + /// + /// Looks up a localized string similar to {0} must return a stream that supports read and seek operations.. + /// + internal static string MethodMustReturnStreamThatSupportsReadAndSeek { + get { + return ResourceManager.GetString("MethodMustReturnStreamThatSupportsReadAndSeek", resourceCulture); + } + } + /// /// Looks up a localized string similar to Methods. /// @@ -3491,6 +3500,15 @@ internal static string Split_into_nested_0_statements { } } + /// + /// Looks up a localized string similar to Stream must support read and seek operations.. + /// + internal static string StreamMustSupportReadAndSeek { + get { + return ResourceManager.GetString("StreamMustSupportReadAndSeek", resourceCulture); + } + } + /// /// Looks up a localized string similar to Structures. /// diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index a660627c25ce..4da1ef01c3e6 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -1626,4 +1626,10 @@ This version used in: {2} in Source (attribute) + + Stream must support read and seek operations. + + + {0} must return a stream that supports read and seek operations. + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index b01cbb89fdb1..d603975c9e29 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -222,6 +222,11 @@ Merge with previous '{0}' statement + + {0} must return a stream that supports read and seek operations. + {0} must return a stream that supports read and seek operations. + + Move contents to namespace... Move contents to namespace... @@ -347,6 +352,11 @@ Split into nested '{0}' statements + + Stream must support read and seek operations. + Stream must support read and seek operations. + + Target type matches Target type matches diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index a0ef014d3ffa..a139e3dbf9cb 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -222,6 +222,11 @@ Merge with previous '{0}' statement + + {0} must return a stream that supports read and seek operations. + {0} must return a stream that supports read and seek operations. + + Move contents to namespace... Move contents to namespace... @@ -347,6 +352,11 @@ Split into nested '{0}' statements + + Stream must support read and seek operations. + Stream must support read and seek operations. + + Target type matches Target type matches diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index dd1568ec5dbe..7443fabe3306 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -222,6 +222,11 @@ Merge with previous '{0}' statement + + {0} must return a stream that supports read and seek operations. + {0} must return a stream that supports read and seek operations. + + Move contents to namespace... Move contents to namespace... @@ -347,6 +352,11 @@ Split into nested '{0}' statements + + Stream must support read and seek operations. + Stream must support read and seek operations. + + Target type matches Target type matches diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index 147673f74062..60ad55fed15d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -222,6 +222,11 @@ Merge with previous '{0}' statement + + {0} must return a stream that supports read and seek operations. + {0} must return a stream that supports read and seek operations. + + Move contents to namespace... Move contents to namespace... @@ -347,6 +352,11 @@ Split into nested '{0}' statements + + Stream must support read and seek operations. + Stream must support read and seek operations. + + Target type matches Target type matches diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 282b0a21091f..060fedb5efa3 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -222,6 +222,11 @@ Merge with previous '{0}' statement + + {0} must return a stream that supports read and seek operations. + {0} must return a stream that supports read and seek operations. + + Move contents to namespace... Move contents to namespace... @@ -347,6 +352,11 @@ Split into nested '{0}' statements + + Stream must support read and seek operations. + Stream must support read and seek operations. + + Target type matches Target type matches diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 282b6eba4e81..6e6644927b01 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -222,6 +222,11 @@ Merge with previous '{0}' statement + + {0} must return a stream that supports read and seek operations. + {0} must return a stream that supports read and seek operations. + + Move contents to namespace... Move contents to namespace... @@ -347,6 +352,11 @@ Split into nested '{0}' statements + + Stream must support read and seek operations. + Stream must support read and seek operations. + + Target type matches Target type matches diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 093bbfec8461..4cfbc64d5b02 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -222,6 +222,11 @@ Merge with previous '{0}' statement + + {0} must return a stream that supports read and seek operations. + {0} must return a stream that supports read and seek operations. + + Move contents to namespace... Move contents to namespace... @@ -347,6 +352,11 @@ Split into nested '{0}' statements + + Stream must support read and seek operations. + Stream must support read and seek operations. + + Target type matches Target type matches diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index a6584edcd6ce..81c4b76dd9e5 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -222,6 +222,11 @@ Merge with previous '{0}' statement + + {0} must return a stream that supports read and seek operations. + {0} must return a stream that supports read and seek operations. + + Move contents to namespace... Move contents to namespace... @@ -347,6 +352,11 @@ Split into nested '{0}' statements + + Stream must support read and seek operations. + Stream must support read and seek operations. + + Target type matches Target type matches diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index 43e14d413f51..92939e410809 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -222,6 +222,11 @@ Merge with previous '{0}' statement + + {0} must return a stream that supports read and seek operations. + {0} must return a stream that supports read and seek operations. + + Move contents to namespace... Move contents to namespace... @@ -347,6 +352,11 @@ Split into nested '{0}' statements + + Stream must support read and seek operations. + Stream must support read and seek operations. + + Target type matches Target type matches diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index c6a802d7f60d..73dedfb578e7 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -222,6 +222,11 @@ Merge with previous '{0}' statement + + {0} must return a stream that supports read and seek operations. + {0} must return a stream that supports read and seek operations. + + Move contents to namespace... Move contents to namespace... @@ -347,6 +352,11 @@ Split into nested '{0}' statements + + Stream must support read and seek operations. + Stream must support read and seek operations. + + Target type matches Target type matches diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 47da1726dcf4..77a3a784b6e5 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -222,6 +222,11 @@ Merge with previous '{0}' statement + + {0} must return a stream that supports read and seek operations. + {0} must return a stream that supports read and seek operations. + + Move contents to namespace... Move contents to namespace... @@ -347,6 +352,11 @@ Split into nested '{0}' statements + + Stream must support read and seek operations. + Stream must support read and seek operations. + + Target type matches Target type matches diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 6214d8e9f7fc..00006e1d7bd7 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -222,6 +222,11 @@ Merge with previous '{0}' statement + + {0} must return a stream that supports read and seek operations. + {0} must return a stream that supports read and seek operations. + + Move contents to namespace... Move contents to namespace... @@ -347,6 +352,11 @@ Split into nested '{0}' statements + + Stream must support read and seek operations. + Stream must support read and seek operations. + + Target type matches Target type matches diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 206ec6b2ac12..a8b64233b757 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -222,6 +222,11 @@ Merge with previous '{0}' statement + + {0} must return a stream that supports read and seek operations. + {0} must return a stream that supports read and seek operations. + + Move contents to namespace... Move contents to namespace... @@ -347,6 +352,11 @@ Split into nested '{0}' statements + + Stream must support read and seek operations. + Stream must support read and seek operations. + + Target type matches Target type matches diff --git a/src/Test/Utilities/Portable/Compilation/CompilationOutputFiles.cs b/src/Test/Utilities/Portable/Compilation/CompilationOutputFiles.cs deleted file mode 100644 index ecbbca1c634e..000000000000 --- a/src/Test/Utilities/Portable/Compilation/CompilationOutputFiles.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.CodeAnalysis.Test.Utilities -{ - public struct CompilationOutputFiles - { - public readonly string PE; - public readonly string Pdb; - public readonly string XmlDocs; - - public CompilationOutputFiles(string pe, string pdb = null, string xmlDocs = null) - { - PE = pe; - Pdb = pdb; - XmlDocs = xmlDocs; - } - } -} diff --git a/src/Test/Utilities/Portable/Roslyn.Test.Utilities.csproj b/src/Test/Utilities/Portable/Roslyn.Test.Utilities.csproj index be81798f2aaf..fc82db7d3db7 100644 --- a/src/Test/Utilities/Portable/Roslyn.Test.Utilities.csproj +++ b/src/Test/Utilities/Portable/Roslyn.Test.Utilities.csproj @@ -63,6 +63,7 @@ + diff --git a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj index b57dbd839c57..be640d2c7238 100644 --- a/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj +++ b/src/VisualStudio/CSharp/Test/Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.csproj @@ -9,6 +9,7 @@ + diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/AnalyzersTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/AnalyzersTests.cs index 3dbe32a9a58d..97d36fe577d1 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/AnalyzersTests.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/CPS/AnalyzersTests.cs @@ -1,37 +1,31 @@ // 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.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework; using Roslyn.Test.Utilities; using Xunit; -using DisposableFile = Microsoft.CodeAnalysis.Test.Utilities.DisposableFile; namespace Roslyn.VisualStudio.CSharp.UnitTests.ProjectSystemShim.CPS { [UseExportProvider] - public class AnalyzersTests + public class AnalyzersTests : TestBase { [WpfFact] [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] public void RuleSet_GeneralOption_CPS() { - string ruleSetSource = @" + var ruleSetFile = Temp.CreateFile().WriteAllText( +@" -"; - using (var ruleSetFile = new DisposableFile()) +"); using (var environment = new TestEnvironment()) using (var project = CSharpHelpers.CreateCSharpCPSProject(environment, "Test")) { - File.WriteAllText(ruleSetFile.Path, ruleSetSource); - var workspaceProject = environment.Workspace.CurrentSolution.Projects.Single(); var options = (CSharpCompilationOptions)workspaceProject.CompilationOptions; @@ -50,21 +44,20 @@ public void RuleSet_GeneralOption_CPS() [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] public void RuleSet_SpecificOptions_CPS() { - string ruleSetSource = @" + var ruleSetFile = Temp.CreateFile().WriteAllText( +@" -"; +"); - using (var ruleSetFile = new DisposableFile()) using (var environment = new TestEnvironment()) using (var project = CSharpHelpers.CreateCSharpCPSProject(environment, "Test")) { // Verify SetRuleSetFile updates the ruleset. - File.WriteAllText(ruleSetFile.Path, ruleSetSource); project.SetOptions($"/ruleset:{ruleSetFile.Path}"); // We need to explicitly update the command line arguments so the new ruleset is used to update options. @@ -78,7 +71,7 @@ public void RuleSet_SpecificOptions_CPS() [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] public void RuleSet_PathCanBeFound() { - using (var ruleSetFile = new DisposableFile()) + var ruleSetFile = Temp.CreateFile(); using (var environment = new TestEnvironment()) { ProjectId projectId; diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/AnalyzersTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/AnalyzersTests.cs index 9afddc15aaff..4e145dcc5b12 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/AnalyzersTests.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/AnalyzersTests.cs @@ -17,42 +17,20 @@ namespace Roslyn.VisualStudio.CSharp.UnitTests.ProjectSystemShim.LegacyProject { [UseExportProvider] - public class AnalyzersTests + public class AnalyzersTests : TestBase { - private sealed class DisposableFile : IDisposable - { - private readonly string _filePath; - - public DisposableFile() - { - _filePath = System.IO.Path.GetTempFileName(); - } - - public void Dispose() - { - File.Delete(_filePath); - } - - public string Path - { - get { return _filePath; } - } - } - [WpfFact] [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] public void RuleSet_GeneralOption() { - string ruleSetSource = @" + var ruleSetFile = Temp.CreateFile().WriteAllText( +@" -"; - using (var ruleSetFile = new DisposableFile()) +"); using (var environment = new TestEnvironment()) { - File.WriteAllText(ruleSetFile.Path, ruleSetSource); - var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); var options = (CSharpCompilationOptions)environment.GetUpdatedCompilationOptionOfSingleProject(); @@ -67,12 +45,12 @@ public void RuleSet_GeneralOption() } } - [WpfFact] [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] public void RuleSet_CanBeFetchedFromWorkspace() { - using (var ruleSetFile = new DisposableFile()) + var ruleSetFile = Temp.CreateFile(); + using (var environment = new TestEnvironment()) { var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); @@ -88,17 +66,15 @@ public void RuleSet_CanBeFetchedFromWorkspace() [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] public void RuleSet_ProjectSettingOverridesGeneralOption() { - string ruleSetSource = @" + var ruleSetFile = Temp.CreateFile().WriteAllText( +@" -"; +"); - using (var ruleSetFile = new DisposableFile()) using (var environment = new TestEnvironment()) { - File.WriteAllText(ruleSetFile.Path, ruleSetSource); - var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); ((IAnalyzerHost)project).SetRuleSetFile(ruleSetFile.Path); @@ -121,20 +97,18 @@ public void RuleSet_ProjectSettingOverridesGeneralOption() [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] public void RuleSet_SpecificOptions() { - string ruleSetSource = @" + var ruleSetFile = Temp.CreateFile().WriteAllText( +@" -"; +"); - using (var ruleSetFile = new DisposableFile()) using (var environment = new TestEnvironment()) { - File.WriteAllText(ruleSetFile.Path, ruleSetSource); - var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); ((IAnalyzerHost)project).SetRuleSetFile(ruleSetFile.Path); @@ -150,20 +124,17 @@ public void RuleSet_SpecificOptions() [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] public void RuleSet_ProjectSettingsOverrideSpecificOptions() { - string ruleSetSource = @" + var ruleSetFile = Temp.CreateFile().WriteAllText( +@" -"; - - using (var ruleSetFile = new DisposableFile()) +"); using (var environment = new TestEnvironment()) { - File.WriteAllText(ruleSetFile.Path, ruleSetSource); - var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); ((IAnalyzerHost)project).SetRuleSetFile(ruleSetFile.Path); @@ -180,7 +151,7 @@ public void RuleSet_ProjectSettingsOverrideSpecificOptions() [Trait(Traits.Feature, Traits.Features.ProjectSystemShims)] public void SetRuleSetFile_RemoveExtraBackslashes() { - using (var ruleSetFile = new DisposableFile()) + var ruleSetFile = Temp.CreateFile(); using (var environment = new TestEnvironment()) { var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); @@ -202,20 +173,18 @@ public void SetRuleSetFile_RemoveExtraBackslashes() [WorkItem(468, "https://github.com/dotnet/roslyn/issues/468")] public void RuleSet_ProjectSettingsOverrideSpecificOptionsAndRestore() { - string ruleSetSource = @" + var ruleSetFile = Temp.CreateFile().WriteAllText( +@" -"; +"); - using (var ruleSetFile = new DisposableFile()) using (var environment = new TestEnvironment()) { - File.WriteAllText(ruleSetFile.Path, ruleSetSource); - var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); ((IAnalyzerHost)project).SetRuleSetFile(ruleSetFile.Path); @@ -243,20 +212,18 @@ public void RuleSet_ProjectSettingsOverrideSpecificOptionsAndRestore() [WorkItem(468, "https://github.com/dotnet/roslyn/issues/468")] public void RuleSet_ProjectNoWarnOverridesOtherSettings() { - string ruleSetSource = @" + var ruleSetFile = Temp.CreateFile().WriteAllText( +@" -"; +"); - using (var ruleSetFile = new DisposableFile()) using (var environment = new TestEnvironment()) { - File.WriteAllText(ruleSetFile.Path, ruleSetSource); - var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); ((IAnalyzerHost)project).SetRuleSetFile(ruleSetFile.Path); @@ -277,16 +244,16 @@ public void RuleSet_ProjectNoWarnOverridesOtherSettings() [WorkItem(33505, "https://github.com/dotnet/roslyn/pull/33505")] public void RuleSet_FileChangingOnDiskRefreshes(bool useCpsProject) { - string ruleSetSource = @" + string ruleSetSource = +@" "; - using (var ruleSetFile = new DisposableFile()) + + var ruleSetFile = Temp.CreateFile().WriteAllText(ruleSetSource); using (var environment = new TestEnvironment()) { - File.WriteAllText(ruleSetFile.Path, ruleSetSource); - if (useCpsProject) { CSharpHelpers.CreateCSharpCPSProject(environment, "Test", binOutputPath: null, $"/ruleset:\"{ruleSetFile.Path}\""); diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpCompilerOptionsTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpCompilerOptionsTests.cs index 1507a87e9f42..1126ef0edb9d 100644 --- a/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpCompilerOptionsTests.cs +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/LegacyProject/CSharpCompilerOptionsTests.cs @@ -5,6 +5,8 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim.Interop; +using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; +using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Interop; using Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework; using Roslyn.Test.Utilities; using Xunit; @@ -116,20 +118,65 @@ public void ProjectOutputPathAndOutputExeNameChange() project.SetOutputFileName(initialPath); Assert.Equal(initialPath, project.GetOutputFileName()); + var outputs = (CompilationOutputFilesWithImplicitPdbPath)environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id); + Assert.Equal(initialPath, outputs.AssemblyFilePath); + // Change output folder from command line arguments - verify that objOutputPath changes. var newPath = @"C:\NewFolder\test.dll"; project.SetOutputFileName(newPath); Assert.Equal(newPath, project.GetOutputFileName()); + outputs = (CompilationOutputFilesWithImplicitPdbPath)environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id); + Assert.Equal(newPath, outputs.AssemblyFilePath); + // Change output file name - verify that outputPath changes. newPath = @"C:\NewFolder\test2.dll"; project.SetOutputFileName(newPath); Assert.Equal(newPath, project.GetOutputFileName()); + outputs = (CompilationOutputFilesWithImplicitPdbPath)environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id); + Assert.Equal(newPath, outputs.AssemblyFilePath); + // Change output file name and folder - verify that outputPath changes. newPath = @"C:\NewFolder3\test3.dll"; project.SetOutputFileName(newPath); Assert.Equal(newPath, project.GetOutputFileName()); + + outputs = (CompilationOutputFilesWithImplicitPdbPath)environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id); + Assert.Equal(newPath, outputs.AssemblyFilePath); + } + } + + [WpfFact] + public void ProjectCompilationOutputsChange() + { + using (var environment = new TestEnvironment()) + { + var project = CSharpHelpers.CreateCSharpProject(environment, "Test"); + + var outputs = (CompilationOutputFilesWithImplicitPdbPath)environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id); + Assert.Equal(null, outputs.AssemblyFilePath); + + Assert.Equal(0, ((ICompilerOptionsHostObject)project).SetCompilerOptions(@"/pdb:C:\a\1.pdb /debug+", out _)); + + // Compilation doesn't have output file, so we don't expect any build outputs either. + outputs = (CompilationOutputFilesWithImplicitPdbPath)environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id); + Assert.Equal(null, outputs.AssemblyFilePath); + + Assert.Equal(0, ((ICompilerOptionsHostObject)project).SetCompilerOptions(@"/out:C:\a\2.dll /debug+", out _)); + + outputs = (CompilationOutputFilesWithImplicitPdbPath)environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id); + Assert.Equal(@"C:\a\2.dll", outputs.AssemblyFilePath); + + project.SetOutputFileName(@"C:\a\3.dll"); + + outputs = (CompilationOutputFilesWithImplicitPdbPath)environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id); + Assert.Equal(@"C:\a\3.dll", outputs.AssemblyFilePath); + + Assert.Equal(0, ((ICompilerOptionsHostObject)project).SetCompilerOptions(@"/pdb:C:\a\4.pdb /debug+", out _)); + + outputs = (CompilationOutputFilesWithImplicitPdbPath)environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id); + Assert.Equal(@"C:\a\3.dll", outputs.AssemblyFilePath); } } } diff --git a/src/VisualStudio/CSharp/Test/ProjectSystemShim/VisualStudioCompilationOutputFilesTests.cs b/src/VisualStudio/CSharp/Test/ProjectSystemShim/VisualStudioCompilationOutputFilesTests.cs new file mode 100644 index 000000000000..06e9c3729d82 --- /dev/null +++ b/src/VisualStudio/CSharp/Test/ProjectSystemShim/VisualStudioCompilationOutputFilesTests.cs @@ -0,0 +1,63 @@ +// 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.Linq; +using System.Reflection.Metadata.Ecma335; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.UnitTests +{ + public class VisualStudioCompilationOutputFilesTests : TestBase + { + [Fact] + public void OpenStream_Errors() + { + Assert.Throws(() => new CompilationOutputFilesWithImplicitPdbPath(@"a.dll")); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void AssemblyAndPdb(bool exactPdbPath) + { + var dir = Temp.CreateDirectory(); + var dllFile = dir.CreateFile("lib.dll"); + var pdbFile = dir.CreateFile("lib.pdb"); + + var source = @"class C { public static void Main() { int x = 1; } }"; + + var compilation = CSharpTestBase.CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.DebugDll, assemblyName: "lib"); + var pdbStream = new MemoryStream(); + var debugDirPdbPath = exactPdbPath ? pdbFile.Path : "a/y/z/lib.pdb"; + var peImage = compilation.EmitToArray(new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb, pdbFilePath: debugDirPdbPath), pdbStream: pdbStream); + pdbStream.Position = 0; + + dllFile.WriteAllBytes(peImage); + pdbFile.WriteAllBytes(pdbStream.ToArray()); + + var outputs = new CompilationOutputFilesWithImplicitPdbPath(dllFile.Path); + + using (var pdb = outputs.OpenPdb()) + { + var encReader = pdb.CreateEditAndContinueMethodDebugInfoReader(); + Assert.True(encReader.IsPortable); + var localSig = encReader.GetLocalSignature(MetadataTokens.MethodDefinitionHandle(1)); + Assert.Equal(MetadataTokens.StandaloneSignatureHandle(1), localSig); + } + + using (var metadata = outputs.OpenAssemblyMetadata(prefetch: false)) + { + var mdReader = metadata.GetMetadataReader(); + Assert.Equal("lib", mdReader.GetString(mdReader.GetAssemblyDefinition().Name)); + } + + // make sure all files are closed and can be deleted + Directory.Delete(dir.Path, recursive: true); + } + + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CompilationOutputFilesWithImplicitPdbPath.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CompilationOutputFilesWithImplicitPdbPath.cs new file mode 100644 index 000000000000..aaf033858732 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/CompilationOutputFilesWithImplicitPdbPath.cs @@ -0,0 +1,73 @@ +// 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.Linq; +using System.Reflection.PortableExecutable; +using Microsoft.CodeAnalysis.Emit; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +{ + /// + /// Provides access to compilation outputs based only on the path of the output asssembly. + /// If PDB path is known upfront use instead. + /// + internal sealed class CompilationOutputFilesWithImplicitPdbPath : CompilationOutputs + { + public string AssemblyFilePath { get; } + + public CompilationOutputFilesWithImplicitPdbPath(string assemblyFilePath = null) + { + if (assemblyFilePath != null) + { + CompilerPathUtilities.RequireAbsolutePath(assemblyFilePath, nameof(assemblyFilePath)); + } + + AssemblyFilePath = assemblyFilePath; + } + + public override string AssemblyDisplayPath => AssemblyFilePath; + public override string PdbDisplayPath => Path.GetFileNameWithoutExtension(AssemblyFilePath) + ".pdb"; + + protected override Stream OpenAssemblyStream() + => AssemblyFilePath != null ? FileUtilities.OpenRead(AssemblyFilePath) : null; + + protected override Stream OpenPdbStream() + { + var assemblyStream = OpenAssemblyStream(); + if (assemblyStream == null) + { + return null; + } + + // find associated PDB + string pdbPath; + using (var peReader = new PEReader(assemblyStream)) + { + var pdbEntry = peReader.ReadDebugDirectory().FirstOrDefault( + e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb || e.Type == DebugDirectoryEntryType.CodeView); + + if (pdbEntry.Type != DebugDirectoryEntryType.CodeView) + { + return null; + } + + pdbPath = peReader.ReadCodeViewDebugDirectoryData(pdbEntry).Path; + } + + // First try to use the full path as specified in the PDB, then look next to the assembly. + Stream result; + try + { + result = new FileStream(pdbPath, FileMode.Open, FileAccess.Read, FileShare.Read); + } + catch (Exception e) when (e is FileNotFoundException || e is DirectoryNotFoundException) + { + pdbPath = Path.Combine(Path.GetDirectoryName(AssemblyFilePath), PathUtilities.GetFileName(pdbPath)); + result = FileUtilities.OpenRead(pdbPath); + } + + return result; + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs index dcc343fbc650..856d2d25bfd6 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/Legacy/AbstractLegacyProject.cs @@ -31,6 +31,8 @@ internal abstract partial class AbstractLegacyProject : ForegroundThreadAffiniti protected IProjectCodeModel ProjectCodeModel { get; set; } protected VisualStudioWorkspace Workspace { get; } + internal VisualStudioProject Test_VisualStudioProject => VisualStudioProject; + /// /// The path to the directory of the project. Read-only, since although you can rename /// a project in Visual Studio you can't change the folder of a project without an diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioCompilationOutputsProviderService.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioCompilationOutputsProviderService.cs new file mode 100644 index 000000000000..62f8a155fd61 --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioCompilationOutputsProviderService.cs @@ -0,0 +1,20 @@ +// 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; +using Microsoft.CodeAnalysis.Emit; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +{ + internal sealed class VisualStudioCompilationOutputsProviderService : ICompilationOutputsProviderService + { + private readonly VisualStudioWorkspaceImpl _workspace; + + public VisualStudioCompilationOutputsProviderService(VisualStudioWorkspaceImpl workspace) + { + _workspace = workspace; + } + + public CompilationOutputs GetCompilationOutputs(ProjectId projectId) + => _workspace.GetCompilationOutputs(projectId); + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioCompilationOutputsProviderServiceFactory.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioCompilationOutputsProviderServiceFactory.cs new file mode 100644 index 000000000000..8b213d5642ed --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioCompilationOutputsProviderServiceFactory.cs @@ -0,0 +1,24 @@ +// 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.Composition; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +{ + [ExportWorkspaceServiceFactory(typeof(ICompilationOutputsProviderService), ServiceLayer.Host), Shared] + internal sealed class VisualStudioCompilationOutputsProviderServiceFactory : IWorkspaceServiceFactory + { + private readonly VisualStudioWorkspaceImpl _workspace; + + [ImportingConstructor] + public VisualStudioCompilationOutputsProviderServiceFactory(VisualStudioWorkspaceImpl workspace) + { + _workspace = workspace; + } + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + => new VisualStudioCompilationOutputsProviderService(_workspace); + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs index 35d35ca6a088..8bbe0218a2da 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs @@ -233,6 +233,11 @@ internal string IntermediateOutputFilePath get => _intermediateOutputFilePath; set { + // The Project System doesn't always indicate whether we emit PDB, what kind of PDB we emit nor the path of the PDB. + // To work around we look for the PDB on the path specified in the PDB debug directory. + // https://github.com/dotnet/roslyn/issues/35065 + _workspace.SetCompilationOutputs(Id, new CompilationOutputFilesWithImplicitPdbPath(value)); + // Unlike OutputFilePath and OutputRefFilePath, the intermediate output path isn't represented in the workspace anywhere; // thus, we won't mutate the solution. We'll still call ChangeProjectOutputPath so we have the rest of the output path tracking // for any P2P reference conversion. diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs index e814c87e5d65..b2f50d25a9b4 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProjectOptionsProcessor.cs @@ -1,4 +1,6 @@ -using System; +// 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.IO; using Microsoft.CodeAnalysis; @@ -19,7 +21,7 @@ internal class VisualStudioProjectOptionsProcessor : IDisposable /// Gate to guard all mutable fields in this class. /// The lock hierarchy means you are allowed to call out of this class and into while holding the lock. /// - private object _gate = new object(); + private readonly object _gate = new object(); private string _commandLine = ""; private CommandLineArguments _commandLineArgumentsForCommandLine; private string _explicitRuleSetFilePath; diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs index b5f76e2b8f86..9eb6ee32359c 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -11,7 +11,9 @@ using EnvDTE; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -89,6 +91,9 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac private readonly Lazy _projectCodeModelFactory; + private readonly Dictionary _projectCompilationOutputs = new Dictionary(); + private readonly object _projectCompilationOutputsGuard = new object(); + public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServiceProvider asyncServiceProvider) : base(VisualStudioMefHostServices.Create(exportProvider)) { @@ -1484,6 +1489,7 @@ protected internal override void OnProjectRemoved(ProjectId projectId) _projectToHierarchyMap = _projectToHierarchyMap.Remove(projectId); _projectToGuidMap = _projectToGuidMap.Remove(projectId); _projectToRuleSetFilePath.Remove(projectId); + _projectCompilationOutputs.Remove(projectId); foreach (var (projectName, projects) in _projectSystemNameToProjectsMap) { @@ -1813,5 +1819,23 @@ private void RefreshMetadataReferencesForFile(object sender, string fullFilePath changedProjectIds.Free(); } } + + internal void SetCompilationOutputs(ProjectId projectId, CompilationOutputs outputs) + { + Contract.ThrowIfNull(outputs); + + lock (_projectCompilationOutputsGuard) + { + _projectCompilationOutputs[projectId] = outputs; + } + } + + internal CompilationOutputs GetCompilationOutputs(ProjectId projectId) + { + lock (_projectCompilationOutputsGuard) + { + return _projectCompilationOutputs.TryGetValue(projectId, out var outputs) ? outputs : CompilationOutputFiles.None; + } + } } } diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb index f4f95a0e5e91..b1e429989cb0 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualBasicCompilerOptionsTests.vb @@ -1,8 +1,10 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Emit Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.VisualBasicHelpers Imports Roslyn.Test.Utilities @@ -222,6 +224,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim project.SetCompilerOptions(compilerOptions) Assert.Equal("C:\test.dll", project.GetOutputFileName()) + Dim outputs = CType(environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id), CompilationOutputFilesWithImplicitPdbPath) + Assert.Equal("C:\test.dll", outputs.AssemblyFilePath) + ' Change output folder from command line arguments - verify that objOutputPath changes. Dim newPath = "C:\NewFolder\test.dll" compilerOptions = CreateMinimalCompilerOptions(project) @@ -230,6 +235,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim project.SetCompilerOptions(compilerOptions) Assert.Equal(newPath, project.GetOutputFileName()) + outputs = CType(environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id), CompilationOutputFilesWithImplicitPdbPath) + Assert.Equal("C:\NewFolder\test.dll", outputs.AssemblyFilePath) + ' Change output file name - verify that outputPath changes. newPath = "C:\NewFolder\test2.dll" compilerOptions = CreateMinimalCompilerOptions(project) @@ -238,6 +246,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim project.SetCompilerOptions(compilerOptions) Assert.Equal(newPath, project.GetOutputFileName()) + outputs = CType(environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id), CompilationOutputFilesWithImplicitPdbPath) + Assert.Equal("C:\NewFolder\test2.dll", outputs.AssemblyFilePath) + ' Change output file name and folder - verify that outputPath changes. newPath = "C:\NewFolder3\test3.dll" compilerOptions = CreateMinimalCompilerOptions(project) @@ -245,6 +256,18 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim compilerOptions.wszExeName = "test3.dll" project.SetCompilerOptions(compilerOptions) Assert.Equal(newPath, project.GetOutputFileName()) + + outputs = CType(environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id), CompilationOutputFilesWithImplicitPdbPath) + Assert.Equal("C:\NewFolder3\test3.dll", outputs.AssemblyFilePath) + + ' Relative path - set by VBIntelliProj in VB Web App project + compilerOptions = CreateMinimalCompilerOptions(project) + compilerOptions.wszOutputPath = "\" + compilerOptions.wszExeName = "test3.dll" + project.SetCompilerOptions(compilerOptions) + Assert.Equal(Nothing, project.GetOutputFileName()) + outputs = CType(environment.Workspace.GetCompilationOutputs(project.Test_VisualStudioProject.Id), CompilationOutputFilesWithImplicitPdbPath) + Assert.Equal(Nothing, outputs.AssemblyFilePath) End Using End Sub End Class diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb index e85286e141ff..240705b510e6 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb @@ -106,7 +106,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr Public ReadOnly Property ServiceProvider As IServiceProvider Public ReadOnly Property ExportProvider As Composition.ExportProvider - Public ReadOnly Property Workspace As VisualStudioWorkspace + Public ReadOnly Property Workspace As VisualStudioWorkspaceImpl Get Return _workspace End Get diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/VisualBasicHelpers/VisualBasicHelpers.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/VisualBasicHelpers/VisualBasicHelpers.vb index 0561c08595fd..832904493508 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/VisualBasicHelpers/VisualBasicHelpers.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/VisualBasicHelpers/VisualBasicHelpers.vb @@ -18,15 +18,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Vi commandLineParserServiceOpt:=New VisualBasicCommandLineParserService()) End Function - Public Function CreateVisualBasicProjectWithNullBinPath(environment As TestEnvironment, projectName As String) As VisualBasicProject - Return New VisualBasicProject(projectName, - MockCompilerHost.FullFrameworkCompilerHost, - environment.CreateHierarchy(projectName, projectBinPath:=Nothing, projectRefPath:=Nothing, "VB"), - environment.ServiceProvider, - environment.ThreadingContext, - commandLineParserServiceOpt:=New VisualBasicCommandLineParserService()) - End Function - Public Function CreateMinimalCompilerOptions(project As VisualBasicProject) As VBCompilerOptions Dim options As VBCompilerOptions = Nothing options.wszExeName = project.AssemblyName + ".exe" diff --git a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb index 01f8870a5e5e..a16dd9deffb7 100644 --- a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb +++ b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.vb @@ -315,8 +315,16 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim If Not String.IsNullOrEmpty(pCompilerOptions.wszExeName) Then VisualStudioProject.AssemblyName = Path.GetFileNameWithoutExtension(pCompilerOptions.wszExeName) + + ' Some legacy projects (e.g. Venus IntelliSense project) set '\' as the wszOutputPath. + ' /src/venus/project/vb/vbprj/vbintelliproj.cpp + ' Ignore paths that are not absolute. If Not String.IsNullOrEmpty(pCompilerOptions.wszOutputPath) Then - VisualStudioProject.IntermediateOutputFilePath = Path.Combine(pCompilerOptions.wszOutputPath, pCompilerOptions.wszExeName) + If PathUtilities.IsAbsolute(pCompilerOptions.wszOutputPath) Then + VisualStudioProject.IntermediateOutputFilePath = Path.Combine(pCompilerOptions.wszOutputPath, pCompilerOptions.wszExeName) + Else + VisualStudioProject.IntermediateOutputFilePath = Nothing + End If End If End If