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

Produce ref assemblies from command-line and msbuild #17558

Merged
merged 14 commits into from
Mar 21, 2017
18 changes: 18 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2288,6 +2288,12 @@ If such a class is used as a base class and if the deriving class defines a dest
<data name="ERR_Merge_conflict_marker_encountered" xml:space="preserve">
<value>Merge conflict marker encountered</value>
</data>
<data name="ERR_NoRefOutWhenRefOnly" xml:space="preserve">
<value>Do not use refout when using refonly.</value>
</data>
<data name="ERR_NoNetModuleOutputWhenRefOutOrRefOnly" xml:space="preserve">
<value>Cannot compile net modules when using /refout or /refonly.</value>
</data>
<data name="ERR_OvlOperatorExpected" xml:space="preserve">
<value>Overloadable operator expected</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable<string> ar
string outputDirectory = baseDirectory;
ImmutableArray<KeyValuePair<string, string>> pathMap = ImmutableArray<KeyValuePair<string, string>>.Empty;
string outputFileName = null;
string outputRefFilePath = null;
bool metadataOnly = false;
string documentationPath = null;
string errorLogPath = null;
bool parseDocumentationComments = false; //Don't just null check documentationFileName because we want to do this even if the file name is invalid.
Expand Down Expand Up @@ -379,6 +381,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable<string> ar
}

continue;

case "out":
if (string.IsNullOrWhiteSpace(value))
{
Expand All @@ -391,6 +394,26 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable<string> ar

continue;

case "refout":
value = RemoveQuotesAndSlashes(value);
if (string.IsNullOrEmpty(value))
{
AddDiagnostic(diagnostics, ErrorCode.ERR_NoFileSpec, arg);
}
else
{
outputRefFilePath = ParseGenericPathToFile(value, diagnostics, baseDirectory);
}

continue;

case "refonly":
if (value != null)
break;
Copy link
Member

Choose a reason for hiding this comment

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

Are we testing /refonly:value?


metadataOnly = true;
continue;

case "t":
case "target":
if (value == null)
Expand Down Expand Up @@ -1141,6 +1164,16 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable<string> ar
diagnosticOptions[o.Key] = o.Value;
}

if (metadataOnly && outputRefFilePath != null)
{
AddDiagnostic(diagnostics, diagnosticOptions, ErrorCode.ERR_NoRefOutWhenRefOnly);
}

if (outputKind == OutputKind.NetModule && (metadataOnly || outputRefFilePath != null))
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 20, 2017

Choose a reason for hiding this comment

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

if (outputKind == OutputKind.NetModule && (metadataOnly || outputRefFilePath != null)) [](start = 12, length = 86)

I would expect this condition to be intercepted on the API level as well. #Closed

{
AddDiagnostic(diagnostics, diagnosticOptions, ErrorCode.ERR_NoNetModuleOutputWhenRefOutOrRefOnly);
}

if (!IsScriptRunner && !sourceFilesSpecified && (outputKind.IsNetModule() || !resourcesOrModulesSpecified))
{
AddDiagnostic(diagnostics, diagnosticOptions, ErrorCode.WRN_NoSources);
Expand Down Expand Up @@ -1257,7 +1290,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable<string> ar

var emitOptions = new EmitOptions
(
metadataOnly: false,
metadataOnly: metadataOnly,
debugInformationFormat: debugInformationFormat,
pdbFilePath: null, // to be determined later
outputNameOverride: null, // to be determined later
Expand All @@ -1282,8 +1315,9 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable<string> ar
Utf8Output = utf8output,
CompilationName = compilationName,
OutputFileName = outputFileName,
OutputRefFilePath = outputRefFilePath,
PdbPath = pdbPath,
EmitPdb = emitPdb,
EmitPdb = emitPdb && !metadataOnly,
Copy link
Member

Choose a reason for hiding this comment

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

emitPdb && !metadataOnly [](start = 26, length = 24)

Code above checks the value of emitPdb. So if we are gonna change it here the check above won't take the metadataOnly flag into account.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's correct and that's by design. The idea is that you can just add /refonly on a command-line and it will work, without having to remove the parameters related to emitting PDBs. For instance, you may have source links and we won't complain about it (one of the checks above).
Does that seem ok?

Copy link
Member

Choose a reason for hiding this comment

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

OK. I'd then add a comment.

SourceLink = sourceLink,
OutputDirectory = outputDirectory,
DocumentationPath = documentationPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2358,8 +2358,6 @@ internal override bool GenerateResourcesAndDocumentationComments(
DiagnosticBag diagnostics,
CancellationToken cancellationToken)
{
Debug.Assert(!moduleBuilder.EmitOptions.EmitMetadataOnly);

// Use a temporary bag so we don't have to refilter pre-existing diagnostics.
var resourceDiagnostics = DiagnosticBag.GetInstance();

Expand Down
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1460,5 +1460,7 @@ internal enum ErrorCode
#endregion more stragglers for C# 7

ERR_Merge_conflict_marker_encountered = 8300,
ERR_NoRefOutWhenRefOnly = 8301,
ERR_NoNetModuleOutputWhenRefOutOrRefOnly = 8302,
}
}
186 changes: 186 additions & 0 deletions src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2784,6 +2784,34 @@ public void ParseOut()
// error CS2005: Missing file specification for '/out:' option
Diagnostic(ErrorCode.ERR_NoFileSpec).WithArguments("/out:"));

