57 changed files with 759 additions and 124 deletions
@ -0,0 +1,8 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<PropertyGroup> |
|||
<AvaloniaBuildTasksLocation>$(MSBuildThisFileDirectory)\..\src\Avalonia.Build.Tasks\bin\$(Configuration)\netstandard2.0\Avalonia.Build.Tasks.dll</AvaloniaBuildTasksLocation> |
|||
<AvaloniaUseExternalMSBuild>true</AvaloniaUseExternalMSBuild> |
|||
</PropertyGroup> |
|||
<Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.props"/> |
|||
<Import Project="$(MSBuildThisFileDirectory)\..\packages\Avalonia\AvaloniaBuildTasks.targets"/> |
|||
</Project> |
|||
@ -0,0 +1,3 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.targets"/> |
|||
</Project> |
|||
@ -0,0 +1,3 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,39 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<PropertyGroup> |
|||
<_AvaloniaUseExternalMSBuild>$(AvaloniaUseExternalMSBuild)</_AvaloniaUseExternalMSBuild> |
|||
<_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false</_AvaloniaUseExternalMSBuild> |
|||
</PropertyGroup> |
|||
|
|||
<UsingTask TaskName="GenerateAvaloniaResourcesTask" |
|||
AssemblyFile="$(AvaloniaBuildTasksLocation)" |
|||
/> |
|||
|
|||
|
|||
<Target Name="AddAvaloniaResources" BeforeTargets="ResolveReferences"> |
|||
<PropertyGroup> |
|||
<AvaloniaResourcesTemporaryFilePath Condition="'$(AvaloniaResourcesTemporaryFilePath)' == ''">$(IntermediateOutputPath)/Avalonia/resources</AvaloniaResourcesTemporaryFilePath> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<EmbeddedResource Include="$(AvaloniaResourcesTemporaryFilePath)"> |
|||
<LogicalName>!AvaloniaResources</LogicalName> |
|||
</EmbeddedResource> |
|||
</ItemGroup> |
|||
</Target> |
|||
|
|||
<Target Name="GenerateAvaloniaResources" |
|||
BeforeTargets="CoreCompile" |
|||
Inputs="@(AvaloniaResource);$(MSBuildProjectFile)" |
|||
Outputs="$(AvaloniaResourcesTemporaryFilePath)" |
|||
DependsOnTargets="$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources"> |
|||
<GenerateAvaloniaResourcesTask |
|||
Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'" |
|||
Output="$(AvaloniaResourcesTemporaryFilePath)" |
|||
Root="$(MSBuildProjectDirectory)" |
|||
Resources="@(AvaloniaResource)" |
|||
EmbeddedResources="@(EmbeddedResources)"/> |
|||
<Exec |
|||
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'" |
|||
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration)"/> |
|||
|
|||
</Target> |
|||
</Project> |
|||
@ -1,6 +1,8 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300" |
|||
Title="Avalonia Control Gallery" |
|||
Icon="resm:ControlCatalog.Assets.test_icon.ico?assembly=ControlCatalog" |
|||
xmlns:local="clr-namespace:ControlCatalog"> |
|||
Icon="/Assets/test_icon.ico?assembly=ControlCatalog" |
|||
xmlns:local="clr-namespace:ControlCatalog" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.MainWindow"> |
|||
<local:MainView/> |
|||
</Window> |
|||
</Window> |
|||
|
|||
@ -0,0 +1,58 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Serialization; |
|||
using System.Runtime.Serialization.Json; |
|||
|
|||
// ReSharper disable AssignNullToNotNullAttribute
|
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
#if !BUILDTASK
|
|||
public |
|||
#endif
|
|||
static class AvaloniaResourcesIndexReaderWriter |
|||
{ |
|||
private const int LastKnownVersion = 1; |
|||
public static List<AvaloniaResourcesIndexEntry> Read(Stream stream) |
|||
{ |
|||
var ver = new BinaryReader(stream).ReadInt32(); |
|||
if (ver > LastKnownVersion) |
|||
throw new Exception("Resources index format version is not known"); |
|||
var index = (AvaloniaResourcesIndex) |
|||
new DataContractSerializer(typeof(AvaloniaResourcesIndex)).ReadObject(stream); |
|||
return index.Entries; |
|||
} |
|||
|
|||
public static void Write(Stream stream, List<AvaloniaResourcesIndexEntry> entries) |
|||
{ |
|||
new BinaryWriter(stream).Write(LastKnownVersion); |
|||
new DataContractSerializer(typeof(AvaloniaResourcesIndex)).WriteObject(stream, |
|||
new AvaloniaResourcesIndex() |
|||
{ |
|||
Entries = entries |
|||
}); |
|||
} |
|||
} |
|||
|
|||
[DataContract] |
|||
public class AvaloniaResourcesIndex |
|||
{ |
|||
[DataMember] |
|||
public List<AvaloniaResourcesIndexEntry> Entries { get; set; } = new List<AvaloniaResourcesIndexEntry>(); |
|||
} |
|||
|
|||
[DataContract] |
|||
public class AvaloniaResourcesIndexEntry |
|||
{ |
|||
[DataMember] |
|||
public string Path { get; set; } |
|||
|
|||
[DataMember] |
|||
public int Offset { get; set; } |
|||
|
|||
[DataMember] |
|||
public int Size { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<BuildOutputTargetFolder>tools</BuildOutputTargetFolder> |
|||
<DefineConstants>$(DefineConstants);BUILDTASK</DefineConstants> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<Compile Include="../Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs"> |
|||
<Link>Shared/AvaloniaResourcesIndex.cs</Link> |
|||
</Compile> |
|||
<Compile Include="../Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaResourceXamlInfo.cs"> |
|||
<Link>Shared/AvaloniaResourceXamlInfo.cs</Link> |
|||
</Compile> |
|||
<PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" /> |
|||
</ItemGroup> |
|||
<!-- Disable built-in Pack target --> |
|||
<Target Name="Pack"/> |
|||
</Project> |
|||
@ -0,0 +1,19 @@ |
|||
using Microsoft.Build.Framework; |
|||
|
|||
namespace Avalonia.Build.Tasks |
|||
{ |
|||
public static class Extensions |
|||
{ |
|||
public static void LogError(this IBuildEngine engine, string file, string message) |
|||
{ |
|||
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "0000", file ?? "", 0, 0, 0, 0, message, "", |
|||
"Avalonia")); |
|||
} |
|||
|
|||
public static void LogWarning(this IBuildEngine engine, string file, string message) |
|||
{ |
|||
engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "0000", file ?? "", 0, 0, 0, 0, message, "", |
|||
"Avalonia")); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,158 @@ |
|||
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; } |
|||
|
|||
class Source |
|||
{ |
|||
public string Path { get; set; } |
|||
public int Size { get; set; } |
|||
private byte[] _data; |
|||
private string _sourcePath; |
|||
|
|||
public Source(string file, string root) |
|||
{ |
|||
file = SPath.GetFullPath(file); |
|||
root = SPath.GetFullPath(root); |
|||
var fileUri = new Uri(file, UriKind.Absolute); |
|||
var rootUri = new Uri(root, UriKind.Absolute); |
|||
rootUri = new Uri(rootUri.ToString().TrimEnd('/') + '/'); |
|||
Path = '/' + rootUri.MakeRelativeUri(fileUri).ToString().TrimStart('/'); |
|||
_sourcePath = file; |
|||
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 => new Source(r.ItemSpec, Root)).ToList(); |
|||
|
|||
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); |
|||
} |
|||
} |
|||
|
|||
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")) |
|||
{ |
|||
XamlFileInfo info; |
|||
try |
|||
{ |
|||
info = XamlFileInfo.Parse(s.ReadAsString()); |
|||
} |
|||
catch(Exception e) |
|||
{ |
|||
BuildEngine.LogError(s.SystemPath, "File doesn't contain valid XAML: " + e); |
|||
return false; |
|||
} |
|||
|
|||
if (info.XClass != null) |
|||
{ |
|||
if (typeToXamlIndex.ContainsKey(info.XClass)) |
|||
{ |
|||
|
|||
BuildEngine.LogError(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() |
|||
{ |
|||
foreach(var r in EmbeddedResources.Where(r=>r.ItemSpec.EndsWith(".xaml")||r.ItemSpec.EndsWith(".paml"))) |
|||
BuildEngine.LogWarning(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; } |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System.Xml.Linq; |
|||
|
|||
namespace Avalonia.Build.Tasks |
|||
{ |
|||
public class XamlFileInfo |
|||
{ |
|||
public string XClass { get; set; } |
|||
|
|||
public static XamlFileInfo Parse(string data) |
|||
{ |
|||
var xdoc = XDocument.Parse(data); |
|||
var xclass = xdoc.Root.Attribute(XName.Get("Class", "http://schemas.microsoft.com/winfx/2006/xaml")); |
|||
return new XamlFileInfo |
|||
{ |
|||
XClass = xclass?.Value |
|||
}; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Globalization; |
|||
|
|||
namespace Avalonia.Markup.Xaml.Converters |
|||
{ |
|||
public class AvaloniaUriTypeConverter : TypeConverter |
|||
{ |
|||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) |
|||
{ |
|||
return sourceType == typeof(string); |
|||
} |
|||
|
|||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) |
|||
{ |
|||
var s = value as string; |
|||
if (s == null) |
|||
return null; |
|||
//On Unix Uri tries to interpret paths starting with "/" as file Uris
|
|||
if (s.StartsWith("/")) |
|||
return new Uri(s, UriKind.Relative); |
|||
return new Uri(s); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System.Collections.Generic; |
|||
using System.Runtime.Serialization; |
|||
|
|||
namespace Avalonia.Markup.Xaml.PortableXaml |
|||
{ |
|||
[DataContract] |
|||
class AvaloniaResourceXamlInfo |
|||
{ |
|||
[DataMember] |
|||
public Dictionary<string, string> ClassToResourcePathIndex { get; set; } = new Dictionary<string, string>(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue