312 lines
11 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|