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;
/// <summary> /// Called before executing script f. If returns true, don't need to compile. /// </summary> /// <param name="f"></param> /// <param name="r">Receives file path and execution options.</param> /// <param name="projFolder">Project folder or null. If not null, f must be its main file.</param> public bool IsCompiled(FileNode f, out CompResults r, FileNode projFolder) { r = new CompResults(); if (_data == null && !_Open()) { return(false); } if (!_data.TryGetValue(f.Id, out string value)) { return(false); } //ADebug.Print(value); int iPipe = 0; bool isScript = f.IsScript; r.role = MetaComments.DefaultRole(isScript); string asmFile; if (r.notInCache = (value != null && value.Starts("|="))) { iPipe = value.IndexOf('|', 2); if (iPipe < 0) { iPipe = value.Length; } asmFile = value.Substring(2, iPipe - 2); } else { asmFile = CacheDirectory + "\\" + f.IdString; } //AOutput.Write(asmFile); if (!AFile.GetProperties(asmFile, out var asmProp, FAFlags.UseRawPath)) { return(false); } DateTime asmDate = asmProp.LastWriteTimeUtc; if (_IsFileModified(f)) { return(false); } bool isMultiFileProject = false; if (value != null && iPipe < value.Length) { iPipe++; foreach (var v in value.Segments("|", SegFlags.NoEmpty, iPipe..)) { int offs = v.start + 1; char ch = value[v.start]; switch (ch) { case 't': r.role = (ERole)value.ToInt(offs); break; case 'a': r.runMode = (ERunMode)value.ToInt(offs); break; case 'n': r.ifRunning = (EIfRunning)value.ToInt(offs); break; case 'N': r.ifRunning2 = (EIfRunning2)value.ToInt(offs); break; case 'u': r.uac = (EUac)value.ToInt(offs); break; case 'b': r.prefer32bit = true; break; case 'q': r.console = true; break; case 'z': r.mtaThread = true; break; case 'd': r.pdbOffset = value.ToInt(offs); break; case 'p': isMultiFileProject = true; if (projFolder != null) { if (!Util.AHash.MD5Result.FromString(value.AsSpan(offs, v.end - offs), out var md5)) { return(false); } Util.AHash.MD5 md = default; foreach (var f1 in projFolder.EnumProjectClassFiles(f)) { if (_IsFileModified(f1)) { return(false); } md.Add(f1.Id); } if (md.IsEmpty || md.Hash != md5) { return(false); } } break; case '*': var dll = value[offs..v.end];