未验证 提交 c64d1e3a 编写于 作者: M msftbot[bot] 提交者: GitHub

Merge pull request #48053 from dotnet/merges/release/dev16.8-to-master

Merge release/dev16.8 to master
......@@ -4771,6 +4771,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
-moduleassemblyname:<string> Name of the assembly which this module will be
a part of
-modulename:<string> Specify the name of the source module
-generatedfilesout:<dir> Place files generated during compilation in the
specified directory.
</value>
<comment>Visual C# Compiler Options</comment>
</data>
......
......@@ -79,6 +79,7 @@ public new CSharpCommandLineArguments Parse(IEnumerable<string> args, string? ba
string? outputFileName = null;
string? outputRefFilePath = null;
bool refOnly = false;
string? generatedFilesOutputDirectory = null;
string? documentationPath = null;
ErrorLogOptions? errorLogOptions = null;
bool parseDocumentationComments = false; //Don't just null check documentationFileName because we want to do this even if the file name is invalid.
......@@ -615,6 +616,17 @@ public new CSharpCommandLineArguments Parse(IEnumerable<string> args, string? ba
}
continue;
case "generatedfilesout":
if (string.IsNullOrWhiteSpace(value))
{
AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_Text.Localize(), arg);
}
else
{
generatedFilesOutputDirectory = ParseGenericPathToFile(value, diagnostics, baseDirectory);
}
continue;
case "doc":
parseDocumentationComments = true;
if (RoslynString.IsNullOrEmpty(value))
......@@ -1489,6 +1501,7 @@ public new CSharpCommandLineArguments Parse(IEnumerable<string> args, string? ba
RuleSetPath = ruleSetPath,
OutputDirectory = outputDirectory!, // error produced when null
DocumentationPath = documentationPath,
GeneratedFilesOutputDirectory = generatedFilesOutputDirectory,
ErrorLogOptions = errorLogOptions,
AppConfigPath = appConfigPath,
SourceFiles = sourceFiles.AsImmutable(),
......
......@@ -5797,9 +5797,8 @@ private ImmutableArray<BoundExpression> GetArgumentsForMethodTypeInference(Immut
{
var visitArgumentResult = argumentResults[i];
var lambdaState = visitArgumentResult.StateForLambda;
var argumentResult = visitArgumentResult.LValueType;
if (!argumentResult.HasType)
argumentResult = visitArgumentResult.RValueType.ToTypeWithAnnotations(compilation);
// Note: for `out` arguments, the argument result contains the declaration type (see `VisitArgumentEvaluate`)
var argumentResult = visitArgumentResult.RValueType.ToTypeWithAnnotations(compilation);
builder.Add(getArgumentForMethodTypeInference(arguments[i], argumentResult, lambdaState));
}
return builder.ToImmutableAndFree();
......
......@@ -1221,6 +1221,8 @@
-moduleassemblyname:&lt;string&gt; Name of the assembly which this module will be
a part of
-modulename:&lt;string&gt; Specify the name of the source module
-generatedfilesout:&lt;dir&gt; Place files generated during compilation in the
specified directory.
</source>
<target state="needs-review-translation">
Parametry kompilátoru Visual C#
......
......@@ -1221,6 +1221,8 @@
-moduleassemblyname:&lt;string&gt; Name of the assembly which this module will be
a part of
-modulename:&lt;string&gt; Specify the name of the source module
-generatedfilesout:&lt;dir&gt; Place files generated during compilation in the
specified directory.
</source>
<target state="needs-review-translation">
Visual C#-Compileroptionen
......
......@@ -1221,6 +1221,8 @@
-moduleassemblyname:&lt;string&gt; Name of the assembly which this module will be
a part of
-modulename:&lt;string&gt; Specify the name of the source module
-generatedfilesout:&lt;dir&gt; Place files generated during compilation in the
specified directory.
</source>
<target state="needs-review-translation">
Opciones del compilador de Visual C#
......
......@@ -1221,6 +1221,8 @@
-moduleassemblyname:&lt;string&gt; Name of the assembly which this module will be
a part of
-modulename:&lt;string&gt; Specify the name of the source module
-generatedfilesout:&lt;dir&gt; Place files generated during compilation in the
specified directory.
</source>
<target state="needs-review-translation">
Options du compilateur Visual C#
......
......@@ -1221,6 +1221,8 @@
-moduleassemblyname:&lt;string&gt; Name of the assembly which this module will be
a part of
-modulename:&lt;string&gt; Specify the name of the source module
-generatedfilesout:&lt;dir&gt; Place files generated during compilation in the
specified directory.
</source>
<target state="needs-review-translation">
Opzioni del compilatore Visual C#
......
......@@ -1221,6 +1221,8 @@
-moduleassemblyname:&lt;string&gt; Name of the assembly which this module will be
a part of
-modulename:&lt;string&gt; Specify the name of the source module
-generatedfilesout:&lt;dir&gt; Place files generated during compilation in the
specified directory.
</source>
<target state="needs-review-translation">
Visual C# コンパイラのオプション
......
......@@ -1221,6 +1221,8 @@
-moduleassemblyname:&lt;string&gt; Name of the assembly which this module will be
a part of
-modulename:&lt;string&gt; Specify the name of the source module
-generatedfilesout:&lt;dir&gt; Place files generated during compilation in the
specified directory.
</source>
<target state="needs-review-translation">
Visual C# 컴파일러 옵션
......
......@@ -1221,6 +1221,8 @@
-moduleassemblyname:&lt;string&gt; Name of the assembly which this module will be
a part of
-modulename:&lt;string&gt; Specify the name of the source module
-generatedfilesout:&lt;dir&gt; Place files generated during compilation in the
specified directory.
</source>
<target state="needs-review-translation">
Opcje kompilatora Visual C#
......
......@@ -1221,6 +1221,8 @@
-moduleassemblyname:&lt;string&gt; Name of the assembly which this module will be
a part of
-modulename:&lt;string&gt; Specify the name of the source module
-generatedfilesout:&lt;dir&gt; Place files generated during compilation in the
specified directory.
</source>
<target state="needs-review-translation">
Opções do Compilador do Visual C#
......
......@@ -1221,6 +1221,8 @@
-moduleassemblyname:&lt;string&gt; Name of the assembly which this module will be
a part of
-modulename:&lt;string&gt; Specify the name of the source module
-generatedfilesout:&lt;dir&gt; Place files generated during compilation in the
specified directory.
</source>
<target state="needs-review-translation">
Параметры компилятора Visual C#
......
......@@ -1221,6 +1221,8 @@
-moduleassemblyname:&lt;string&gt; Name of the assembly which this module will be
a part of
-modulename:&lt;string&gt; Specify the name of the source module
-generatedfilesout:&lt;dir&gt; Place files generated during compilation in the
specified directory.
</source>
<target state="needs-review-translation">
Visual C# Derleyici Seçenekleri
......
......@@ -1221,6 +1221,8 @@
-moduleassemblyname:&lt;string&gt; Name of the assembly which this module will be
a part of
-modulename:&lt;string&gt; Specify the name of the source module
-generatedfilesout:&lt;dir&gt; Place files generated during compilation in the
specified directory.
</source>
<target state="needs-review-translation">
Visual C# 编译器选项
......
......@@ -1221,6 +1221,8 @@
-moduleassemblyname:&lt;string&gt; Name of the assembly which this module will be
a part of
-modulename:&lt;string&gt; Specify the name of the source module
-generatedfilesout:&lt;dir&gt; Place files generated during compilation in the
specified directory.
</source>
<target state="needs-review-translation">
Visual C# 編譯器選項
......
......@@ -2476,6 +2476,24 @@ private static void ValidateEmbeddedSources_Windows(Dictionary<string, string> e
}
}
private static void ValidateWrittenSources(Dictionary<string, Dictionary<string, string>> expectedFilesMap, Encoding encoding = null)
{
foreach ((var dirPath, var fileMap) in expectedFilesMap.ToArray())
{
foreach (var file in Directory.GetFiles(dirPath))
{
var name = Path.GetFileName(file);
var content = File.ReadAllText(file, encoding ?? Encoding.UTF8);
Assert.Equal(fileMap[name], content);
Assert.True(fileMap.Remove(name));
}
Assert.Empty(fileMap);
Assert.True(expectedFilesMap.Remove(dirPath));
}
Assert.Empty(expectedFilesMap);
}
[Fact]
public void Optimize()
{
......@@ -12343,11 +12361,12 @@ class C
VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/debug:embedded", "/out:embed.exe" }, generators: new[] { generator }, analyzers: null);
var generatorPrefix = $"{generator.GetType().Module.ModuleVersionId}_{generator.GetType().FullName}";
ValidateEmbeddedSources_Portable(new Dictionary<string, string> { { Path.Combine(dir.Path, $"{generatorPrefix}_generatedSource.cs"), generatedSource } }, dir, true);
var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator);
ValidateEmbeddedSources_Portable(new Dictionary<string, string> { { Path.Combine(dir.Path, generatorPrefix, $"generatedSource.cs"), generatedSource } }, dir, true);
// Clean up temp files
CleanupAllGeneratedFiles(src.Path);
Directory.Delete(dir.Path, true);
}
[Theory, CombinatorialData]
......@@ -12382,8 +12401,8 @@ class C
generators: new[] { generator });
// Verify source generator was executed, regardless of the value of 'skipAnalyzers'.
var generatorPrefix = $"{generator.GetType().Module.ModuleVersionId}_{generator.GetType().FullName}";
ValidateEmbeddedSources_Portable(new Dictionary<string, string> { { Path.Combine(dir.Path, $"{generatorPrefix}_generatedSource.cs"), generatedSource } }, dir, true);
var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator);
ValidateEmbeddedSources_Portable(new Dictionary<string, string> { { Path.Combine(dir.Path, generatorPrefix, "generatedSource.cs"), generatedSource } }, dir, true);
if (expectedAnalyzerExecution)
{
......@@ -12417,17 +12436,210 @@ class C
VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/debug:embedded", "/out:embed.exe" }, generators: new[] { generator, generator2 }, analyzers: null);
var generator1Prefix = $"{generator.GetType().Module.ModuleVersionId}_{generator.GetType().FullName}";
var generator2Prefix = $"{generator2.GetType().Module.ModuleVersionId}_{generator2.GetType().FullName}";
var generator1Prefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator);
var generator2Prefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator2);
ValidateEmbeddedSources_Portable(new Dictionary<string, string>
{
{ $"{dir.Path}{Path.DirectorySeparatorChar}{generator1Prefix}_{source1Name}", source1},
{ $"{dir.Path}{Path.DirectorySeparatorChar}{generator2Prefix}_{source2Name}", source2},
{ Path.Combine(dir.Path, generator1Prefix, source1Name), source1},
{ Path.Combine(dir.Path, generator2Prefix, source2Name), source2},
}, dir, true);
// Clean up temp files
CleanupAllGeneratedFiles(src.Path);
Directory.Delete(dir.Path, true);
}
[Fact]
public void SourceGenerators_WriteGeneratedSources()
{
var dir = Temp.CreateDirectory();
var src = dir.CreateFile("temp.cs").WriteAllText(@"
class C
{
}");
var generatedDir = dir.CreateDirectory("generated");
var generatedSource = "public class D { }";
var generator = new SingleFileTestGenerator(generatedSource, "generatedSource.cs");
VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedfilesout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, generators: new[] { generator }, analyzers: null);
var generatorPrefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator);
ValidateWrittenSources(new() { { Path.Combine(generatedDir.Path, generatorPrefix), new() { { "generatedSource.cs", generatedSource } } } });
// Clean up temp files
CleanupAllGeneratedFiles(src.Path);
Directory.Delete(dir.Path, true);
}
[Theory]
[InlineData("partial class D {}", "file1.cs", "partial class E {}", "file2.cs")] // different files, different names
[InlineData("partial class D {}", "file1.cs", "partial class E {}", "file1.cs")] // different files, same names
[InlineData("partial class D {}", "file1.cs", "partial class D {}", "file2.cs")] // same files, different names
[InlineData("partial class D {}", "file1.cs", "partial class D {}", "file1.cs")] // same files, same names
[InlineData("partial class D {}", "file1.cs", "", "file2.cs")] // empty second file
public void SourceGenerators_WriteGeneratedSources_MultipleFiles(string source1, string source1Name, string source2, string source2Name)
{
var dir = Temp.CreateDirectory();
var src = dir.CreateFile("temp.cs").WriteAllText(@"
class C
{
}");
var generatedDir = dir.CreateDirectory("generated");
var generator = new SingleFileTestGenerator(source1, source1Name);
var generator2 = new SingleFileTestGenerator2(source2, source2Name);
VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedfilesout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, generators: new[] { generator, generator2 }, analyzers: null);
var generator1Prefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator);
var generator2Prefix = GeneratorDriver.GetFilePathPrefixForGenerator(generator2);
ValidateWrittenSources(new()
{
{ Path.Combine(generatedDir.Path, generator1Prefix), new() { { source1Name, source1 } } },
{ Path.Combine(generatedDir.Path, generator2Prefix), new() { { source2Name, source2 } } }
});
// Clean up temp files
CleanupAllGeneratedFiles(src.Path);
Directory.Delete(dir.Path, true);
}
[ConditionalFact(typeof(DesktopClrOnly))] //CoreCLR doesn't support SxS loading
[WorkItem(47990, "https://github.com/dotnet/roslyn/issues/47990")]
public void SourceGenerators_SxS_AssemblyLoading()
{
// compile the generators
var dir = Temp.CreateDirectory();
var snk = Temp.CreateFile("TestKeyPair_", ".snk", dir.Path).WriteAllBytes(TestResources.General.snKey);
var src = dir.CreateFile("generator.cs");
var virtualSnProvider = new DesktopStrongNameProvider(ImmutableArray.Create(dir.Path));
string createGenerator(string version)
{
var generatorSource = $@"
using Microsoft.CodeAnalysis;
[assembly:System.Reflection.AssemblyVersion(""{version}"")]
[Generator]
public class TestGenerator : ISourceGenerator
{{
public void Execute(GeneratorExecutionContext context) {{ context.AddSource(""generatedSource.cs"", ""//from version {version}""); }}
public void Initialize(GeneratorInitializationContext context) {{ }}
}}";
var path = Path.Combine(dir.Path, Guid.NewGuid().ToString() + ".dll");
var comp = CreateEmptyCompilation(source: generatorSource,
references: TargetFrameworkUtil.NetStandard20References.Add(MetadataReference.CreateFromAssemblyInternal(typeof(ISourceGenerator).Assembly)),
options: TestOptions.DebugDll.WithCryptoKeyFile(Path.GetFileName(snk.Path)).WithStrongNameProvider(virtualSnProvider),
assemblyName: "generator");
comp.VerifyDiagnostics();
comp.Emit(path);
return path;
}
var gen1 = createGenerator("1.0.0.0");
var gen2 = createGenerator("2.0.0.0");
var generatedDir = dir.CreateDirectory("generated");
VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedfilesout:" + generatedDir.Path, "/analyzer:" + gen1, "/analyzer:" + gen2 }.ToArray());
// This is wrong! Both generators are writing the same file out, over the top of each other
// See https://github.com/dotnet/roslyn/issues/47990
ValidateWrittenSources(new()
{
// { Path.Combine(generatedDir.Path, "generator", "TestGenerator"), new() { { "generatedSource.cs", "//from version 1.0.0.0" } } },
{ Path.Combine(generatedDir.Path, "generator", "TestGenerator"), new() { { "generatedSource.cs", "//from version 2.0.0.0" } } }
});
}
[Fact]
public void SourceGenerators_DoNotWriteGeneratedSources_When_No_Directory_Supplied()
{
var dir = Temp.CreateDirectory();
var src = dir.CreateFile("temp.cs").WriteAllText(@"
class C
{
}");
var generatedDir = dir.CreateDirectory("generated");
var generatedSource = "public class D { }";
var generator = new SingleFileTestGenerator(generatedSource, "generatedSource.cs");
VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/langversion:preview", "/out:embed.exe" }, generators: new[] { generator }, analyzers: null);
ValidateWrittenSources(new() { { generatedDir.Path, new() } });
// Clean up temp files
CleanupAllGeneratedFiles(src.Path);
Directory.Delete(dir.Path, true);
}
[Fact]
public void SourceGenerators_Error_When_GeneratedDir_NotExist()
{
var dir = Temp.CreateDirectory();
var src = dir.CreateFile("temp.cs").WriteAllText(@"
class C
{
}");
var generatedDirPath = Path.Combine(dir.Path, "noexist");
var generatedSource = "public class D { }";
var generator = new SingleFileTestGenerator(generatedSource, "generatedSource.cs");
var output = VerifyOutput(dir, src, expectedErrorCount: 1, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedfilesout:" + generatedDirPath, "/langversion:preview", "/out:embed.exe" }, generators: new[] { generator }, analyzers: null);
Assert.Contains("CS0016:", output);
// Clean up temp files
CleanupAllGeneratedFiles(src.Path);
Directory.Delete(dir.Path, true);
}
[Fact]
public void SourceGenerators_Error_When_NoDirectoryArgumentGiven()
{
var dir = Temp.CreateDirectory();
var src = dir.CreateFile("temp.cs").WriteAllText(@"
class C
{
}");
var output = VerifyOutput(dir, src, expectedErrorCount: 2, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedfilesout:", "/langversion:preview", "/out:embed.exe" });
Assert.Contains("error CS2006: Command-line syntax error: Missing '<text>' for '/generatedfilesout:' option", output);
// Clean up temp files
CleanupAllGeneratedFiles(src.Path);
Directory.Delete(dir.Path, true);
}
[Fact]
public void SourceGenerators_ReportedWrittenFiles_To_TouchedFilesLogger()
{
var dir = Temp.CreateDirectory();
var src = dir.CreateFile("temp.cs").WriteAllText(@"
class C
{
}");
var generatedDir = dir.CreateDirectory("generated");
var generatedSource = "public class D { }";
var generator = new SingleFileTestGenerator(generatedSource, "generatedSource.cs");
VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedfilesout:" + generatedDir.Path, $"/touchedfiles:{dir.Path}/touched", "/langversion:preview", "/out:embed.exe" }, generators: new[] { generator }, analyzers: null);
var touchedFiles = Directory.GetFiles(dir.Path, "touched*");
Assert.Equal(2, touchedFiles.Length);
string[] writtenText = File.ReadAllLines(Path.Combine(dir.Path, "touched.write"));
Assert.Equal(2, writtenText.Length);
Assert.EndsWith("EMBED.EXE", writtenText[0], StringComparison.OrdinalIgnoreCase);
Assert.EndsWith("GENERATEDSOURCE.CS", writtenText[1], StringComparison.OrdinalIgnoreCase);
// Clean up temp files
CleanupAllGeneratedFiles(src.Path);
Directory.Delete(dir.Path, true);
}
[Fact]
......
......@@ -720,8 +720,8 @@ class C { }
var filePaths = outputCompilation.SyntaxTrees.Skip(1).Select(t => t.FilePath).ToArray();
Assert.Equal(new[] {
$"{generator.GetType().Module.ModuleVersionId}_{generator.GetType().FullName}_source.cs",
$"{generator2.GetType().Module.ModuleVersionId}_{generator2.GetType().FullName}_source.cs"
Path.Combine(generator.GetType().Assembly.GetName().Name!, generator.GetType().FullName!, "source.cs"),
Path.Combine(generator2.GetType().Assembly.GetName().Name!, generator2.GetType().FullName!, "source.cs")
}, filePaths);
}
......
......@@ -293,10 +293,10 @@ public void AutoNoGet()
var comp = CreateCompilation(text, parseOptions: TestOptions.Regular);
comp.VerifyDiagnostics(
// (4,20): error CS8034: Auto-implemented properties must have get accessors.
// (4,20): error CS8051: Auto-implemented properties must have get accessors.
// public int Q { set; } = 0;
Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, "set").WithArguments("C.Q.set").WithLocation(4, 20),
// (5,20): error CS8034: Auto-implemented properties must have get accessors.
// (5,20): error CS8051: Auto-implemented properties must have get accessors.
// public int R { set; }
Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, "set").WithArguments("C.R.set").WithLocation(5, 20));
}
......
......@@ -86,6 +86,12 @@ public bool ErrorEndLocation
get { return (string?)_store[nameof(ErrorReport)]; }
}
public string? GeneratedFilesOutputPath
{
set { _store[nameof(GeneratedFilesOutputPath)] = value; }
get { return (string?)_store[nameof(GeneratedFilesOutputPath)]; }
}
public bool GenerateFullPaths
{
set { _store[nameof(GenerateFullPaths)] = value; }
......@@ -205,6 +211,7 @@ protected internal override void AddResponseFileCommands(CommandLineBuilderExten
commandLine.AppendPlusOrMinusSwitch("/unsafe", _store, nameof(AllowUnsafeBlocks));
commandLine.AppendPlusOrMinusSwitch("/checked", _store, nameof(CheckForOverflowUnderflow));
commandLine.AppendSwitchWithSplitting("/nowarn:", DisabledWarnings, ",", ';', ',');
commandLine.AppendSwitchIfNotNull("/generatedfilesout:", GeneratedFilesOutputPath);
commandLine.AppendWhenTrue("/fullpaths", _store, nameof(GenerateFullPaths));
commandLine.AppendSwitchIfNotNull("/moduleassemblyname:", ModuleAssemblyName);
commandLine.AppendSwitchIfNotNull("/pdb:", PdbFile);
......
......@@ -96,6 +96,7 @@
ErrorReport="$(ErrorReport)"
Features="$(Features)"
FileAlignment="$(FileAlignment)"
GeneratedFilesOutputPath="$(CompilerGeneratedFilesOutputPath)"
GenerateFullPaths="$(GenerateFullPaths)"
HighEntropyVA="$(HighEntropyVA)"
Instrument="$(Instrument)"
......
......@@ -26,7 +26,7 @@
<Target Name="_BeforeVBCSCoreCompile"
DependsOnTargets="ShimReferencePathsWhenCommonTargetsDoesNotUnderstandReferenceAssemblies">
<ItemGroup Condition="'$(TargetingClr2Framework)' == 'true'">
<ReferencePathWithRefAssemblies>
<EmbedInteropTypes />
......@@ -259,12 +259,42 @@
If a user requests that any @(AdditionalFiles) items are copied to the output directory
we add them to the @(None) group to ensure they will be copied.
-->
<Target Name="CopyAdditionalFiles"
BeforeTargets="AssignTargetPaths">
<Target Name="CopyAdditionalFiles"
BeforeTargets="AssignTargetPaths">
<ItemGroup>
<None Include="@(AdditionalFiles)" Condition="'%(AdditionalFiles.CopyToOutputDirectory)' != ''" />
</ItemGroup>
</Target>
<!--
========================
CompilerGeneratedFilesOutputPath
========================
Controls output of generated files.
CompilerGeneratedFilesOutputPath controls the location the files will be output to.
The compiler will not emit any generated files when the path is empty, and defaults to a /generated directory in $(IntermediateOutputPath) if $(IntermediateOutputPath) has a value.
EmitCompilerGeneratedFiles allows the user to control if anything is emitted by clearing the property when not true.
When EmitCompilerGeneratedFiles is true, we ensure that CompilerGeneatedFilesOutputPath has a value and issue a warning if not.
We will create CompilerGeneratedFilesOutputPath if it does not exist.
-->
<PropertyGroup>
<EmitCompilerGeneratedFiles Condition="'$(EmitCompilerGeneratedFiles)' == ''">false</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath Condition="'$(EmitCompilerGeneratedFiles)' != 'true'"></CompilerGeneratedFilesOutputPath>
<CompilerGeneratedFilesOutputPath Condition="'$(EmitCompilerGeneratedFiles)' == 'true' and '$(CompilerGeneratedFilesOutputPath)' == '' and '$(IntermediateOutputPath)' != ''">$(IntermediateOutputPath)/generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<Target Name="CreateCompilerGeneratedFilesOutputPath"
BeforeTargets="CoreCompile"
Condition="'$(EmitCompilerGeneratedFiles)' == 'true' and !('$(DesignTimeBuild)' == 'true' OR '$(BuildingProject)' != 'true')">
<Warning Condition="'$(CompilerGeneratedFilesOutputPath)' == ''"
Text="EmitCompilerGeneratedFiles was true, but no CompilerGeneratedFilesOutputPath was provided. CompilerGeneratedFilesOutputPath must be set in order to emit generated files." />
<MakeDir Condition="'$(CompilerGeneratedFilesOutputPath)' != ''"
Directories="$(CompilerGeneratedFilesOutputPath)" />
</Target>
</Project>
\ No newline at end of file
......@@ -5,7 +5,7 @@
#nullable enable
// uncomment the below define to dump binlogs of each test
// #define DUMP_MSBUILD_BIN_LOG
//#define DUMP_MSBUILD_BIN_LOG
using System;
......@@ -569,6 +569,107 @@ public void AdditionalFilesAreAddedToNoneWhenCopied()
Assert.Equal("Never", noneItems[2].GetMetadataValue("CopyToOutputDirectory"));
}
[Fact]
public void GeneratedFilesOutputPathHasDefaults()
{
XmlReader xmlReader = XmlReader.Create(new StringReader($@"
<Project>
<Import Project=""Microsoft.Managed.Core.targets"" />
</Project>
"));
var instance = CreateProjectInstance(xmlReader);
var emit = instance.GetPropertyValue("EmitCompilerGeneratedFiles");
var dir = instance.GetPropertyValue("CompilerGeneratedFilesOutputPath");
Assert.Equal("false", emit);
Assert.Equal(string.Empty, dir);
}
[Fact]
public void GeneratedFilesOutputPathDefaultsToIntermediateOutputPathWhenSet()
{
XmlReader xmlReader = XmlReader.Create(new StringReader($@"
<Project>
<PropertyGroup>
<IntermediateOutputPath>fallbackDirectory</IntermediateOutputPath>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
<Import Project=""Microsoft.Managed.Core.targets"" />
</Project>
"));
var instance = CreateProjectInstance(xmlReader);
bool runSuccess = instance.Build(target: "CreateCompilerGeneratedFilesOutputPath", GetTestLoggers());
Assert.True(runSuccess);
var emit = instance.GetPropertyValue("EmitCompilerGeneratedFiles");
var dir = instance.GetPropertyValue("CompilerGeneratedFilesOutputPath");
Assert.Equal("true", emit);
Assert.Equal("fallbackDirectory/generated", dir);
}
[Fact]
public void GeneratedFilesOutputPathDefaultsIsEmptyWhenEmitDisable()
{
XmlReader xmlReader = XmlReader.Create(new StringReader($@"
<Project>
<PropertyGroup>
<EmitCompilerGeneratedFiles>false</EmitCompilerGeneratedFiles>
<IntermediateOutputPath>fallbackDirectory</IntermediateOutputPath>
</PropertyGroup>
<Import Project=""Microsoft.Managed.Core.targets"" />
</Project>
"));
var instance = CreateProjectInstance(xmlReader);
var emit = instance.GetPropertyValue("EmitCompilerGeneratedFiles");
var dir = instance.GetPropertyValue("CompilerGeneratedFilesOutputPath");
Assert.Equal("false", emit);
Assert.Equal(string.Empty, dir);
}
[Theory]
[InlineData(true, "generatedDirectory")]
[InlineData(true, null)]
[InlineData(false, "generatedDirectory")]
[InlineData(false, null)]
public void GeneratedFilesOutputPathCanBeSetAndSuppressed(bool emitGeneratedFiles, string? generatedFilesDir)
{
XmlReader xmlReader = XmlReader.Create(new StringReader($@"
<Project>
<PropertyGroup>
<EmitCompilerGeneratedFiles>{(emitGeneratedFiles.ToString().ToLower())}</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>{generatedFilesDir}</CompilerGeneratedFilesOutputPath>
<IntermediateOutputPath>fallbackDirectory</IntermediateOutputPath>
</PropertyGroup>
<Import Project=""Microsoft.Managed.Core.targets"" />
</Project>
"));
var instance = CreateProjectInstance(xmlReader);
bool runSuccess = instance.Build(target: "CreateCompilerGeneratedFilesOutputPath", GetTestLoggers());
Assert.True(runSuccess);
var emit = instance.GetPropertyValue("EmitCompilerGeneratedFiles");
var dir = instance.GetPropertyValue("CompilerGeneratedFilesOutputPath");
Assert.Equal(emitGeneratedFiles.ToString().ToLower(), emit);
if (emitGeneratedFiles)
{
string expectedDir = generatedFilesDir ?? "fallbackDirectory/generated";
Assert.Equal(expectedDir, dir);
}
else
{
Assert.Equal(string.Empty, dir);
}
}
[Theory, CombinatorialData]
[WorkItem(40926, "https://github.com/dotnet/roslyn/issues/40926")]
public void TestSkipAnalyzers(
......@@ -616,7 +717,7 @@ static string getPropertyGroup(string propertyName, bool? propertyValue)
}
}
private ProjectInstance CreateProjectInstance(XmlReader reader)
private static ProjectInstance CreateProjectInstance(XmlReader reader)
{
Project proj = new Project(reader);
......@@ -624,25 +725,43 @@ private ProjectInstance CreateProjectInstance(XmlReader reader)
proj.Xml.AddTarget("PrepareForBuild");
// create a dummy WriteLinesToFile task
var usingTask = proj.Xml.AddUsingTask("WriteLinesToFile", string.Empty, Assembly.GetExecutingAssembly().FullName);
usingTask.TaskFactory = nameof(DummyTaskFactory);
var taskParams = usingTask.AddParameterGroup();
taskParams.AddParameter("Lines", "", "", "System.String[]");
taskParams.AddParameter("File", "", "", "System.String");
taskParams.AddParameter("Overwrite", "", "", "System.Boolean");
taskParams.AddParameter("WriteOnlyWhenDifferent", "", "", "System.Boolean");
addTask(proj, "WriteLinesToFile", new()
{
{ "Lines", "System.String[]" },
{ "File", "System.String" },
{ "Overwrite", "System.Boolean" },
{ "WriteOnlyWhenDifferent", "System.Boolean" }
});
// dummy makeDir task
addTask(proj, "MakeDir", new()
{
{ "Directories", "System.String[]" }
});
// create an instance and return it
return proj.CreateProjectInstance();
static void addTask(Project proj, string taskName, Dictionary<string, string> parameters)
{
var task = proj.Xml.AddUsingTask(taskName, string.Empty, Assembly.GetExecutingAssembly().FullName);
task.TaskFactory = nameof(DummyTaskFactory);
var taskParams = task.AddParameterGroup();
foreach (var kvp in parameters)
{
taskParams.AddParameter(kvp.Key, string.Empty, string.Empty, kvp.Value);
}
}
}
private ILogger[] GetTestLoggers([CallerMemberName] string callerName = "")
private static ILogger[] GetTestLoggers([CallerMemberName] string callerName = "")
{
#if DUMP_MSBUILD_BIN_LOG
return new ILogger[]
{
new BinaryLogger()
new Build.Logging.BinaryLogger()
{
Parameters = callerName + ".binlog"
}
......
......@@ -125,6 +125,11 @@ public abstract class CommandLineArguments
/// </summary>
public string? DocumentationPath { get; internal set; }
/// <summary>
/// Absolute path of the directory to place generated files in, or <c>null</c> to not emit any generated files.
/// </summary>
public string? GeneratedFilesOutputDirectory { get; internal set; }
/// <summary>
/// Options controlling the generation of a SARIF log file containing compilation or
/// analysis diagnostics, or null if no log file is desired.
......
......@@ -963,17 +963,64 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat
// We pass it to the generators, which will realize any symbols they require.
compilation = RunGenerators(compilation, Arguments.ParseOptions, generators, analyzerConfigProvider, additionalTextFiles, diagnostics);
var generatedSyntaxTrees = compilation.SyntaxTrees.Skip(Arguments.SourceFiles.Length);
if (Arguments.AnalyzerConfigPaths.Length > 0)
bool hasAnalyzerConfigs = !Arguments.AnalyzerConfigPaths.IsEmpty;
bool hasGeneratedOutputPath = !string.IsNullOrWhiteSpace(Arguments.GeneratedFilesOutputDirectory);
var generatedSyntaxTrees = compilation.SyntaxTrees.Skip(Arguments.SourceFiles.Length).ToList();
var analyzerOptionsBuilder = hasAnalyzerConfigs ? ArrayBuilder<AnalyzerConfigOptionsResult>.GetInstance(generatedSyntaxTrees.Count) : null;
var embeddedTextBuilder = ArrayBuilder<EmbeddedText>.GetInstance(generatedSyntaxTrees.Count);
try
{
var generatedSourceFileAnalyzerConfigOptions = generatedSyntaxTrees.SelectAsArray(f => analyzerConfigSet.GetOptionsForSourcePath(f.FilePath));
analyzerConfigProvider = UpdateAnalyzerConfigOptionsProvider(
analyzerConfigProvider,
generatedSyntaxTrees,
generatedSourceFileAnalyzerConfigOptions);
}
foreach (var tree in generatedSyntaxTrees)
{
Debug.Assert(!string.IsNullOrWhiteSpace(tree.FilePath));
cancellationToken.ThrowIfCancellationRequested();
var sourceText = tree.GetText(cancellationToken);
// embed the generated text and get analyzer options for it if needed
embeddedTextBuilder.Add(EmbeddedText.FromSource(tree.FilePath, sourceText));
if (hasAnalyzerConfigs)
{
analyzerOptionsBuilder.Add(analyzerConfigSet.GetOptionsForSourcePath(tree.FilePath));
}
// write out the file if we have an output path
if (hasGeneratedOutputPath)
{
var path = Path.Combine(Arguments.GeneratedFilesOutputDirectory, tree.FilePath);
if (Directory.Exists(Arguments.GeneratedFilesOutputDirectory))
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
embeddedTexts = embeddedTexts.AddRange(generatedSyntaxTrees.Select(t => EmbeddedText.FromSource(t.FilePath, t.GetText())));
var fileStream = OpenFile(path, diagnostics, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete);
if (fileStream is object)
{
using var disposer = new NoThrowStreamDisposer(fileStream, path, diagnostics, MessageProvider);
using var writer = new StreamWriter(fileStream, tree.Encoding);
sourceText.Write(writer, cancellationToken);
touchedFilesLogger?.AddWritten(path);
}
}
}
embeddedTexts = embeddedTexts.AddRange(embeddedTextBuilder);
if (hasAnalyzerConfigs)
{
analyzerConfigProvider = UpdateAnalyzerConfigOptionsProvider(
analyzerConfigProvider,
generatedSyntaxTrees,
analyzerOptionsBuilder.ToImmutable());
}
}
finally
{
analyzerOptionsBuilder?.Free();
embeddedTextBuilder.Free();
}
}
AnalyzerOptions analyzerOptions = CreateAnalyzerOptions(
......
*REMOVED*Microsoft.CodeAnalysis.SpecialType.Count = 44 -> Microsoft.CodeAnalysis.SpecialType
*REMOVED*Microsoft.CodeAnalysis.Compilation.CreateFunctionPointerTypeSymbol(Microsoft.CodeAnalysis.ITypeSymbol returnType, Microsoft.CodeAnalysis.RefKind returnRefKind, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.ITypeSymbol> parameterTypes, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.RefKind> parameterRefKinds) -> Microsoft.CodeAnalysis.IFunctionPointerTypeSymbol
Microsoft.CodeAnalysis.CommandLineArguments.GeneratedFilesOutputDirectory.get -> string
Microsoft.CodeAnalysis.Compilation.CreateFunctionPointerTypeSymbol(Microsoft.CodeAnalysis.ITypeSymbol returnType, Microsoft.CodeAnalysis.RefKind returnRefKind, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.ITypeSymbol> parameterTypes, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.RefKind> parameterRefKinds, System.Reflection.Metadata.SignatureCallingConvention callingConvention = System.Reflection.Metadata.SignatureCallingConvention.Default, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.INamedTypeSymbol> callingConventionTypes = default(System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.INamedTypeSymbol>)) -> Microsoft.CodeAnalysis.IFunctionPointerTypeSymbol
Microsoft.CodeAnalysis.CommandLineArguments.SkipAnalyzers.get -> bool
Microsoft.CodeAnalysis.AnalyzerConfigSet.GlobalConfigOptions.get -> Microsoft.CodeAnalysis.AnalyzerConfigOptionsResult
......
......@@ -6,6 +6,7 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Diagnostics;
......@@ -335,10 +336,10 @@ private ImmutableArray<SyntaxTree> ParseAdditionalSources(ISourceGenerator gener
{
var trees = ArrayBuilder<SyntaxTree>.GetInstance(generatedSources.Length);
var type = generator.GetType();
var prefix = $"{type.Module.ModuleVersionId}_{type.FullName}";
var prefix = GetFilePathPrefixForGenerator(generator);
foreach (var source in generatedSources)
{
trees.Add(ParseGeneratedSourceText(source, $"{prefix}_{source.HintName}", cancellationToken));
trees.Add(ParseGeneratedSourceText(source, Path.Combine(prefix, source.HintName), cancellationToken));
}
return trees.ToImmutableAndFree();
}
......@@ -384,6 +385,12 @@ private static GeneratorState SetGeneratorException(CommonMessageProvider provid
return new GeneratorState(generatorState.Info, e, diagnostic);
}
internal static string GetFilePathPrefixForGenerator(ISourceGenerator generator)
{
var type = generator.GetType();
return Path.Combine(type.Assembly.GetName().Name ?? string.Empty, type.FullName!);
}
internal abstract CommonMessageProvider MessageProvider { get; }
internal abstract GeneratorDriver FromState(GeneratorDriverState state);
......
......@@ -102,7 +102,7 @@ public void NavigateToSourceGeneratedFile(Project project, ISourceGenerator gene
// The file name we generate here is chosen to match the compiler's choice, so the debugger can recognize the files should match.
// This can only be changed if the compiler changes the algorithm as well.
var temporaryFilePath = Path.Combine(projectDirectory, $"{generatorType.Module.ModuleVersionId}_{generatorType.FullName}_{generatedSourceHintName}");
var temporaryFilePath = Path.Combine(projectDirectory, generatorType.Assembly.GetName().Name ?? string.Empty, generatorType.FullName, generatedSourceHintName);
// Don't write to the file if it's already there, as that potentially triggers a file reload
if (!File.Exists(temporaryFilePath))
......@@ -135,35 +135,40 @@ public void NavigateToSourceGeneratedFile(Project project, ISourceGenerator gene
string filePath,
[NotNullWhen(true)] out ProjectId? projectId,
[NotNullWhen(true)] out string? generatorTypeName,
[NotNullWhen(true)] out string? generatorAssemblyName,
[NotNullWhen(true)] out string? generatedSourceHintName)
{
if (!filePath.StartsWith(_temporaryDirectory))
{
projectId = null;
generatorTypeName = null;
generatorAssemblyName = null;
generatedSourceHintName = null;
return false;
}
string fileName = Path.GetFileName(filePath);
string[] parts = fileName.Split(new[] { '_' }, count: 3);
var fileInfo = new FileInfo(filePath);
var generatorDir = fileInfo.Directory;
var assemblyDir = generatorDir.Parent;
var projectDir = assemblyDir.Parent;
generatorTypeName = parts[1];
generatedSourceHintName = parts[2];
generatorTypeName = generatorDir.Name;
generatorAssemblyName = assemblyDir.Name;
generatedSourceHintName = fileInfo.Name;
projectId = ProjectId.CreateFromSerialized(Guid.Parse(new FileInfo(filePath).Directory.Name));
projectId = ProjectId.CreateFromSerialized(Guid.Parse(projectDir.Name));
return true;
}
void IRunningDocumentTableEventListener.OnOpenDocument(string moniker, ITextBuffer textBuffer, IVsHierarchy? hierarchy, IVsWindowFrame? windowFrame)
{
if (TryParseGeneratedFilePath(moniker, out var projectId, out var generatorTypeName, out var generatedSourceHintName))
if (TryParseGeneratedFilePath(moniker, out var projectId, out var generatorTypeName, out var generatorAssemblyName, out var generatedSourceHintName))
{
// Attach to the text buffer if we haven't already
if (!_openFiles.TryGetValue(moniker, out OpenSourceGeneratedFile openFile))
{
openFile = new OpenSourceGeneratedFile(this, textBuffer, _visualStudioWorkspace, projectId, generatorTypeName, generatedSourceHintName, _threadingContext);
openFile = new OpenSourceGeneratedFile(this, textBuffer, _visualStudioWorkspace, projectId, generatorTypeName, generatorAssemblyName, generatedSourceHintName, _threadingContext);
_openFiles.Add(moniker, openFile);
_threadingContext.JoinableTaskFactory.Run(() => openFile.UpdateBufferContentsAsync(CancellationToken.None));
......@@ -203,6 +208,7 @@ private class OpenSourceGeneratedFile : ForegroundThreadAffinitizedObject, IDisp
private readonly Workspace _workspace;
private readonly ProjectId _projectId;
private readonly string _generatorTypeName;
private readonly string _generatorAssemblyName;
private readonly string _generatedSourceHintName;
/// <summary>
......@@ -235,7 +241,7 @@ private class OpenSourceGeneratedFile : ForegroundThreadAffinitizedObject, IDisp
private ImageMoniker _currentWindowFrameImageMoniker = default;
private IVsInfoBarUIElement? _currentWindowFrameInfoBarElement = null;
public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuffer textBuffer, Workspace workspace, ProjectId projectId, string generatorTypeName, string generatedSourceHintName, IThreadingContext threadingContext)
public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuffer textBuffer, Workspace workspace, ProjectId projectId, string generatorTypeName, string generatorAssemblyName, string generatedSourceHintName, IThreadingContext threadingContext)
: base(threadingContext, assertIsForeground: true)
{
_fileManager = fileManager;
......@@ -243,6 +249,7 @@ public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuff
_workspace = workspace;
_projectId = projectId;
_generatorTypeName = generatorTypeName;
_generatorAssemblyName = generatorAssemblyName;
_generatedSourceHintName = generatedSourceHintName;
// We'll create a read-only region for the file, but it'll be a dynamic region we can temporarily suspend
......@@ -300,7 +307,9 @@ public async Task UpdateBufferContentsAsync(CancellationToken cancellationToken)
{
var generatorDriverRunResults = await project.GetGeneratorDriverRunResultAsync(cancellationToken).ConfigureAwait(false);
var generatorRunResult = generatorDriverRunResults?.Results.SingleOrNull(r => r.Generator.GetType().FullName == _generatorTypeName);
var generatorRunResult = generatorDriverRunResults?.Results.SingleOrNull(r =>
r.Generator.GetType().FullName.Equals(_generatorTypeName, StringComparison.OrdinalIgnoreCase) &&
r.Generator.GetType().Assembly.GetName().Name.Equals(_generatorAssemblyName));
if (generatorRunResult == null)
{
......
......@@ -61,7 +61,7 @@ private static Project AddEmptyProject(Solution solution)
Assert.NotSame(originalCompilation, newCompilation);
var generatedTree = Assert.Single(newCompilation.SyntaxTrees);
Assert.Equal($"{typeof(GenerateFileForEachAdditionalFileWithContentsCommented).Module.ModuleVersionId}_{typeof(GenerateFileForEachAdditionalFileWithContentsCommented).FullName}_Test.generated.cs", Path.GetFileName(generatedTree.FilePath));
Assert.Equal("Microsoft.CodeAnalysis.Workspaces.UnitTests\\Microsoft.CodeAnalysis.UnitTests.SolutionWithSourceGeneratorTests+GenerateFileForEachAdditionalFileWithContentsCommented\\Test.generated.cs", generatedTree.FilePath);
}
[Fact]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册