/// <summary> /// Compiles the Html Help 2 project /// </summary> /// <param name="projectRoot">The root locaion of the project inputs</param> /// <param name="helpName">The name of the project</param> /// <param name="langID">The language ID of the final help file</param> public void Compile( DirectoryInfo projectRoot, string helpName, short langID ) { string ProjectPathAndName = Path.Combine( projectRoot.FullName, helpName ); using( CompilerStatus status = new CompilerStatus( ProjectPathAndName + ".HxComp.log" ) ) { int cookie = -1; HxCompClass compiler = new HxCompClass(); compiler.Initialize(); compiler.LangId = langID; cookie = compiler.AdviseCompilerMessageCallback( status ); compiler.Compile( ProjectPathAndName + ".HxC", projectRoot.FullName, helpName + ".HxS", 0 ); if ( cookie != -1 ) compiler.UnadviseCompilerMessageCallback( cookie ); if ( status.CompileFailed ) throw new Exception( status.ErrorMessage ); Trace.WriteLine( "Help title compile complete" ); } }
//deletes leftovers and checks current status void InitCompiler() { var rldt = RelativePath(LiveDirTest); if (File.Exists(rldt)) { File.Delete(rldt); } compilerCurrentStatus = IsInitialized(); }
/// <summary> /// Compiles the code with the specified reference assemblies into a dll file. /// </summary> /// <param name="ReferenceAssemblies">The reference assemblies.</param> /// <param name="Code">The code.</param> public static void Compile(string[] ReferenceAssemblies, string Code) { DeleteOldDll(); CodeHost.Stop(); #if !DEBUG param.CompilerOptions = @"/optimize"; #endif //add user references param.ReferencedAssemblies.AddRange(ReferenceAssemblies); //add our references //for output support param.ReferencedAssemblies.Add("API.dll"); //for input support param.ReferencedAssemblies.Add("InputLib.dll"); //for joystick support param.ReferencedAssemblies.Add("Soopah.XNA.Input.dll"); //set the default status Status = CompilerStatus.Success; try { //compile the source code CompilerResults cr = cp.CompileAssemblyFromSource(param, Code); //check if the code has warnings //if so then elevate status to a warning if (cr.Errors.HasWarnings) Status = CompilerStatus.Warning; //check if the code has errors //if so then elevate status to a error if (cr.Errors.HasErrors) Status = CompilerStatus.Error; //if the code has any warnings or errors, format them as strings if (cr.Errors.Count > 0) Errors = GetErrorStrings(cr.Errors); else Errors = null; //else set errors to null //make sure the code does not have errors before trying to load it. if (!cr.Errors.HasErrors) CodeHost.AssemblyPath = cr.PathToAssembly; //if not then set the user code assembly path. } catch (Exception ex) { //catch any unresolvable errors and show them to the user. Status = CompilerStatus.AppError; Errors = new string[] { ex.ToString() }; } }
/// <inheritdoc /> public bool Initialize() { lock (CompilerLock) { if (!CompilerIdleNoLock()) { return(false); } lastCompilerError = null; compilerCurrentStatus = CompilerStatus.Initializing; CompilerThread = new Thread(new ThreadStart(InitializeImpl)); CompilerThread.Start(); return(true); } }
//kicks off the compiler thread /// <inheritdoc /> public bool Compile(bool silent = false) { lock (CompilerLock) { if (compilerCurrentStatus != CompilerStatus.Initialized) { return(false); } silentCompile = silent; lastCompilerError = null; compilerCurrentStatus = CompilerStatus.Compiling; CompilerThread = new Thread(new ThreadStart(CompileImpl)); CompilerThread.Start(); } return(true); }
/// <inheritdoc /> public async Task <Models.CompileJob> Compile(Models.RevisionInformation revisionInformation, DreamMakerSettings dreamMakerSettings, DreamDaemonSecurity securityLevel, uint apiValidateTimeout, IRepository repository, CancellationToken cancellationToken) { if (revisionInformation == null) { throw new ArgumentNullException(nameof(revisionInformation)); } if (dreamMakerSettings == null) { throw new ArgumentNullException(nameof(dreamMakerSettings)); } if (repository == null) { throw new ArgumentNullException(nameof(repository)); } if (securityLevel == DreamDaemonSecurity.Ultrasafe) { throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, "Cannot compile with ultrasafe security!"); } logger.LogTrace("Begin Compile"); var job = new Models.CompileJob { DirectoryName = Guid.NewGuid(), DmeName = dreamMakerSettings.ProjectName, RevisionInformation = revisionInformation }; logger.LogTrace("Compile output GUID: {0}", job.DirectoryName); lock (this) { if (Status != CompilerStatus.Idle) { throw new JobException("There is already a compile in progress!"); } Status = CompilerStatus.Copying; } try { var commitInsert = revisionInformation.CommitSha.Substring(0, 7); string remoteCommitInsert; if (revisionInformation.CommitSha == revisionInformation.OriginCommitSha) { commitInsert = String.Format(CultureInfo.InvariantCulture, "^{0}", commitInsert); remoteCommitInsert = String.Empty; } else { remoteCommitInsert = String.Format(CultureInfo.InvariantCulture, ". Remote commit: ^{0}", revisionInformation.OriginCommitSha.Substring(0, 7)); } var testmergeInsert = revisionInformation.ActiveTestMerges.Count == 0 ? String.Empty : String.Format(CultureInfo.InvariantCulture, " (Test Merges: {0})", String.Join(", ", revisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x => { var result = String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7)); if (x.Comment != null) { result += String.Format(CultureInfo.InvariantCulture, " ({0})", x.Comment); } return(result); }))); using (var byondLock = await byond.UseExecutables(null, cancellationToken).ConfigureAwait(false)) { await chat.SendUpdateMessage(String.Format(CultureInfo.InvariantCulture, "Deploying revision: {0}{1}{2} BYOND Version: {3}", commitInsert, testmergeInsert, remoteCommitInsert, byondLock.Version), cancellationToken).ConfigureAwait(false); async Task CleanupFailedCompile(bool cancelled) { logger.LogTrace("Cleaning compile directory..."); Status = CompilerStatus.Cleanup; var chatTask = chat.SendUpdateMessage(cancelled ? "Deploy cancelled!" : "Deploy failed!", cancellationToken); try { await ioManager.DeleteDirectory(job.DirectoryName.ToString(), CancellationToken.None).ConfigureAwait(false); } catch (Exception e) { logger.LogWarning("Error cleaning up compile directory {0}! Exception: {1}", ioManager.ResolvePath(job.DirectoryName.ToString()), e); } await chatTask.ConfigureAwait(false); }; try { await ioManager.CreateDirectory(job.DirectoryName.ToString(), cancellationToken).ConfigureAwait(false); var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); var dirB = ioManager.ConcatPath(job.DirectoryName.ToString(), BDirectoryName); logger.LogTrace("Copying repository to game directory..."); //copy the repository var fullDirA = ioManager.ResolvePath(dirA); var repoOrigin = repository.Origin; using (repository) await repository.CopyTo(fullDirA, cancellationToken).ConfigureAwait(false); Status = CompilerStatus.PreCompile; var resolvedGameDirectory = ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName)); await eventConsumer.HandleEvent(EventType.CompileStart, new List <string> { resolvedGameDirectory, repoOrigin }, cancellationToken).ConfigureAwait(false); Status = CompilerStatus.Modifying; if (job.DmeName == null) { logger.LogTrace("Searching for available .dmes..."); var path = (await ioManager.GetFilesWithExtension(dirA, DmeExtension, cancellationToken).ConfigureAwait(false)).FirstOrDefault(); if (path == default) { throw new JobException("Unable to find any .dme!"); } var dmeWithExtension = ioManager.GetFileName(path); job.DmeName = dmeWithExtension.Substring(0, dmeWithExtension.Length - DmeExtension.Length - 1); } logger.LogDebug("Selected {0}.dme for compilation!", job.DmeName); await ModifyDme(job, cancellationToken).ConfigureAwait(false); Status = CompilerStatus.Compiling; //run compiler, verify api job.ByondVersion = byondLock.Version.ToString(); var exitCode = await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false); var apiValidated = false; if (exitCode == 0) { Status = CompilerStatus.Verifying; apiValidated = await VerifyApi(apiValidateTimeout, securityLevel, job, byondLock, dreamMakerSettings.ApiValidationPort.Value, cancellationToken).ConfigureAwait(false); } if (!apiValidated) { //server never validated or compile failed await eventConsumer.HandleEvent(EventType.CompileFailure, new List <string> { resolvedGameDirectory, exitCode == 0 ? "1" : "0" }, cancellationToken).ConfigureAwait(false); throw new JobException(exitCode == 0 ? "Validation of the TGS api failed!" : String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}{2}", exitCode, Environment.NewLine, job.Output)); } logger.LogTrace("Running post compile event..."); Status = CompilerStatus.PostCompile; await eventConsumer.HandleEvent(EventType.CompileComplete, new List <string> { ioManager.ResolvePath(ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName)) }, cancellationToken).ConfigureAwait(false); logger.LogTrace("Duplicating compiled game..."); Status = CompilerStatus.Duplicating; //duplicate the dmb et al await ioManager.CopyDirectory(dirA, dirB, null, cancellationToken).ConfigureAwait(false); logger.LogTrace("Applying static game file symlinks..."); Status = CompilerStatus.Symlinking; //symlink in the static data var symATask = configuration.SymlinkStaticFilesTo(fullDirA, cancellationToken); var symBTask = configuration.SymlinkStaticFilesTo(ioManager.ResolvePath(dirB), cancellationToken); await Task.WhenAll(symATask, symBTask).ConfigureAwait(false); await chat.SendUpdateMessage("Deployment complete! Changes will be applied on next server reboot.", cancellationToken).ConfigureAwait(false); logger.LogDebug("Compile complete!"); return(job); } catch (Exception e) { await CleanupFailedCompile(e is OperationCanceledException).ConfigureAwait(false); throw; } } } catch (OperationCanceledException) { await eventConsumer.HandleEvent(EventType.CompileCancelled, null, default).ConfigureAwait(false); throw; } finally { Status = CompilerStatus.Idle; } }
//Compiler thread void CompileImpl() { if (Thread.CurrentThread.Name == null) { Thread.CurrentThread.Name = "Compiler Thread"; } try { if (!RepoConfigsMatch()) { lock (CompilerLock) { lastCompilerError = "Repository TGS3.json does not match cached version! Please update the config appropriately!"; compilerCurrentStatus = IsInitialized(); return; } } string resurrectee; bool repobusy_check = false; lock (RepoLock) { if (RepoBusy) { repobusy_check = true; } else { RepoBusy = true; } } if (repobusy_check) { SendMessage("DM: Copy aborted, repo locked!", MessageType.DeveloperInfo); lock (CompilerLock) { lastCompilerError = "The repo could not be locked for copying"; compilerCurrentStatus = CompilerStatus.Initialized; //still fairly valid return; } } string CurrentSha; string dmeName, dmePath; try { bool silent; lock (CompilerLock) { silent = silentCompile; silentCompile = false; } if (!silent) { SendMessage("DM: Compiling...", MessageType.DeveloperInfo); } resurrectee = GetStagingDir(); //non-relative var Config = GetCachedRepoConfig(); var deleteExcludeList = new List <string> { BridgeDLLName }; deleteExcludeList.AddRange(Config.StaticDirectoryPaths); deleteExcludeList.AddRange(Config.DLLPaths); Helpers.DeleteDirectory(resurrectee, true, deleteExcludeList); Directory.CreateDirectory(resurrectee + "/.git/logs"); foreach (var I in Config.StaticDirectoryPaths) { var the_path = Path.Combine(resurrectee, I); if (!Directory.Exists(the_path)) { CreateSymlink(Path.Combine(resurrectee, I), RelativePath(Path.Combine(StaticDirs, I))); } } foreach (var I in Config.DLLPaths) { var the_path = Path.Combine(resurrectee, I); if (!File.Exists(the_path)) { CreateSymlink(the_path, RelativePath(Path.Combine(StaticDirs, I))); } } if (!File.Exists(Path.Combine(resurrectee, BridgeDLLName))) { CreateSymlink(Path.Combine(resurrectee, BridgeDLLName), RelativePath(BridgeDLLName)); } deleteExcludeList.Add(".git"); Helpers.CopyDirectory(RelativePath(RepoPath), resurrectee, deleteExcludeList); CurrentSha = GetShaOrBranchNoLock(out string error, false, false); //just the tip const string GitLogsDir = "/.git/logs"; Helpers.CopyDirectory(RelativePath(RepoPath + GitLogsDir), resurrectee + GitLogsDir); dmeName = String.Format(CultureInfo.InvariantCulture, "{0}.dme", ProjectName()); dmePath = Path.Combine(resurrectee, dmeName); HandleDMEModifications(resurrectee, dmePath); try { File.Copy(RelativePath(PRJobFile), Path.Combine(resurrectee, PRJobFile)); } catch { } } finally { lock (RepoLock) RepoBusy = false; } var res = CreateBackup(); if (res != null) { lock (CompilerLock) { lastCompilerError = res; compilerCurrentStatus = CompilerStatus.Initialized; return; } } if (!File.Exists(dmePath)) { var errorMsg = String.Format("Could not find {0}!", dmeName); SendMessage("DM: " + errorMsg, MessageType.DeveloperInfo); WriteError(errorMsg, EventID.DMCompileCrash); lock (CompilerLock) { lastCompilerError = errorMsg; compilerCurrentStatus = CompilerStatus.Initialized; return; } } if (!PrecompileHook()) { lastCompilerError = "The precompile hook failed"; compilerCurrentStatus = CompilerStatus.Initialized; //still fairly valid WriteWarning("Precompile hook failed!", EventID.DMCompileError); return; } bool stagedBuild; lock (ByondLock) { stagedBuild = updateStat == ByondStatus.Staged; if (stagedBuild) { updateStat = ByondStatus.CompilingStaged; } else if (GetVersion(ByondVersion.Installed) == null) { lock (CompilerLock) { lastCompilerError = "BYOND not installed!"; compilerCurrentStatus = CompilerStatus.Initialized; return; } } } using (var DM = new Process()) //will kill the process if the thread is terminated { DM.StartInfo.FileName = RelativePath(Path.Combine(stagedBuild ? StagingDirectoryInner : ByondDirectory, "bin/dm.exe")); DM.StartInfo.Arguments = String.Format("-clean {0}", dmePath); DM.StartInfo.RedirectStandardOutput = true; DM.StartInfo.UseShellExecute = false; var OutputList = new StringBuilder(); DM.OutputDataReceived += new DataReceivedEventHandler( delegate(object sender, DataReceivedEventArgs e) { OutputList.Append(Environment.NewLine); OutputList.Append(e.Data); } ); try { lock (CompilerLock) { if (compilationCancellationRequestation) { return; } canCancelCompilation = true; } DM.Start(); DM.BeginOutputReadLine(); while (!DM.HasExited) { DM.WaitForExit(100); } DM.CancelOutputRead(); lock (CompilerLock) { canCancelCompilation = false; compilationCancellationRequestation = false; } } catch { if (!DM.HasExited) { DM.Kill(); DM.WaitForExit(); } throw; } finally { lock (CompilerLock) { canCancelCompilation = false; } if (stagedBuild) { lock (ByondLock) updateStat = ByondStatus.Staged; RequestRestart(); } } if (DM.ExitCode == 0) { lock (watchdogLock) { //gotta go fast var online = currentStatus == DreamDaemonStatus.Online; if (online) { Proc.Suspend(); } try { var rgdl = RelativePath(GameDirLive); if (Directory.Exists(rgdl)) { //these next two lines should be atomic but this is the best we can do Directory.Delete(rgdl); } CreateSymlink(rgdl, resurrectee); } finally { if (online && !Proc.HasExited) { Proc.Resume(); } } } var staged = DaemonStatus() != DreamDaemonStatus.Offline; if (!PostcompileHook()) { lastCompilerError = "The postcompile hook failed"; compilerCurrentStatus = CompilerStatus.Initialized; //still fairly valid WriteWarning("Postcompile hook failed!", EventID.DMCompileError); return; } UpdateLiveSha(CurrentSha); var msg = String.Format("Compile complete!{0}", !staged ? "" : " Server will update on reboot."); WorldAnnounce("Server updated, changes will be applied on reboot..."); SendMessage("DM: " + msg, MessageType.DeveloperInfo); WriteInfo(msg, EventID.DMCompileSuccess); lock (CompilerLock) { if (staged) { UpdateStaged = true; } lastCompilerError = null; compilerCurrentStatus = CompilerStatus.Initialized; //still fairly valid } } else { SendMessage("DM: Compile failed!", MessageType.DeveloperInfo); //Also happens for warnings WriteWarning("Compile error: " + OutputList.ToString(), EventID.DMCompileError); lock (CompilerLock) { lastCompilerError = "DM compile failure"; compilerCurrentStatus = CompilerStatus.Initialized; } } } } catch (ThreadAbortException) { return; } catch (Exception e) { SendMessage("DM: Compiler thread crashed!", MessageType.DeveloperInfo); WriteError("Compile manager errror: " + e.ToString(), EventID.DMCompileCrash); lock (CompilerLock) { lastCompilerError = e.ToString(); compilerCurrentStatus = CompilerStatus.Initialized; //still fairly valid } } finally { lock (CompilerLock) { canCancelCompilation = false; if (compilationCancellationRequestation) { compilerCurrentStatus = CompilerStatus.Initialized; compilationCancellationRequestation = false; SendMessage("DM: Compile cancelled!", MessageType.DeveloperInfo); WriteInfo("Compilation cancelled", EventID.DMCompileCancel); } } } }
//Initializing thread public void InitializeImpl() { Thread.CurrentThread.Name = "Initialization Thread"; try { if (DaemonStatus() != DreamDaemonStatus.Offline) { lock (CompilerLock) { lastCompilerError = "Dream daemon must not be running"; compilerCurrentStatus = IsInitialized(); return; } } if (!Exists()) //repo { lock (CompilerLock) { lastCompilerError = "Repository is not setup!"; compilerCurrentStatus = IsInitialized(); return; } } if (!RepoConfigsMatch()) { lock (CompilerLock) { lastCompilerError = "Repository TGS3.json does not match cached version! Please update the config appropriately!"; compilerCurrentStatus = IsInitialized(); return; } } try { SendMessage("DM: Setting up symlinks...", MessageType.DeveloperInfo); CleanGameFolder(); Helpers.DeleteDirectory(RelativePath(GameDir)); Directory.CreateDirectory(RelativePath(GameDirA)); Directory.CreateDirectory(RelativePath(GameDirB)); var rep_config = GetCachedRepoConfig(); if (rep_config != null) { foreach (var I in rep_config.StaticDirectoryPaths) { CreateSymlink(RelativePath(Path.Combine(GameDirA, I)), RelativePath(Path.Combine(StaticDirs, I))); } foreach (var I in rep_config.DLLPaths) { CreateSymlink(RelativePath(Path.Combine(GameDirA, I)), RelativePath(Path.Combine(StaticDirs, I))); } } var rbdlln = RelativePath(BridgeDLLName); CreateSymlink(RelativePath(Path.Combine(GameDirA, BridgeDLLName)), rbdlln); CreateSymlink(RelativePath(Path.Combine(GameDirB, BridgeDLLName)), rbdlln); CreateSymlink(RelativePath(GameDirLive), RelativePath(GameDirA)); lock (CompilerLock) { compilerCurrentStatus = CompilerStatus.Compiling; silentCompile = true; } } catch (ThreadAbortException) { return; } catch (Exception e) { lock (CompilerLock) { SendMessage("DM: Setup failed!", MessageType.DeveloperInfo); WriteError("Compiler Initialization Error: " + e.ToString(), EventID.DMInitializeCrash); lastCompilerError = e.ToString(); compilerCurrentStatus = CompilerStatus.Uninitialized; return; } } } catch (ThreadAbortException) { return; } CompileImpl(); }