JD2022-TU1/main/tools/apps/Packager/Packager.cs

312 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using JD.Core.ProjectManaging;
using Packager.Parameters;
using Packager.Tasks;
using PluginCommon;
using Ubisoft.Collections;
using Path = System.IO.Path;
namespace Packager
{
/// <summary>
/// Instantiates profile object (build configuration) and the builder object. Also, it does the bootstrap tasks:
/// 1. cleaning up the gametool temporary output directory.
/// 2. clean up the cache, if mentioned.
/// 3. Start the task builder with index of first task.
/// </summary>
public class GameTool : IDisposable
{
private readonly MessageLogger _logger = new MessageLogger(nameof(Packager));
private readonly Builder _builder;
private readonly BuildConfiguration _buildConfiguration;
private readonly CommandLineArgs _commandArgs;
private int _startTaskIndex;
public GameTool(CommandLineArgs args)
{
_logger.LogInfo($"Packager started at {System.Reflection.Assembly.GetExecutingAssembly().Location} with arguments {args}");
_commandArgs = args;
ParseCommandLine();
_buildConfiguration = new BuildConfiguration(args);
Setup();
_builder = new Builder(_buildConfiguration, ConfigurationArguments.StopOnErrors);
_logger.LogInfo($"{nameof(Builder)} was created, its log file can be found at \"{_builder.Logger.LogFilePath}\".");
}
private static class ConfigurationArguments
{
public static bool Restart { get; set; }
public static bool SilentMode { get; set; }
public static bool StopOnErrors { get; set; }
public static bool ClearCache { get; set; }
public static string GraphVizRoot { get; set; }
}
public void Start()
{
if (ConfigurationArguments.SilentMode)
{
MessageUtils.IsApplicationSilent = true;
}
if (ConfigurationArguments.GraphVizRoot != null)
{
ExportTaskGraph();
}
_builder.BuildTaskList(_startTaskIndex);
}
private void ParseCommandLine()
{
ConfigurationArguments.StopOnErrors = _commandArgs.getBoolValue("stoponerror", false);
ConfigurationArguments.Restart = _commandArgs.getBoolValue("restart", true);
ConfigurationArguments.SilentMode = _commandArgs.getBoolValue("silentmode", true);
ConfigurationArguments.ClearCache = _commandArgs.getBoolValue("clearcachedirectory", false);
ConfigurationArguments.GraphVizRoot = _commandArgs.getStringValue("graphvizroot", null);
}
private void Setup()
{
if (ConfigurationArguments.Restart)
{
Reset();
}
else
{
FindStartTaskIndex();
InvalidateTask(_buildConfiguration.Tasks[_startTaskIndex]);
}
if (ConfigurationArguments.ClearCache)
{
ClearCacheDirectory();
}
}
private void ClearCacheDirectory()
{
var cachePath = Path.Combine(DataPath.Value, "cache");
if (Directory.Exists(cachePath))
{
var directoryInfo = new DirectoryInfo(cachePath);
DeleteFilesFromDirectory(directoryInfo);
}
}
private void DeleteFilesFromDirectory(DirectoryInfo directory)
{
try
{
directory.GetDirectories().ForEach(DeleteFilesFromDirectory);
directory.GetFiles().ForEach(DeleteFile);
directory.Delete();
}
catch (Exception exception)
{
var message = $"[Warning] Unable to clear Cache : {exception.Message}";
_logger.LogException(exception);
Console.WriteLine(message);
}
}
private void DeleteFile(FileInfo file)
{
file.IsReadOnly = false;
file.Delete();
}
private void CleanSessionFolder()
{
var buildConfigurationSessionPath = Path.Combine(DataPath.GetAbsolutePath("output"), "temp", nameof(Packager), _buildConfiguration.Name);
if (Directory.Exists(buildConfigurationSessionPath))
{
foreach (string filePath in Directory.EnumerateFiles(buildConfigurationSessionPath, "*.*", SearchOption.AllDirectories))
{
File.SetAttributes(filePath, FileAttributes.Normal);
File.Delete(filePath);
}
System.IO.Directory.Delete(buildConfigurationSessionPath, recursive: true);
}
System.IO.Directory.CreateDirectory(buildConfigurationSessionPath);
}
private void DeleteFolderContent(string folderPath)
{
if (!Directory.Exists(folderPath))
return;
try
{
System.IO.Directory.Delete(folderPath);
}
catch (Exception ex)
{
_logger.LogException(ex);
MessageUtils.ShowMessage(ex.Message, $"Can't delete folder {folderPath}");
}
}
private void DestroyTaskOutputs(Task task)
{
task.ResetOutputs();
DeleteFolderContent(task.TaskOutputFolder);
if (File.Exists(task.TaskOutputFile))
File.Delete(task.TaskOutputFile);
if (File.Exists(task.TaskLogFile))
File.Delete(task.TaskLogFile);
}
private Task[] GetDependentTasks(Task task)
{
int index = task.OrderId;
return _buildConfiguration.Tasks.Skip(index).ToArray();
}
private void InvalidateTask(Task task)
{
foreach (var dependentTask in GetDependentTasks(task))
{
DestroyTaskOutputs(dependentTask);
}
}
private void InvalidateAllTasks()
{
foreach (var i in Enumerable.Range(0, _buildConfiguration.Tasks.Count))
{
DestroyTaskOutputs(_buildConfiguration.Tasks[i]);
}
}
private void ExportTaskGraph()
{
var dotPath = Path.Combine(ConfigurationArguments.GraphVizRoot, "bin/dot.exe");
if (File.Exists(dotPath))
{
_logger.LogInfo($"Using GraphViz install: {dotPath}");
}
else
{
_logger.LogError($"GraphViz toolchain not found here: {dotPath}");
return;
}
// build graph data in graphviz code
var stringBuilder = new StringBuilder();
stringBuilder.Append("digraph G {\n");
foreach (var task in _buildConfiguration.Tasks)
{
stringBuilder.Append("\"" + task.InstanceName + "\";\n");
// flatten input references recursively
var inputReferences = new List<Reference>();
foreach (var reference in task.GetInputsDesc())
{
inputReferences.AddRange(reference.Input.EnumerateChildren());
}
foreach (var inputReference in inputReferences)
{
// Look up the input in the output of other tasks.
//
// Note: the O(n^2) traversal of tasks could be avoided, but
// that implies a much larger refactoring of the task system.
foreach (var inputTask in _buildConfiguration.Tasks)
{
var query = from memberInfo in inputTask.GetType().GetMembers()
where memberInfo.GetCustomAttributes(typeof(OutputAttribute), false).Length > 0
select (Reference)(memberInfo.MemberType == MemberTypes.Property ? ((PropertyInfo)memberInfo).GetValue(inputTask) : ((FieldInfo)memberInfo).GetValue(inputTask));
var outputReferences = query.ToArray();
if (outputReferences.Contains(inputReference))
{
stringBuilder.Append("\"" + inputTask.InstanceName + "\" -> \"" + task.InstanceName + "\";\n");
}
}
}
}
stringBuilder.Append("}\n");
// call the 'dot' tool from GraphViz
using (var dotProcess = new Process())
{
var graphCode = stringBuilder.ToString();
_logger.LogInfo(graphCode);
dotProcess.StartInfo.FileName = dotPath;
dotProcess.StartInfo.Arguments = "-Tpng";
dotProcess.StartInfo.UseShellExecute = false;
dotProcess.StartInfo.RedirectStandardInput = true;
dotProcess.StartInfo.RedirectStandardOutput = true;
dotProcess.Start();
// input graph data
dotProcess.StandardInput.Write(graphCode);
dotProcess.StandardInput.Close();
var outputFolder = _commandArgs.getStringValue("outputpath", null);
Directory.CreateDirectory(outputFolder);
// render the graph as an image in the output folder
var outputImagePath = Path.Combine(outputFolder, "task-graph.png");
using (var outputFileStream = File.Create(outputImagePath))
{
dotProcess.StandardOutput.BaseStream.CopyTo(outputFileStream);
}
dotProcess.WaitForExit();
}
}
public void Reset()
{
CleanSessionFolder();
InvalidateAllTasks();
SetStartTask(0);
}
private void SetStartTask(int taskIndex)
{
_startTaskIndex = taskIndex;
}
private void FindStartTaskIndex()
{
int count = _buildConfiguration.Tasks.Count;
SetStartTask(count);
for (int i = count - 1; i >= 0; --i)
{
Task task = _buildConfiguration.Tasks[i];
string outputFileName = task.TaskOutputFile;
if (!File.Exists(outputFileName))
{
SetStartTask(i);
}
}
}
public void Dispose()
{
_builder?.Dispose();
}
}
}