|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal abstract class CSharpCompiler : CommonCompiler
{
internal const string ResponseFileName = "csc.rsp";
private readonly CommandLineDiagnosticFormatter _diagnosticFormatter;
private readonly string? _tempDirectory;
protected CSharpCompiler(CSharpCommandLineParser parser, string? responseFile, string[] args, BuildPaths buildPaths, string? additionalReferenceDirectories, IAnalyzerAssemblyLoader assemblyLoader, GeneratorDriverCache? driverCache = null, ICommonCompilerFileSystem? fileSystem = null)
: base(parser, responseFile, args, buildPaths, additionalReferenceDirectories, assemblyLoader, driverCache, fileSystem)
{
_diagnosticFormatter = new CommandLineDiagnosticFormatter(buildPaths.WorkingDirectory, Arguments.PrintFullPaths, Arguments.ShouldIncludeErrorEndLocation);
_tempDirectory = buildPaths.TempDirectory;
}
public override DiagnosticFormatter DiagnosticFormatter { get { return _diagnosticFormatter; } }
protected internal new CSharpCommandLineArguments Arguments { get { return (CSharpCommandLineArguments)base.Arguments; } }
public override Compilation? CreateCompilation(
TextWriter consoleOutput,
TouchedFileLogger? touchedFilesLogger,
ErrorLogger? errorLogger,
ImmutableArray<AnalyzerConfigOptionsResult> analyzerConfigOptions,
AnalyzerConfigOptionsResult globalConfigOptions)
{
var parseOptions = Arguments.ParseOptions;
// We compute script parse options once so we don't have to do it repeatedly in
// case there are many script files.
var scriptParseOptions = parseOptions.WithKind(SourceCodeKind.Script);
bool hadErrors = false;
var sourceFiles = Arguments.SourceFiles;
var trees = new SyntaxTree?[sourceFiles.Length];
var normalizedFilePaths = new string?[sourceFiles.Length];
var diagnosticBag = DiagnosticBag.GetInstance();
if (Arguments.CompilationOptions.ConcurrentBuild)
{
RoslynParallel.For(
0,
sourceFiles.Length,
UICultureUtilities.WithCurrentUICulture<int>(i =>
{
//NOTE: order of trees is important!!
trees[i] = ParseFile(
parseOptions,
scriptParseOptions,
ref hadErrors,
sourceFiles[i],
diagnosticBag,
out normalizedFilePaths[i]);
}),
CancellationToken.None);
}
else
{
for (int i = 0; i < sourceFiles.Length; i++)
{
//NOTE: order of trees is important!!
trees[i] = ParseFile(
parseOptions,
scriptParseOptions,
ref hadErrors,
sourceFiles[i],
diagnosticBag,
out normalizedFilePaths[i]);
}
}
// If errors had been reported in ParseFile, while trying to read files, then we should simply exit.
if (ReportDiagnostics(diagnosticBag.ToReadOnlyAndFree(), consoleOutput, errorLogger, compilation: null))
{
Debug.Assert(hadErrors);
return null;
}
var diagnostics = new List<DiagnosticInfo>();
var uniqueFilePaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < sourceFiles.Length; i++)
{
var normalizedFilePath = normalizedFilePaths[i];
Debug.Assert(normalizedFilePath != null);
Debug.Assert(sourceFiles[i].IsInputRedirected || PathUtilities.IsAbsolute(normalizedFilePath));
if (!uniqueFilePaths.Add(normalizedFilePath))
{
// warning CS2002: Source file '{0}' specified multiple times
diagnostics.Add(new DiagnosticInfo(MessageProvider, (int)ErrorCode.WRN_FileAlreadyIncluded,
Arguments.PrintFullPaths ? normalizedFilePath : _diagnosticFormatter.RelativizeNormalizedPath(normalizedFilePath)));
trees[i] = null;
}
}
if (Arguments.TouchedFilesPath != null)
{
Debug.Assert(touchedFilesLogger is object);
foreach (var path in uniqueFilePaths)
{
touchedFilesLogger.AddRead(path);
}
}
var assemblyIdentityComparer = DesktopAssemblyIdentityComparer.Default;
var appConfigPath = this.Arguments.AppConfigPath;
if (appConfigPath != null)
{
try
{
using (var appConfigStream = new FileStream(appConfigPath, FileMode.Open, FileAccess.Read))
{
assemblyIdentityComparer = DesktopAssemblyIdentityComparer.LoadFromXml(appConfigStream);
}
if (touchedFilesLogger != null)
{
touchedFilesLogger.AddRead(appConfigPath);
}
}
catch (Exception ex)
{
diagnostics.Add(new DiagnosticInfo(MessageProvider, (int)ErrorCode.ERR_CantReadConfigFile, appConfigPath, ex.Message));
}
}
var xmlFileResolver = new LoggingXmlFileResolver(Arguments.BaseDirectory, touchedFilesLogger);
var sourceFileResolver = new LoggingSourceFileResolver(ImmutableArray<string>.Empty, Arguments.BaseDirectory, Arguments.PathMap, touchedFilesLogger);
MetadataReferenceResolver referenceDirectiveResolver;
var resolvedReferences = ResolveMetadataReferences(diagnostics, touchedFilesLogger, out referenceDirectiveResolver);
if (ReportDiagnostics(diagnostics, consoleOutput, errorLogger, compilation: null))
{
return null;
}
var loggingFileSystem = new LoggingStrongNameFileSystem(touchedFilesLogger, _tempDirectory);
var optionsProvider = new CompilerSyntaxTreeOptionsProvider(trees, analyzerConfigOptions, globalConfigOptions);
return CSharpCompilation.Create(
Arguments.CompilationName,
trees.WhereNotNull(),
resolvedReferences,
Arguments.CompilationOptions
.WithMetadataReferenceResolver(referenceDirectiveResolver)
.WithAssemblyIdentityComparer(assemblyIdentityComparer)
.WithXmlReferenceResolver(xmlFileResolver)
.WithStrongNameProvider(Arguments.GetStrongNameProvider(loggingFileSystem))
.WithSourceReferenceResolver(sourceFileResolver)
.WithSyntaxTreeOptionsProvider(optionsProvider));
}
private SyntaxTree? ParseFile(
CSharpParseOptions parseOptions,
CSharpParseOptions scriptParseOptions,
ref bool addedDiagnostics,
CommandLineSourceFile file,
DiagnosticBag diagnostics,
out string? normalizedFilePath)
{
var fileDiagnostics = new List<DiagnosticInfo>();
var content = TryReadFileContent(file, fileDiagnostics, out normalizedFilePath);
if (content == null)
{
foreach (var info in fileDiagnostics)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(info));
}
fileDiagnostics.Clear();
addedDiagnostics = true;
return null;
}
else
{
Debug.Assert(fileDiagnostics.Count == 0);
return ParseFile(parseOptions, scriptParseOptions, content, file);
}
}
private static SyntaxTree ParseFile(
CSharpParseOptions parseOptions,
CSharpParseOptions scriptParseOptions,
SourceText content,
CommandLineSourceFile file)
{
var tree = SyntaxFactory.ParseSyntaxTree(
content,
file.IsScript ? scriptParseOptions : parseOptions,
file.Path);
// prepopulate line tables.
// we will need line tables anyways and it is better to not wait until we are in emit
// where things run sequentially.
bool isHiddenDummy;
tree.GetMappedLineSpanAndVisibility(default(TextSpan), out isHiddenDummy);
return tree;
}
/// <summary>
/// Given a compilation and a destination directory, determine three names:
/// 1) The name with which the assembly should be output.
/// 2) The path of the assembly/module file.
/// 3) The path of the pdb file.
///
/// When csc produces an executable, but the name of the resulting assembly
/// is not specified using the "/out" switch, the name is taken from the name
/// of the file (note: file, not class) containing the assembly entrypoint
/// (as determined by binding and the "/main" switch).
///
/// For example, if the command is "csc /target:exe a.cs b.cs" and b.cs contains the
/// entrypoint, then csc will produce "b.exe" and "b.pdb" in the output directory,
/// with assembly name "b" and module name "b.exe" embedded in the file.
/// </summary>
protected override string GetOutputFileName(Compilation compilation, CancellationToken cancellationToken)
{
if (Arguments.OutputFileName is object)
{
return Arguments.OutputFileName;
}
Debug.Assert(Arguments.CompilationOptions.OutputKind.IsApplication());
var comp = (CSharpCompilation)compilation;
Symbol? entryPoint = comp.ScriptClass;
if (entryPoint is null)
{
var method = comp.GetEntryPoint(cancellationToken);
if (method is object)
{
entryPoint = method.PartialImplementationPart ?? method;
}
else
{
// no entrypoint found - an error will be reported and the compilation won't be emitted
return "error";
}
}
string entryPointFileName = PathUtilities.GetFileName(entryPoint.GetFirstLocation().SourceTree!.FilePath);
return Path.ChangeExtension(entryPointFileName, ".exe");
}
internal override bool SuppressDefaultResponseFile(IEnumerable<string> args)
{
return args.Any(arg => new[] { "/noconfig", "-noconfig" }.Contains(arg.ToLowerInvariant()));
}
/// <summary>
/// Print compiler logo
/// </summary>
/// <param name="consoleOutput"></param>
public override void PrintLogo(TextWriter consoleOutput)
{
consoleOutput.WriteLine(ErrorFacts.GetMessage(MessageID.IDS_LogoLine1, Culture), GetToolName(), GetCompilerVersion());
consoleOutput.WriteLine(ErrorFacts.GetMessage(MessageID.IDS_LogoLine2, Culture));
consoleOutput.WriteLine();
}
public override void PrintLangVersions(TextWriter consoleOutput)
{
consoleOutput.WriteLine(ErrorFacts.GetMessage(MessageID.IDS_LangVersions, Culture));
var defaultVersion = LanguageVersion.Default.MapSpecifiedToEffectiveVersion();
var latestVersion = LanguageVersion.Latest.MapSpecifiedToEffectiveVersion();
foreach (var v in (LanguageVersion[])Enum.GetValues(typeof(LanguageVersion)))
{
if (v == defaultVersion)
{
consoleOutput.WriteLine($"{v.ToDisplayString()} (default)");
}
else if (v == latestVersion)
{
consoleOutput.WriteLine($"{v.ToDisplayString()} (latest)");
}
else
{
consoleOutput.WriteLine(v.ToDisplayString());
}
}
consoleOutput.WriteLine();
}
internal override Type Type
{
get
{
// We do not use this.GetType() so that we don't break mock subtypes
return typeof(CSharpCompiler);
}
}
internal override string GetToolName()
{
return ErrorFacts.GetMessage(MessageID.IDS_ToolName, Culture);
}
/// <summary>
/// Print Commandline help message (up to 80 English characters per line)
/// </summary>
/// <param name="consoleOutput"></param>
public override void PrintHelp(TextWriter consoleOutput)
{
consoleOutput.WriteLine(ErrorFacts.GetMessage(MessageID.IDS_CSCHelp, Culture));
}
protected override bool TryGetCompilerDiagnosticCode(string diagnosticId, out uint code)
{
return CommonCompiler.TryGetCompilerDiagnosticCode(diagnosticId, "CS", out code);
}
protected override void ResolveAnalyzersFromArguments(
List<DiagnosticInfo> diagnostics,
CommonMessageProvider messageProvider,
CompilationOptions compilationOptions,
bool skipAnalyzers,
out ImmutableArray<DiagnosticAnalyzer> analyzers,
out ImmutableArray<ISourceGenerator> generators)
{
Arguments.ResolveAnalyzersFromArguments(LanguageNames.CSharp, diagnostics, messageProvider, AssemblyLoader, compilationOptions, skipAnalyzers, out analyzers, out generators);
}
protected override void ResolveEmbeddedFilesFromExternalSourceDirectives(
SyntaxTree tree,
SourceReferenceResolver resolver,
OrderedSet<string> embeddedFiles,
DiagnosticBag diagnostics)
{
foreach (LineDirectiveTriviaSyntax directive in tree.GetRoot().GetDirectives(
d => d.IsActive && !d.HasErrors && d.Kind() == SyntaxKind.LineDirectiveTrivia))
{
var path = (string?)directive.File.Value;
if (path == null)
{
continue;
}
string? resolvedPath = resolver.ResolveReference(path, tree.FilePath);
if (resolvedPath == null)
{
diagnostics.Add(
MessageProvider.CreateDiagnostic(
(int)ErrorCode.ERR_NoSourceFile,
directive.File.GetLocation(),
path,
CSharpResources.CouldNotFindFile));
continue;
}
embeddedFiles.Add(resolvedPath);
}
}
private protected override GeneratorDriver CreateGeneratorDriver(string baseDirectory, ParseOptions parseOptions, ImmutableArray<ISourceGenerator> generators, AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider, ImmutableArray<AdditionalText> additionalTexts)
{
return CSharpGeneratorDriver.Create(generators, additionalTexts, (CSharpParseOptions)parseOptions, analyzerConfigOptionsProvider, driverOptions: new GeneratorDriverOptions(disabledOutputs: IncrementalGeneratorOutputKind.Host, baseDirectory: baseDirectory));
}
private protected override void DiagnoseBadAccesses(TextWriter consoleOutput, ErrorLogger? errorLogger, Compilation compilation, ImmutableArray<Diagnostic> diagnostics)
{
DiagnosticBag newDiagnostics = DiagnosticBag.GetInstance();
foreach (var diag in diagnostics)
{
var symbol = diag switch
{
{ Code: (int)ErrorCode.ERR_BadAccess, Arguments: [Symbol s] } => s,
{ Code: (int)ErrorCode.ERR_InaccessibleGetter, Arguments: [Symbol s] } => s,
{ Code: (int)ErrorCode.ERR_InaccessibleSetter, Arguments: [Symbol s] } => s,
{ Code: (int)ErrorCode.ERR_ImplicitImplementationOfInaccessibleInterfaceMember, Arguments: [_, Symbol s, _] } => s,
_ => null
};
if (symbol is null || ReferenceEquals(compilation.Assembly, symbol.ContainingAssembly))
{
// Can't be IVT related
continue;
}
// '{0}' is defined in assembly '{1}'.
newDiagnostics.Add(new CSDiagnostic(
new CSDiagnosticInfo(ErrorCode.ERR_SymbolDefinedInAssembly, symbol, symbol.ContainingAssembly),
diag.Location));
}
ReportDiagnostics(newDiagnostics.ToReadOnlyAndFree(), consoleOutput, errorLogger, compilation);
}
}
}
|