Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Make compilation outputs available via a workspace service #34809

Merged
merged 4 commits into from
Apr 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1792,30 +1792,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);
Expand All @@ -1829,6 +1834,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();
Expand All @@ -1841,12 +1847,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();
Expand Down Expand Up @@ -1914,14 +1922,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:"));
Expand Down Expand Up @@ -3312,6 +3329,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);
Expand All @@ -3328,6 +3346,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);
Expand Down
45 changes: 45 additions & 0 deletions src/Compilers/Core/Portable/CommandLine/CommandLineArguments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,51 @@ internal CommandLineArguments()
{
}

/// <summary>
/// Returns a full path of the file that the compiler will generate the assembly to if compilation succeeds.
/// </summary>
/// <remarks>
/// The method takes <paramref name="outputFileName"/> rather than using the value of <see cref="OutputFileName"/>
/// 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 <see cref="OutputFileName"/> to
/// be unspecified.
/// </remarks>
public string GetOutputFilePath(string outputFileName)
{
if (outputFileName == null)
{
throw new ArgumentNullException(nameof(outputFileName));
}

return Path.Combine(OutputDirectory, outputFileName);
}

/// <summary>
/// Returns a full path of the PDB file that the compiler will generate the debug symbols to
/// if <see cref="EmitPdbFile"/> is true and the compilation succeeds.
/// </summary>
/// <remarks>
/// The method takes <paramref name="outputFileName"/> rather than using the value of <see cref="OutputFileName"/>
/// 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 <see cref="OutputFileName"/> to
/// be unspecified.
/// </remarks>
public string GetPdbFilePath(string outputFileName)
{
if (outputFileName == null)
{
throw new ArgumentNullException(nameof(outputFileName));
}

return PdbPath ?? Path.Combine(OutputDirectory, Path.ChangeExtension(outputFileName, ".pdb"));
}

/// <summary>
/// Returns true if the PDB is generated to a PDB file, as opposed to embedded to the output binary and not generated at all.
/// </summary>
public bool EmitPdbFile
=> EmitPdb && EmitOptions.DebugInformationFormat != DebugInformationFormat.Embedded;

#region Metadata References

/// <summary>
Expand Down
8 changes: 3 additions & 5 deletions src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -870,8 +870,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;
Expand Down Expand Up @@ -1023,10 +1023,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;
Expand Down
3 changes: 3 additions & 0 deletions src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ArgumentException>(() => DebugInformationReaderProvider.CreateFromStream(new TestStream(canRead: false, canSeek: true, canWrite: true)));
Assert.Throws<ArgumentException>(() => DebugInformationReaderProvider.CreateFromStream(new TestStream(canRead: true, canSeek: false, canWrite: true)));
Assert.Throws<ArgumentNullException>(() => DebugInformationReaderProvider.CreateFromStream(null));
Assert.Throws<ArgumentNullException>(() => DebugInformationReaderProvider.CreateFromMetadataReader(null));
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -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<ArgumentNullException>(() => EditAndContinueMethodDebugInfoReader.Create((ISymUnmanagedReader5)null));
Assert.Throws<ArgumentNullException>(() => EditAndContinueMethodDebugInfoReader.Create((MetadataReader)null));
Assert.Throws<ArgumentNullException>(() => 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<ISymUnmanagedReader5>().Object;
Assert.Throws<ArgumentOutOfRangeException>(() => EditAndContinueMethodDebugInfoReader.Create(mockSymReader, 0));
Assert.Throws<ArgumentOutOfRangeException>(() => 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();
Expand All @@ -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<ISymUnmanagedReader5>(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());
Expand Down
55 changes: 55 additions & 0 deletions src/EditorFeatures/Test/Emit/CompilationOutputFilesTests.cs
Original file line number Diff line number Diff line change
@@ -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<ArgumentException>(() => new CompilationOutputFiles(@"a.dll"));
Assert.Throws<ArgumentException>(() => 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);
Copy link
Member

Choose a reason for hiding this comment

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

Don't we have a TempDirectory helper that you can dispose to clean these up?

Copy link
Member Author

Choose a reason for hiding this comment

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

We do, but it catches any exceptions and ignores failures. I want to validate that the files can be deleted (are not open).

Copy link
Member

@jasonmalinowski jasonmalinowski Apr 17, 2019

Choose a reason for hiding this comment

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

Add a comment then to that effect, or otherwise I could totally imagine somebody (i.e. me) cleaning that up and not knowing intent. #Resolved

Copy link
Member

Choose a reason for hiding this comment

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

@tmat: did this not get added?

}
}
}
Loading