parsedArgs = DefaultParse(new[] { @"/refout:", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify(
// error CS2005: Missing file specification for '/refout:' option
Diagnostic(ErrorCode.ERR_NoFileSpec).WithArguments("/refout:"));

parsedArgs = DefaultParse(new[] { @"/refout:ref.dll", "/refonly", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify(
// error CS8301: Do not use refout when using refonly.
Diagnostic(ErrorCode.ERR_NoRefOutWhenRefOnly).WithLocation(1, 1));

parsedArgs = DefaultParse(new[] { "/refonly:incorrect", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify(
// error CS2007: Unrecognized option: '/refonly:incorrect'
Diagnostic(ErrorCode.ERR_BadSwitch).WithArguments("/refonly:incorrect").WithLocation(1, 1)
);

parsedArgs = DefaultParse(new[] { @"/refout:ref.dll", "/target:module", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify(
// error CS8302: Cannot compile net modules when using /refout or /refonly.
Diagnostic(ErrorCode.ERR_NoNetModuleOutputWhenRefOutOrRefOnly).WithLocation(1, 1)
);

parsedArgs = DefaultParse(new[] { @"/refonly", "/target:module", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify(
// error CS8302: Cannot compile net modules when using /refout or /refonly.
Diagnostic(ErrorCode.ERR_NoNetModuleOutputWhenRefOutOrRefOnly).WithLocation(1, 1)
);

// Dev11 reports CS2007: Unrecognized option: '/out'
parsedArgs = DefaultParse(new[] { @"/out", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify(
Expand Down Expand Up @@ -8845,6 +8873,164 @@ public void Version()
}
}

[Fact]
public void RefOut()
{
var dir = Temp.CreateDirectory();
dir.CreateDirectory("ref");

var src = dir.CreateFile("a.cs");
src.WriteAllText(@"
class C
{
/// <summary>Main method</summary>
public static void Main()
{
System.Console.Write(""Hello"");
}
}");

var outWriter = new StringWriter(CultureInfo.InvariantCulture);
var csc = new MockCSharpCompiler(null, dir.Path,
new[] { "/nologo", "/out:a.exe", "/refout:ref/a.dll", "/doc:doc.xml", "/deterministic", "a.cs" });

int exitCode = csc.Run(outWriter);
Assert.Equal(0, exitCode);

var exe = Path.Combine(dir.Path, "a.exe");
Assert.True(File.Exists(exe));

MetadataReaderUtils.VerifyPEMetadata(exe,
new[] { "TypeDef:<Module>", "TypeDef:C" },
new[] { "MethodDef: Void Main()", "MethodDef: Void .ctor()" },
new[] { "CompilationRelaxationsAttribute", "RuntimeCompatibilityAttribute", "DebuggableAttribute" }
);

var doc = Path.Combine(dir.Path, "doc.xml");
Assert.True(File.Exists(doc));

var content = File.ReadAllText(doc);
var expectedDoc =
@"<?xml version=""1.0""?>
<doc>
<assembly>
<name>a</name>
</assembly>
<members>
<member name=""M:C.Main"">
<summary>Main method</summary>
</member>
</members>
</doc>";
Assert.Equal(expectedDoc, content.Trim());

var output = ProcessUtilities.RunAndGetOutput(exe, startFolder: dir.Path);
Assert.Equal("Hello", output.Trim());

var refDll = Path.Combine(dir.Path, Path.Combine("ref", "a.dll"));
Assert.True(File.Exists(refDll));

// The types and members that are included needs further refinement.
// ReferenceAssemblyAttribute is missing.
// See issue https://github.com/dotnet/roslyn/issues/17612
MetadataReaderUtils.VerifyPEMetadata(refDll,
new[] { "TypeDef:<Module>", "TypeDef:C" },
new[] { "MethodDef: Void Main()", "MethodDef: Void .ctor()" },
new[] { "CompilationRelaxationsAttribute", "RuntimeCompatibilityAttribute", "DebuggableAttribute" }
);

output = ProcessUtilities.RunAndGetOutput(refDll, startFolder: dir.ToString(), expectedRetCode: -532462766);

// Clean up temp files
CleanupAllGeneratedFiles(dir.Path);
Copy link
Member

@cston cston Mar 16, 2017

Choose a reason for hiding this comment

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

Are the FileStream instances closed? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. I wasn't disposing stream above (File.OpenRead(refDll)) so was probably leaking files.
Thanks


In reply to: 106547188 [](ancestors = 106547188)

}

[Fact]
public void RefOutWithError()
{
var dir = Temp.CreateDirectory();
dir.CreateDirectory("ref");

var src = dir.CreateFile("a.cs");
src.WriteAllText(@"class C { public static void Main() { error(); } }");

var outWriter = new StringWriter(CultureInfo.InvariantCulture);
var csc = new MockCSharpCompiler(null, dir.Path,
new[] { "/nologo", "/out:a.dll", "/refout:ref/a.dll", "/deterministic", "a.cs" });
int exitCode = csc.Run(outWriter);
Assert.Equal(1, exitCode);

var dll = Path.Combine(dir.Path, "a.dll");
Assert.False(File.Exists(dll));

var refDll = Path.Combine(dir.Path, Path.Combine("ref", "a.dll"));
Assert.False(File.Exists(refDll));

Assert.Equal("a.cs(1,39): error CS0103: The name 'error' does not exist in the current context", outWriter.ToString().Trim());

// Clean up temp files
CleanupAllGeneratedFiles(dir.Path);
}

[Fact]
public void RefOnly()
{
var dir = Temp.CreateDirectory();

var src = dir.CreateFile("a.cs");
src.WriteAllText(@"
class C
{
/// <summary>Main method</summary>
public static void Main()
{
error(); // semantic error in method body
}
}");

var outWriter = new StringWriter(CultureInfo.InvariantCulture);
var csc = new MockCSharpCompiler(null, dir.Path,
new[] { "/nologo", "/out:a.dll", "/refonly", "/debug", "/deterministic", "/doc:doc.xml", "a.cs" });
int exitCode = csc.Run(outWriter);
Assert.Equal(0, exitCode);

var refDll = Path.Combine(dir.Path, "a.dll");
Assert.True(File.Exists(refDll));

// The types and members that are included needs further refinement.
// ReferenceAssemblyAttribute is missing.
// See issue https://github.com/dotnet/roslyn/issues/17612
MetadataReaderUtils.VerifyPEMetadata(refDll,
new[] { "TypeDef:<Module>", "TypeDef:C" },
new[] { "MethodDef: Void Main()", "MethodDef: Void .ctor()" },
new[] { "CompilationRelaxationsAttribute", "RuntimeCompatibilityAttribute", "DebuggableAttribute" }
);

var pdb = Path.Combine(dir.Path, "a.pdb");
Assert.False(File.Exists(pdb));

var doc = Path.Combine(dir.Path, "doc.xml");
Assert.True(File.Exists(doc));

var content = File.ReadAllText(doc);
var expectedDoc =
@"<?xml version=""1.0""?>
<doc>
<assembly>
<name>a</name>
</assembly>
<members>
<member name=""M:C.Main"">
<summary>Main method</summary>
</member>
</members>
</doc>";
Assert.Equal(expectedDoc, content.Trim());

// Clean up temp files
CleanupAllGeneratedFiles(dir.Path);
}

public class QuotedArgumentTests
{
private void VerifyQuotedValid<T>(string name, string value, T expected, Func<CSharpCommandLineArguments, T> getValue)
Expand Down
Loading