mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-16 21:21:03 +08:00
373 lines
16 KiB
C#
373 lines
16 KiB
C#
#nullable disable
|
|
// The adapter is compiled by the SDK; the value protocol is shared with the worker.
|
|
// Only this source adapter enters the persistent build node. The real task DLL
|
|
// and its dependencies are loaded by a single-use dotnet process.
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Runtime.Serialization;
|
|
using System.Runtime.Serialization.Json;
|
|
using System.Text;
|
|
using AtomUI.Build.Tasks.Isolation;
|
|
using Microsoft.Build.Framework;
|
|
#if !ATOMUI_BUILD_TASK_HOST
|
|
using Microsoft.Build.Utilities;
|
|
#endif
|
|
|
|
namespace AtomUI.Build.Tasks.Isolation
|
|
{
|
|
// The protocol contains values only. No task-defined types or MSBuild runtime
|
|
// objects cross the process boundary, including item implementations.
|
|
[DataContract]
|
|
public sealed class TaskRequest
|
|
{
|
|
[DataMember] public string TaskName { get; set; }
|
|
[DataMember] public string ProjectFile;
|
|
[DataMember] public int Line;
|
|
[DataMember] public int Column;
|
|
[DataMember] public Dictionary<string, string> Properties = new Dictionary<string, string>();
|
|
[DataMember] public Dictionary<string, TaskWireItem[]> Items = new Dictionary<string, TaskWireItem[]>();
|
|
}
|
|
|
|
[DataContract]
|
|
public sealed class TaskResponse
|
|
{
|
|
[DataMember] public bool Success;
|
|
[DataMember] public Dictionary<string, string> Properties = new Dictionary<string, string>();
|
|
[DataMember] public Dictionary<string, TaskWireItem[]> Items = new Dictionary<string, TaskWireItem[]>();
|
|
[DataMember] public List<TaskDiagnostic> Diagnostics = new List<TaskDiagnostic>();
|
|
}
|
|
|
|
[DataContract]
|
|
public sealed class TaskWireItem : ITaskItem
|
|
{
|
|
[DataMember] public string ItemSpec { get; set; }
|
|
[DataMember] public string FullPath;
|
|
[DataMember] public Dictionary<string, string> Metadata = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
public int MetadataCount { get { return Metadata.Count; } }
|
|
public ICollection MetadataNames { get { return Metadata.Keys.ToArray(); } }
|
|
public string GetMetadata(string name)
|
|
{
|
|
string value;
|
|
if (Metadata.TryGetValue(name, out value))
|
|
return value;
|
|
return name.Equals("FullPath", StringComparison.OrdinalIgnoreCase) ? FullPath ?? string.Empty : string.Empty;
|
|
}
|
|
public void SetMetadata(string name, string value) { Metadata[name] = value; }
|
|
public void RemoveMetadata(string name) { Metadata.Remove(name); }
|
|
public IDictionary CloneCustomMetadata() { return new Dictionary<string, string>(Metadata, StringComparer.OrdinalIgnoreCase); }
|
|
public void CopyMetadataTo(ITaskItem destination)
|
|
{
|
|
foreach (var pair in Metadata)
|
|
if (string.IsNullOrEmpty(destination.GetMetadata(pair.Key)))
|
|
{
|
|
var literalDestination = destination as ITaskItem2;
|
|
if (literalDestination != null)
|
|
literalDestination.SetMetadataValueLiteral(pair.Key, pair.Value);
|
|
else
|
|
destination.SetMetadata(pair.Key, pair.Value);
|
|
}
|
|
}
|
|
|
|
[OnDeserialized]
|
|
private void OnDeserialized(StreamingContext context)
|
|
{
|
|
Metadata = new Dictionary<string, string>(Metadata, StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
public static TaskWireItem FromItem(ITaskItem item)
|
|
{
|
|
var result = new TaskWireItem { ItemSpec = item.ItemSpec, FullPath = item.GetMetadata("FullPath") };
|
|
foreach (DictionaryEntry entry in item.CloneCustomMetadata())
|
|
result.Metadata.Add((string)entry.Key, (string)entry.Value);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
[DataContract]
|
|
public sealed class TaskDiagnostic
|
|
{
|
|
[DataMember] public string Kind;
|
|
[DataMember] public string Message;
|
|
[DataMember] public string Subcategory;
|
|
[DataMember] public string Code;
|
|
[DataMember] public string HelpKeyword;
|
|
[DataMember] public string File;
|
|
[DataMember] public int Line;
|
|
[DataMember] public int Column;
|
|
[DataMember] public int EndLine;
|
|
[DataMember] public int EndColumn;
|
|
[DataMember] public int Importance;
|
|
}
|
|
|
|
public static class TaskWire
|
|
{
|
|
public static void Write<T>(string path, T value)
|
|
{
|
|
using (var stream = File.Create(path))
|
|
Serializer<T>().WriteObject(stream, value);
|
|
}
|
|
|
|
public static T Read<T>(string json)
|
|
{
|
|
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
|
|
return (T)Serializer<T>().ReadObject(stream);
|
|
}
|
|
|
|
public static void WriteResponse(TaskResponse response)
|
|
{
|
|
using (var stream = Console.OpenStandardOutput())
|
|
Serializer<TaskResponse>().WriteObject(stream, response);
|
|
}
|
|
|
|
private static DataContractJsonSerializer Serializer<T>()
|
|
{
|
|
return new DataContractJsonSerializer(typeof(T),
|
|
new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true });
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#if !ATOMUI_BUILD_TASK_HOST
|
|
namespace AtomUI.Build.Tasks
|
|
{
|
|
public abstract class IsolatedBuildTask : Task, ICancelableTask
|
|
{
|
|
[Required] public string TaskAssembly { get; set; }
|
|
public string DotNetPath { get; set; }
|
|
|
|
private readonly object processGate = new object();
|
|
private Process process;
|
|
private bool cancelled;
|
|
|
|
public void Cancel()
|
|
{
|
|
lock (processGate)
|
|
{
|
|
cancelled = true;
|
|
if (process != null && !process.HasExited)
|
|
process.Kill();
|
|
}
|
|
}
|
|
|
|
public override bool Execute()
|
|
{
|
|
string directory = Path.Combine(Path.GetTempPath(), "atomui-build-task-" + Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(directory);
|
|
try
|
|
{
|
|
return ExecuteProcess(Path.Combine(directory, "request.json"));
|
|
}
|
|
catch (Exception error)
|
|
{
|
|
Log.LogErrorFromException(error, true);
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
lock (processGate)
|
|
{
|
|
if (process != null)
|
|
{
|
|
// A failed read or cancellation must not let a worker outlive
|
|
// the invocation and keep the toolset locked in the background.
|
|
if (!process.HasExited)
|
|
{
|
|
process.Kill();
|
|
process.WaitForExit();
|
|
}
|
|
process.Dispose();
|
|
process = null;
|
|
}
|
|
}
|
|
Directory.Delete(directory, true);
|
|
}
|
|
}
|
|
|
|
private bool ExecuteProcess(string projectFile)
|
|
{
|
|
var parameters = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
|
.Where(p => p.DeclaringType == GetType()).ToArray();
|
|
var outputs = parameters.Where(p => p.IsDefined(typeof(OutputAttribute), true)).ToArray();
|
|
var request = new TaskRequest
|
|
{
|
|
TaskName = GetType().Name, ProjectFile = BuildEngine.ProjectFileOfTaskNode,
|
|
Line = BuildEngine.LineNumberOfTaskNode, Column = BuildEngine.ColumnNumberOfTaskNode
|
|
};
|
|
foreach (var parameter in parameters.Except(outputs))
|
|
{
|
|
object value = parameter.GetValue(this, null);
|
|
if (value == null)
|
|
continue;
|
|
if (parameter.PropertyType == typeof(ITaskItem[]))
|
|
request.Items.Add(parameter.Name, ((ITaskItem[])value).Select(TaskWireItem.FromItem).ToArray());
|
|
else
|
|
request.Properties.Add(parameter.Name, Convert.ToString(value, CultureInfo.InvariantCulture));
|
|
}
|
|
TaskWire.Write(projectFile, request);
|
|
string arguments = Quote(Path.GetFullPath(TaskAssembly)) + " " + Quote(projectFile);
|
|
var start = new ProcessStartInfo
|
|
{
|
|
FileName = string.IsNullOrWhiteSpace(DotNetPath) ? "dotnet" : DotNetPath,
|
|
Arguments = arguments,
|
|
WorkingDirectory = Directory.GetCurrentDirectory(),
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
StandardOutputEncoding = Encoding.UTF8,
|
|
StandardErrorEncoding = Encoding.UTF8
|
|
};
|
|
lock (processGate)
|
|
{
|
|
if (cancelled)
|
|
return false;
|
|
process = Process.Start(start);
|
|
}
|
|
var standardOutput = process.StandardOutput.ReadToEndAsync();
|
|
var standardError = process.StandardError.ReadToEndAsync();
|
|
process.WaitForExit();
|
|
string outputText = standardOutput.GetAwaiter().GetResult();
|
|
string errorText = standardError.GetAwaiter().GetResult();
|
|
foreach (string line in errorText.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries))
|
|
Log.LogMessageFromText(line, MessageImportance.High);
|
|
if (process.ExitCode != 0)
|
|
{
|
|
Log.LogError("AtomUI build task {0} exited with code {1}. {2}", GetType().Name, process.ExitCode, outputText);
|
|
return false;
|
|
}
|
|
|
|
TaskResponse result = TaskWire.Read<TaskResponse>(outputText);
|
|
foreach (TaskDiagnostic diagnostic in result.Diagnostics)
|
|
{
|
|
if (diagnostic.Kind == "error")
|
|
Log.LogError(diagnostic.Subcategory, diagnostic.Code, diagnostic.HelpKeyword, diagnostic.File,
|
|
diagnostic.Line, diagnostic.Column, diagnostic.EndLine, diagnostic.EndColumn, diagnostic.Message);
|
|
else if (diagnostic.Kind == "warning")
|
|
Log.LogWarning(diagnostic.Subcategory, diagnostic.Code, diagnostic.HelpKeyword, diagnostic.File,
|
|
diagnostic.Line, diagnostic.Column, diagnostic.EndLine, diagnostic.EndColumn, diagnostic.Message);
|
|
else
|
|
Log.LogMessage((MessageImportance)diagnostic.Importance, "{0}", diagnostic.Message);
|
|
}
|
|
if (!result.Success)
|
|
return false;
|
|
foreach (var output in outputs)
|
|
{
|
|
if (output.PropertyType == typeof(ITaskItem[]))
|
|
{
|
|
// Keep the original task's literal ITaskItem semantics, including
|
|
// custom metadata named Identity. TaskItem would reinterpret both.
|
|
output.SetValue(this, result.Items[output.Name].Cast<ITaskItem>().ToArray(), null);
|
|
}
|
|
else
|
|
output.SetValue(this, Convert.ChangeType(result.Properties[output.Name], output.PropertyType,
|
|
CultureInfo.InvariantCulture), null);
|
|
}
|
|
return !Log.HasLoggedErrors;
|
|
}
|
|
|
|
private static string Quote(string path)
|
|
{
|
|
if (path.IndexOf('"') >= 0)
|
|
throw new ArgumentException("A process path cannot contain a quote.", "path");
|
|
return "\"" + path + "\"";
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
namespace AtomUI.Build.Tasks
|
|
{
|
|
public sealed class ValidateRegistrationToolchainTask : IsolatedBuildTask
|
|
{
|
|
public string LinkerAssembly { get; set; } = string.Empty;
|
|
public string NativeCompiler { get; set; } = string.Empty;
|
|
}
|
|
public sealed class PrepareTypeMapLinkTask : IsolatedBuildTask
|
|
{
|
|
public string SdkVersion { get; set; } = string.Empty;
|
|
public string WasmSdkTasksPath { get; set; } = string.Empty;
|
|
public string BackendAssembly { get; set; } = string.Empty;
|
|
public string LinkerAssembly { get; set; } = string.Empty;
|
|
public string Capability { get; set; } = string.Empty;
|
|
public string ReceiptPath { get; set; } = string.Empty;
|
|
public string SignaturePath { get; set; } = string.Empty;
|
|
public string LinkSemaphore { get; set; } = string.Empty;
|
|
public string OutputDirectory { get; set; } = string.Empty;
|
|
public string Configuration { get; set; } = string.Empty;
|
|
public ITaskItem[] Inputs { get; set; } = Array.Empty<ITaskItem>();
|
|
public ITaskItem[] Assemblies { get; set; } = Array.Empty<ITaskItem>();
|
|
public ITaskItem[] LinkOptions { get; set; } = Array.Empty<ITaskItem>();
|
|
public ITaskItem[] LinkItems { get; set; } = Array.Empty<ITaskItem>();
|
|
}
|
|
public sealed class VerifyTypeMapReceiptTask : IsolatedBuildTask
|
|
{
|
|
public string ReceiptPath { get; set; } = string.Empty;
|
|
public string SignaturePath { get; set; } = string.Empty;
|
|
public bool BindReceipt { get; set; }
|
|
public bool RequireConsumedAssemblies { get; set; }
|
|
public ITaskItem[] ConsumedAssemblies { get; set; } = Array.Empty<ITaskItem>();
|
|
}
|
|
public sealed class ResolveRegistrationToolsTask : IsolatedBuildTask
|
|
{
|
|
public ITaskItem[] Candidates { get; set; } = Array.Empty<ITaskItem>();
|
|
public bool IncludeBackend { get; set; }
|
|
[Output] public string GeneratorAssembly { get; set; } = string.Empty;
|
|
[Output] public string BackendAssembly { get; set; } = string.Empty;
|
|
}
|
|
public sealed class GenerateThemeAssetWrappersTask : IsolatedBuildTask
|
|
{
|
|
[Required] public ITaskItem[] ThemeAssets { get; set; } = new ITaskItem[0];
|
|
[Required] public string OutputDirectory { get; set; } = string.Empty;
|
|
[Required] public string AssemblyName { get; set; } = string.Empty;
|
|
[Required] public string GeneratedCodePath { get; set; } = string.Empty;
|
|
[Output] public ITaskItem[] GeneratedAssets { get; set; } = new ITaskItem[0];
|
|
}
|
|
public sealed class ExportLanguageTemplatesTask : IsolatedBuildTask
|
|
{
|
|
[Required] public ITaskItem[] SourceFiles { get; set; } = Array.Empty<ITaskItem>();
|
|
[Required] public string TargetLanguage { get; set; } = string.Empty;
|
|
public string OutputRootDirectory { get; set; }
|
|
[Output] public ITaskItem[] ExportedFiles { get; set; } = Array.Empty<ITaskItem>();
|
|
}
|
|
public sealed class PrepareLanguagePackageAssetsTask : IsolatedBuildTask
|
|
{
|
|
[Required] public string PackageId { get; set; } = string.Empty;
|
|
[Required] public ITaskItem[] LanguageFiles { get; set; } = Array.Empty<ITaskItem>();
|
|
public ITaskItem[] SourceLanguageFiles { get; set; } = Array.Empty<ITaskItem>();
|
|
public string ExpectedLanguage { get; set; }
|
|
public string MinimumTargetState { get; set; } = "final";
|
|
public bool RequireVerifiedContract { get; set; }
|
|
[Output] public ITaskItem[] PreparedLanguageFiles { get; set; } = Array.Empty<ITaskItem>();
|
|
}
|
|
public sealed class PrepareLanguagePackageTask : IsolatedBuildTask
|
|
{
|
|
[Required] public string PackageId { get; set; } = string.Empty;
|
|
[Required] public ITaskItem[] LanguageFiles { get; set; } = Array.Empty<ITaskItem>();
|
|
public ITaskItem[] SourceLanguageFiles { get; set; } = Array.Empty<ITaskItem>();
|
|
public ITaskItem[] PackageFiles { get; set; } = Array.Empty<ITaskItem>();
|
|
[Required] public string OutputManifestPath { get; set; } = string.Empty;
|
|
public string ExpectedLanguage { get; set; }
|
|
public string MinimumTargetState { get; set; } = "final";
|
|
public bool RequireVerifiedContract { get; set; }
|
|
[Output] public ITaskItem[] PreparedLanguageFiles { get; set; } = Array.Empty<ITaskItem>();
|
|
}
|
|
public sealed class GenerateLanguagePackagePropsTask : IsolatedBuildTask
|
|
{
|
|
[Required] public string PackageId { get; set; } = string.Empty;
|
|
[Required] public ITaskItem[] LanguageFiles { get; set; } = Array.Empty<ITaskItem>();
|
|
[Required] public string OutputPath { get; set; } = string.Empty;
|
|
public bool RequireTargetLanguage { get; set; } = true;
|
|
public string SourceKind { get; set; } = "StaticLanguagePack";
|
|
}
|
|
}
|
|
|
|
#endif
|