A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

170 lines
6.1 KiB

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using Avalonia.Markup.Xaml.PortableXaml;
using Avalonia.Utilities;
using Microsoft.Build.Framework;
using SPath=System.IO.Path;
namespace Avalonia.Build.Tasks
{
public class GenerateAvaloniaResourcesTask : ITask
{
[Required]
public ITaskItem[] Resources { get; set; }
[Required]
public string Root { get; set; }
[Required]
public string Output { get; set; }
[Required]
public ITaskItem[] EmbeddedResources { get; set; }
public string ReportImportance { get; set; }
private MessageImportance _reportImportance;
class Source
{
public string Path { get; set; }
public int Size { get; set; }
private byte[] _data;
private string _sourcePath;
public Source(string relativePath, string root)
{
root = SPath.GetFullPath(root);
Path = "/" + relativePath.Replace('\\', '/');
_sourcePath = SPath.Combine(root, relativePath);
Size = (int)new FileInfo(_sourcePath).Length;
}
public string SystemPath => _sourcePath ?? Path;
public Source(string path, byte[] data)
{
Path = path;
_data = data;
Size = data.Length;
}
public Stream Open()
{
if (_data != null)
return new MemoryStream(_data, false);
return File.OpenRead(_sourcePath);
}
public string ReadAsString()
{
if (_data != null)
return Encoding.UTF8.GetString(_data);
return File.ReadAllText(_sourcePath);
}
}
List<Source> BuildResourceSources()
=> Resources.Select(r =>
{
var src = new Source(r.ItemSpec, Root);
BuildEngine.LogMessage($"avares -> name:{src.Path}, path: {src.SystemPath}, size:{src.Size}, ItemSpec:{r.ItemSpec}", _reportImportance);
return src;
}).ToList();
private void Pack(Stream output, List<Source> sources)
{
var offsets = new Dictionary<Source, int>();
var coffset = 0;
foreach (var s in sources)
{
offsets[s] = coffset;
coffset += s.Size;
}
var index = sources.Select(s => new AvaloniaResourcesIndexEntry
{
Path = s.Path,
Size = s.Size,
Offset = offsets[s]
}).ToList();
var ms = new MemoryStream();
AvaloniaResourcesIndexReaderWriter.Write(ms, index);
new BinaryWriter(output).Write((int)ms.Length);
ms.Position = 0;
ms.CopyTo(output);
foreach (var s in sources)
{
using(var input = s.Open())
input.CopyTo(output);
}
}
private bool PreProcessXamlFiles(List<Source> sources)
{
var typeToXamlIndex = new Dictionary<string, string>();
foreach (var s in sources.ToList())
{
if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml") || s.Path.ToLowerInvariant().EndsWith(".axaml"))
{
XamlFileInfo info;
try
{
info = XamlFileInfo.Parse(s.ReadAsString());
}
catch(Exception e)
{
BuildEngine.LogError(BuildEngineErrorCode.InvalidXAML, s.SystemPath, "File doesn't contain valid XAML: " + e);
return false;
}
if (info.XClass != null)
{
if (typeToXamlIndex.ContainsKey(info.XClass))
{
BuildEngine.LogError(BuildEngineErrorCode.DuplicateXClass, s.SystemPath,
$"Duplicate x:Class directive, {info.XClass} is already used in {typeToXamlIndex[info.XClass]}");
return false;
}
typeToXamlIndex[info.XClass] = s.Path;
}
}
}
var xamlInfo = new AvaloniaResourceXamlInfo
{
ClassToResourcePathIndex = typeToXamlIndex
};
var ms = new MemoryStream();
new DataContractSerializer(typeof(AvaloniaResourceXamlInfo)).WriteObject(ms, xamlInfo);
sources.Add(new Source("/!AvaloniaResourceXamlInfo", ms.ToArray()));
return true;
}
public bool Execute()
{
Enum.TryParse<MessageImportance>(ReportImportance, out _reportImportance);
BuildEngine.LogMessage($"GenerateAvaloniaResourcesTask -> Root: {Root}, {Resources?.Count()} resources, Output:{Output}", _reportImportance < MessageImportance.Low ? MessageImportance.High : _reportImportance);
foreach (var r in EmbeddedResources.Where(r => r.ItemSpec.EndsWith(".xaml") || r.ItemSpec.EndsWith(".paml") || r.ItemSpec.EndsWith(".axaml")))
BuildEngine.LogWarning(BuildEngineErrorCode.LegacyResmScheme, r.ItemSpec,
"XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work");
var resources = BuildResourceSources();
if (!PreProcessXamlFiles(resources))
return false;
var dir = Path.GetDirectoryName(Output);
Directory.CreateDirectory(dir);
using (var file = File.Create(Output))
Pack(file, resources);
return true;
}
public IBuildEngine BuildEngine { get; set; }
public ITaskHost HostObject { get; set; }
}
}