/// <summary> /// Compiles C# file or project if need. /// Returns false if fails (C# errors etc). /// </summary> /// <param name="reason">Whether to recompile if compiled, etc. See also Remarks.</param> /// <param name="r">Results.</param> /// <param name="f">C# file. If projFolder used, must be the main file of the project.</param> /// <param name="projFolder">null or project folder.</param> /// <remarks> /// Must be always called in the main UI thread (Thread.CurrentThread.ManagedThreadId == 1). /// /// Adds <see cref="MetaReferences.DefaultReferences"/>. /// /// If f role is classFile: /// If CompReason.Run, does not compile (just parses meta), sets r.role=classFile and returns false. /// Else compiles but does not create output files. /// </remarks> public static bool Compile(ECompReason reason, out CompResults r, FileNode f, FileNode projFolder = null) { Debug.Assert(Thread.CurrentThread.ManagedThreadId == 1); r = null; var cache = XCompiled.OfCollection(f.Model); bool isCompiled = reason != ECompReason.CompileAlways && cache.IsCompiled(f, out r, projFolder); //AOutput.Write("isCompiled=" + isCompiled); if (!isCompiled) { bool ok = false; try { ok = _Compile(reason == ECompReason.Run, f, out r, projFolder); } catch (Exception ex) { //AOutput.Write($"Failed to compile '{f.Name}'. {ex.ToStringWithoutStack()}"); AOutput.Write($"Failed to compile '{f.Name}'. {ex}"); } if (!ok) { cache.Remove(f, false); return(false); } } return(true); }
public static XCompiled OfWorkspace(FilesModel m) { var cc = m.CompilerContext; if (cc == null) { m.CompilerContext = cc = new XCompiled(m); } return(cc as XCompiled); }
public static XCompiled OfCollection(FilesModel coll) { var cc = coll.CompilerContext; if (cc == null) { coll.CompilerContext = cc = new XCompiled(coll); } return(cc as XCompiled); }
static bool _Compile(bool forRun, FileNode f, out CompResults r, FileNode projFolder) { var p1 = APerf.Create(); r = new CompResults(); var m = new MetaComments(); if (!m.Parse(f, projFolder, EMPFlags.PrintErrors)) { return(false); } var err = m.Errors; p1.Next('m'); bool needOutputFiles = m.Role != ERole.classFile; //if for run, don't compile if f role is classFile if (forRun && !needOutputFiles) { r.role = ERole.classFile; return(false); } XCompiled cache = XCompiled.OfCollection(f.Model); string outPath = null, outFile = null, fileName = null; if (needOutputFiles) { if (m.OutputPath != null) { outPath = m.OutputPath; fileName = m.Name + ".dll"; } else { outPath = cache.CacheDirectory; fileName = f.IdString; } outFile = outPath + "\\" + fileName; AFile.CreateDirectory(outPath); } if (m.PreBuild.f != null && !_RunPrePostBuildScript(false, m, outFile)) { return(false); } var po = m.CreateParseOptions(); var trees = new CSharpSyntaxTree[m.CodeFiles.Count]; for (int i = 0; i < trees.Length; i++) { var f1 = m.CodeFiles[i]; trees[i] = CSharpSyntaxTree.ParseText(f1.code, po, f1.f.FilePath, Encoding.UTF8) as CSharpSyntaxTree; //info: file path is used later in several places: in compilation error messages, run time stack traces (from PDB), Visual Studio debugger, etc. // Our AOutputServer.SetNotifications callback will convert file/line info to links. It supports compilation errors and run time stack traces. } //p1.Next('t'); string asmName = m.Name; if (m.Role == ERole.editorExtension) { asmName = asmName + "|" + (++c_versionCounter).ToString(); //AssemblyLoadContext.Default cannot load multiple assemblies with same name } var compilation = CSharpCompilation.Create(asmName, trees, m.References.Refs, m.CreateCompilationOptions()); //p1.Next('c'); #if PDB string pdbFile = null; #endif MemoryStream pdbStream = null; string xdFile = null; Stream xdStream = null; Stream resNat = null; ResourceDescription[] resMan = null; EmitOptions eOpt = null; if (needOutputFiles) { _AddAttributes(ref compilation, needVersionEtc: m.Role == ERole.miniProgram || m.Role == ERole.exeProgram); //create debug info always. It is used for run-time error links. #if PDB pdbStream = new MemoryStream(); if (m.OutputPath != null) { pdbFile = Path.ChangeExtension(outFile, "pdb"); eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.Pdb, pdbFilePath: pdbFile); //eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb, pdbFilePath: pdbFile); //smaller, but not all tools support it } else { //eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded); //no, it is difficult to extract, because we load assembly from byte[] to avoid locking. We instead append portable PDB stream to the assembly stream. eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb); //adds < 1 KB; almost the same compiling speed. Separate pdb file is 14 KB; 2 times slower compiling, slower loading. } #else if (m.OutputPath != null) { //we don't use classic pdb file becouse of this error after switching to .NET Core: // Unexpected error writing debug information -- 'The version of Windows PDB writer is older than required: 'diasymreader.dll'' eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded); } else { pdbStream = new MemoryStream(); //eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded); //no, it is difficult to extract, because we load assembly from byte[] to avoid locking. We instead append portable PDB stream to the assembly stream. eOpt = new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb); //adds < 1 KB; almost the same compiling speed. Separate pdb file is 14 KB; 2 times slower compiling, slower loading. } #endif if (m.XmlDocFile != null) { xdStream = AFile.WaitIfLocked(() => File.Create(xdFile = APath.Normalize(m.XmlDocFile, outPath))); } resMan = _CreateManagedResources(m); if (err.ErrorCount != 0) { err.PrintAll(); return(false); } if (m.Role == ERole.exeProgram || m.Role == ERole.classLibrary) { resNat = _CreateNativeResources(m, compilation); } if (err.ErrorCount != 0) { err.PrintAll(); return(false); } //EmbeddedText.FromX //it seems we can embed source code in PDB. Not tested. } //p1.Next(); var asmStream = new MemoryStream(8000); var emitResult = compilation.Emit(asmStream, pdbStream, xdStream, resNat, resMan, eOpt); if (needOutputFiles) { xdStream?.Dispose(); resNat?.Dispose(); //info: compiler disposes resMan } //p1.Next('e'); var diag = emitResult.Diagnostics; if (!diag.IsEmpty) { foreach (var d in diag) { if (d.Severity == DiagnosticSeverity.Hidden) { continue; } err.AddErrorOrWarning(d, f); if (d.Severity == DiagnosticSeverity.Error && d.Id == "CS0009") { MetaReferences.RemoveBadReference(d.GetMessage()); } } err.PrintAll(); } if (!emitResult.Success) { if (needOutputFiles) { AFile.Delete(outFile); #if PDB if (pdbFile != null) { AFile.Delete(pdbFile); } #endif if (xdFile != null) { AFile.Delete(xdFile); } } return(false); } if (needOutputFiles) { //If there is no [STAThread], will need MTA thread. if (m.Role == ERole.miniProgram || m.Role == ERole.exeProgram) { bool hasSTAThread = compilation.GetEntryPoint(default)?.GetAttributes().Any(o => o.ToString() == "System.STAThreadAttribute") ?? false;