context: // Copyright (c) Microsoft Open Technologies, Inc.  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.Generic;
  using System.Collections.Immutable;
  using System.Diagnostics;
  using System.Globalization;
  using System.IO;
  using System.Linq;
  using System.Reflection;
  using System.Threading;
  using System.Threading.Tasks;
  using Microsoft.CodeAnalysis.CSharp.Symbols;
  using Microsoft.CodeAnalysis.Instrumentation;
  using Microsoft.CodeAnalysis.Text;
  using Roslyn.Utilities;
  
  namespace Microsoft.CodeAnalysis.CSharp
  {
      internal abstract class CSharpCompiler : CommonCompiler
      {
          private static string responseFileName;
          private CommandLineDiagnosticFormatter diagnosticFormatter;
  
          protected CSharpCompiler(CSharpCommandLineParser parser, string responseFile, string[] args, string baseDirectory, string additionalReferencePaths)
              : base(parser, responseFile, args, baseDirectory, additionalReferencePaths)
          {
              this.diagnosticFormatter = new CommandLineDiagnosticFormatter(baseDirectory, Arguments.PrintFullPaths, Arguments.ShouldIncludeErrorEndLocation);
          }
  
          public override DiagnosticFormatter DiagnosticFormatter { get { return this.diagnosticFormatter; } }
          protected internal new CSharpCommandLineArguments Arguments { get { return (CSharpCommandLineArguments)base.Arguments; } }
  
          // Name of the response file to load by default (unless /noconfig is specified)
          protected static string CSharpResponseFileName
          {
              get
              {
                  // TODO: Bug 872626 (angocke)
                  // WORKAROUND: REMOVE BEFORE SHIPPING
                  // First look for csc.rsp, then look for rcsc.rsp.
                  if (String.IsNullOrEmpty(responseFileName))
                  {
                      responseFileName = Path.Combine(ResponseFileDirectory, "csc.rsp");
                      if (!File.Exists(responseFileName))
                      {
                          responseFileName = Path.Combine(ResponseFileDirectory, "rcsc.rsp");
                      }
                  }
                  return responseFileName;
              }
          }
  
          public override int Run(TextWriter consoleOutput, CancellationToken cancellationToken = default(CancellationToken))
          {
              try
              {
                  return base.Run(consoleOutput, cancellationToken);
              }
              catch (OperationCanceledException)
              {
                  DiagnosticInfo diag = new DiagnosticInfo(MessageProvider, (int)ErrorCode.ERR_CompileCancelled);
                  PrintErrors(new[] { diag }, consoleOutput);
                  return 1;
              }
          }
  
          protected override Compilation CreateCompilation(TextWriter consoleOutput, TouchedFileLogger touchedFilesLogger)
          {
              var parseOptions = Arguments.ParseOptions;
              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];
  
              if (Arguments.CompilationOptions.ConcurrentBuild)
              {
                  var tasks = new Task[sourceFiles.Length];
                  for(int i = 0; i < sourceFiles.Length; i++)
                  {
                      //NOTE: order of trees is important!!
                      var treeIndex = i;
                      tasks[treeIndex] = Task.Run(async () =>
                      {
                          var file = sourceFiles[treeIndex];
  
                          var fileReadDiagnostics = new List<DiagnosticInfo>();
                          var result = await ReadFileContentAsync(file, fileReadDiagnostics, Arguments.Encoding).ConfigureAwait(false);
  
                          var content = result.Item1;
                          normalizedFilePaths[treeIndex] = result.Item2;
  
                          if (content == null)
                          {
                              PrintErrors(fileReadDiagnostics, consoleOutput);
                              fileReadDiagnostics.Clear();
                              hadErrors = true;
                              trees[treeIndex] = null;
                          }
                          else
                          {
                              trees[treeIndex] = ParseFile(parseOptions, scriptParseOptions, content, file);
                          }
                      });
                  }
  
                  Task.WaitAll(tasks);
              }
              else
              {
                  for (int i = 0; i < sourceFiles.Length; i++)
                  {
                      var file = sourceFiles[i];
  
                      //NOTE: order of trees is important!!
                      trees[i] = ParseFile(consoleOutput, parseOptions, scriptParseOptions, ref hadErrors, file, out normalizedFilePaths[i]);
                  }
              }
  
              // If errors had been reported in ParseFile, while trying to read files, then we should simply exit.
              if (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(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 : this.diagnosticFormatter.RelativizeNormalizedPath(normalizedFilePath)));
  
                      trees[i] = null;
                  }
              }
  
              if (Arguments.TouchedFilesPath != null)
              {
                  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 metadataProvider = GetMetadataProvider();
              var xmlFileResolver = new LoggingXmlFileResolver(Arguments.BaseDirectory, touchedFilesLogger);
              var sourceFileResolver = new LoggingSourceFileResolver(ImmutableArray<string>.Empty, Arguments.BaseDirectory, touchedFilesLogger);
  
              var externalReferenceResolver = GetExternalMetadataResolver(touchedFilesLogger);
              MetadataReferenceResolver referenceDirectiveResolver;
              var resolvedReferences = ResolveMetadataReferences(externalReferenceResolver, metadataProvider, diagnostics, assemblyIdentityComparer, touchedFilesLogger, out referenceDirectiveResolver);
              if (PrintErrors(diagnostics, consoleOutput))    
              {
                  return null;
              }
  
              var strongNameProvider = new LoggingStrongNameProvider(Arguments.KeyFileSearchPaths, touchedFilesLogger);
  
              var compilation = CSharpCompilation.Create(
                  Arguments.CompilationName,
                  trees.WhereNotNull(),
                  resolvedReferences,
                  Arguments.CompilationOptions.
                      WithMetadataReferenceResolver(referenceDirectiveResolver).
                      WithMetadataReferenceProvider(metadataProvider).
                      WithAssemblyIdentityComparer(assemblyIdentityComparer).
                      WithStrongNameProvider(strongNameProvider).
                      WithXmlReferenceResolver(xmlFileResolver).
                      WithSourceReferenceResolver(sourceFileResolver));
  
              // Print the diagnostics produced during the parsing stage and exit if there were any errors.
              if (PrintErrors(compilation.GetParseDiagnostics(), consoleOutput))
              {
                  return null;
              }
  
              if (PrintErrors(compilation.GetDeclarationDiagnostics(), consoleOutput))
              {
                  return null;
              }
  
              return compilation;
          }
  
          private SyntaxTree ParseFile(TextWriter consoleOutput,
              CSharpParseOptions parseOptions,
              CSharpParseOptions scriptParseOptions,
              ref bool hadErrors,
              CommandLineSourceFile file,
              out string normalizedFilePath)
          {
              var fileReadDiagnostics = new List<DiagnosticInfo>();
              var content = ReadFileContent(file, fileReadDiagnostics, Arguments.Encoding, out normalizedFilePath);
  
              if (content == null)
              {
                  PrintErrors(fileReadDiagnostics, consoleOutput);
                  fileReadDiagnostics.Clear();
                  hadErrors = true;
                  return null;
              }
              else
              {
                  return ParseFile(parseOptions, scriptParseOptions, content, file);
              }
          }
  
          private static SyntaxTree ParseFile(
              CSharpParseOptions parseOptions, 
              CSharpParseOptions scriptParseOptions,
              SourceText content,
              CommandLineSourceFile file)
          {
              var tree = SyntaxFactory.ParseSyntaxTree(
                                  content,
                                  file.Path,
                                  file.IsScript ? scriptParseOptions : parseOptions);
  
              // 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)
          {
  
gt:             if (Arguments.OutputFileName == null)