committed by
GitHub
65 changed files with 813 additions and 148 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;CoreResGen" |
||||
|
Inputs="@(AvaloniaResource);$(MSBuildAllProjects)" |
||||
|
Outputs="$(AvaloniaResourcesTemporaryFilePath)" |
||||
|
DependsOnTargets="$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences"> |
||||
|
<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" |
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300" |
||||
Title="Avalonia Control Gallery" |
Title="Avalonia Control Gallery" |
||||
Icon="resm:ControlCatalog.Assets.test_icon.ico?assembly=ControlCatalog" |
Icon="/Assets/test_icon.ico" |
||||
xmlns:local="clr-namespace:ControlCatalog"> |
xmlns:local="clr-namespace:ControlCatalog" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:Class="ControlCatalog.MainWindow"> |
||||
<local:MainView/> |
<local:MainView/> |
||||
</Window> |
</Window> |
||||
|
|||||
@ -0,0 +1,64 @@ |
|||||
|
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] |
||||
|
#if !BUILDTASK
|
||||
|
public |
||||
|
#endif
|
||||
|
class AvaloniaResourcesIndex |
||||
|
{ |
||||
|
[DataMember] |
||||
|
public List<AvaloniaResourcesIndexEntry> Entries { get; set; } = new List<AvaloniaResourcesIndexEntry>(); |
||||
|
} |
||||
|
|
||||
|
[DataContract] |
||||
|
#if !BUILDTASK
|
||||
|
public |
||||
|
#endif
|
||||
|
class AvaloniaResourcesIndexEntry |
||||
|
{ |
||||
|
[DataMember] |
||||
|
public string Path { get; set; } |
||||
|
|
||||
|
[DataMember] |
||||
|
public int Offset { get; set; } |
||||
|
|
||||
|
[DataMember] |
||||
|
public int Size { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<BuildOutputTargetFolder>tools</BuildOutputTargetFolder> |
||||
|
<DefineConstants>$(DefineConstants);BUILDTASK</DefineConstants> |
||||
|
<Packable>false</Packable> |
||||
|
</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> |
||||
|
</Project> |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace Avalonia.Build.Tasks |
||||
|
{ |
||||
|
public enum BuildEngineErrorCode |
||||
|
{ |
||||
|
InvalidXAML = 1, |
||||
|
DuplicateXClass = 2, |
||||
|
LegacyResmScheme = 3, |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using Microsoft.Build.Framework; |
||||
|
|
||||
|
namespace Avalonia.Build.Tasks |
||||
|
{ |
||||
|
public static class Extensions |
||||
|
{ |
||||
|
static string FormatErrorCode(BuildEngineErrorCode code) => $"AVLN:{(int)code:0000}"; |
||||
|
|
||||
|
public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message) |
||||
|
{ |
||||
|
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message, |
||||
|
"", "Avalonia")); |
||||
|
} |
||||
|
|
||||
|
public static void LogWarning(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message) |
||||
|
{ |
||||
|
engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message, |
||||
|
"", "Avalonia")); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,159 @@ |
|||||
|
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(); |
||||
|
|
||||
|
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")) |
||||
|
{ |
||||
|
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() |
||||
|
{ |
||||
|
foreach(var r in EmbeddedResources.Where(r=>r.ItemSpec.EndsWith(".xaml")||r.ItemSpec.EndsWith(".paml"))) |
||||
|
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; } |
||||
|
} |
||||
|
} |
||||
@ -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