Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
[X] Accept RD.Source with assembly (#13484)
Browse files Browse the repository at this point in the history
this also always add the assembly part in the Source url

- fixes #13187
- fixes #2660
  • Loading branch information
StephaneDelcroix committed Jan 22, 2021
1 parent 11b3525 commit af2c1b0
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Xamarin.Forms.Build.Tasks;
Expand All @@ -13,7 +14,7 @@ class RDSourceTypeConverter : ICompiledTypeConverter
{
public IEnumerable<Instruction> ConvertFromString(string value, ILContext context, BaseNode node)
{
var module = context.Body.Method.Module;
var currentModule = context.Body.Method.Module;
var body = context.Body;

INode rootNode = node;
Expand All @@ -22,7 +23,23 @@ public IEnumerable<Instruction> ConvertFromString(string value, ILContext contex

var rdNode = node.Parent as IElementNode;

var rootTargetPath = XamlCTask.GetPathForType(module, ((ILRootNode)rootNode).TypeReference);
var rootTargetPath = XamlCTask.GetPathForType(currentModule, ((ILRootNode)rootNode).TypeReference);

var module = currentModule;
string asmName = null;
if (value.Contains(";assembly="))
{
var parts = value.Split(new[] { ";assembly=" }, StringSplitOptions.RemoveEmptyEntries);
value = parts[0];
asmName = parts[1];

This comment has been minimized.

Copy link
@Tommigun1980

Tommigun1980 Jan 23, 2021

Wouldn’t it be better to just ignore the assembly definition if a value is not specified, instead of crashing? I think it’s valid to specify ‘;assembly=;’, no? This also assumes there are no additional properties in the uri and assembly is the last one, so to make it foolproof it’d need to be split by ‘;’ instead and traversing the splits.

if (currentModule.Assembly.Name.Name != asmName)
{
var ar = currentModule.AssemblyReferences.FirstOrDefault(ar => ar.Name == asmName);
if (ar == null)
throw new BuildException(BuildExceptionCode.ResourceMissing, node, null, value);
module = currentModule.AssemblyResolver.Resolve(ar).MainModule;
}
}
var uri = new Uri(value, UriKind.Relative);

var resourcePath = ResourceDictionary.RDSourceTypeConverter.GetResourcePath(uri, rootTargetPath);
Expand All @@ -36,25 +53,38 @@ public IEnumerable<Instruction> ConvertFromString(string value, ILContext contex

//abuse the converter, produce some side effect, but leave the stack untouched
//public void SetAndLoadSource(Uri value, string resourceID, Assembly assembly, System.Xml.IXmlLineInfo lineInfo)
foreach (var instruction in context.Variables[rdNode].LoadAs(module.GetTypeDefinition(resourceDictionaryType), module))
foreach (var instruction in context.Variables[rdNode].LoadAs(currentModule.GetTypeDefinition(resourceDictionaryType), currentModule))
yield return instruction;
//reappend assembly= in all cases, see other RD converter
if (!string.IsNullOrEmpty(asmName))
value = $"{value};assembly={asmName}";
else
value = $"{value};assembly={((ILRootNode)rootNode).TypeReference.Module.Assembly.Name.Name}";
foreach (var instruction in (new UriTypeConverter()).ConvertFromString(value, context, node))
yield return instruction; //the Uri

//keep the Uri for later
yield return Create(Dup);
var uriVarDef = new VariableDefinition(module.ImportReference(("System", "System", "Uri")));
var uriVarDef = new VariableDefinition(currentModule.ImportReference(("System", "System", "Uri")));
body.Variables.Add(uriVarDef);
yield return Create(Stloc, uriVarDef);
yield return Create(Ldstr, resourcePath); //resourcePath
yield return Create(Ldtoken, module.ImportReference(((ILRootNode)rootNode).TypeReference));
yield return Create(Call, module.ImportMethodReference(("mscorlib", "System", "Type"), methodName: "GetTypeFromHandle", parameterTypes: new[] { ("mscorlib", "System", "RuntimeTypeHandle") }, isStatic: true));
yield return Create(Call, module.ImportMethodReference(("mscorlib", "System.Reflection", "IntrospectionExtensions"), methodName: "GetTypeInfo", parameterTypes: new[] { ("mscorlib", "System", "Type") }, isStatic: true));
yield return Create(Callvirt, module.ImportPropertyGetterReference(("mscorlib", "System.Reflection", "TypeInfo"), propertyName: "Assembly", flatten: true));

if (!string.IsNullOrEmpty(asmName))
{
yield return Create(Ldstr, asmName);
yield return Create(Call, currentModule.ImportMethodReference(("mscorlib", "System.Reflection", "Assembly"), methodName: "Load", parameterTypes: new[] { ("mscorlib", "System", "String") }, isStatic: true));
}
else //we could use assembly.Load in the 'else' part too, but I don't want to change working code right now
{
yield return Create(Ldtoken, currentModule.ImportReference(((ILRootNode)rootNode).TypeReference));
yield return Create(Call, currentModule.ImportMethodReference(("mscorlib", "System", "Type"), methodName: "GetTypeFromHandle", parameterTypes: new[] { ("mscorlib", "System", "RuntimeTypeHandle") }, isStatic: true));
yield return Create(Call, currentModule.ImportMethodReference(("mscorlib", "System.Reflection", "IntrospectionExtensions"), methodName: "GetTypeInfo", parameterTypes: new[] { ("mscorlib", "System", "Type") }, isStatic: true));
yield return Create(Callvirt, currentModule.ImportPropertyGetterReference(("mscorlib", "System.Reflection", "TypeInfo"), propertyName: "Assembly", flatten: true));
}
foreach (var instruction in node.PushXmlLineInfo(context))
yield return instruction; //lineinfo
yield return Create(Callvirt, module.ImportMethodReference(resourceDictionaryType,
yield return Create(Callvirt, currentModule.ImportMethodReference(resourceDictionaryType,
methodName: "SetAndLoadSource",
parameterTypes: new[] { ("System", "System", "Uri"), ("mscorlib", "System", "String"), ("mscorlib", "System.Reflection", "Assembly"), ("System.Xml.ReaderWriter", "System.Xml", "IXmlLineInfo") }));
//ldloc the stored uri as return value
Expand Down
7 changes: 7 additions & 0 deletions Xamarin.Forms.Controls/AppResources.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ResourceDictionary
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<Color x:Key="notBlue">Red</Color>
<Color x:Key="AccentColor">#FF4B14</Color>
</ResourceDictionary>
15 changes: 14 additions & 1 deletion Xamarin.Forms.Core/ResourceDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,10 +369,23 @@ object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceP

var lineInfo = (serviceProvider.GetService(typeof(Xaml.IXmlLineInfoProvider)) as Xaml.IXmlLineInfoProvider)?.XmlLineInfo;
var rootTargetPath = XamlResourceIdAttribute.GetPathForType(rootObjectType);
var assembly = rootObjectType.GetTypeInfo().Assembly;
#if NETSTANDARD2_0
if (value.Contains(";assembly="))
{
var parts = value.Split(new[] { ";assembly=" }, StringSplitOptions.RemoveEmptyEntries);
value = parts[0];
var asmName = parts[1];
assembly = Assembly.Load(asmName);
}
#endif
var uri = new Uri(value, UriKind.Relative); //we don't want file:// uris, even if they start with '/'
var resourcePath = GetResourcePath(uri, rootTargetPath);

targetRD.SetAndLoadSource(uri, resourcePath, rootObjectType.GetTypeInfo().Assembly, lineInfo);
//Re-add the assembly= in all cases, so HotReload doesn't have to make assumptions
uri = new Uri($"{value};assembly={assembly.GetName().Name}", UriKind.Relative);
targetRD.SetAndLoadSource(uri, resourcePath, assembly, lineInfo);

return uri;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<ResourceDictionary Source="SharedResourceDictionary.xaml" x:Key="shortURI"/>
<ResourceDictionary Source="./AppResources/Colors.xaml" x:Key="Colors" />
<ResourceDictionary Source="./AppResources/CompiledColors.xaml" x:Key="CompiledColors" />
<ResourceDictionary Source="/AppResources/Colors.xaml;assembly=Xamarin.Forms.Xaml.UnitTests" x:Key="inCurrentAssembly" />
<ResourceDictionary Source="/AppResources.xaml;assembly=Xamarin.Forms.Controls" x:Key="inOtherAssembly" />
</ResourceDictionary>
</ContentPage.Resources>
<Label x:Name="label" Style="{StaticResource sharedfoo}"/>
Expand Down
30 changes: 16 additions & 14 deletions Xamarin.Forms.Xaml.UnitTests/ResourceDictionaryWithSource.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,8 @@ public ResourceDictionaryWithSource(bool useCompiledXaml)
[TestFixture]
public class Tests
{
[SetUp]
public void Setup()
{
Device.PlatformServices = new MockPlatformServices();
}

[TearDown]
public void TearDown()
{
Device.PlatformServices = null;
}
[SetUp] public void Setup() => Device.PlatformServices = new MockPlatformServices();
[TearDown] public void TearDown() => Device.PlatformServices = null;

[TestCase(false), TestCase(true)]
public void RDWithSourceAreFound(bool useCompiledXaml)
Expand All @@ -42,11 +33,11 @@ public void RDWithSourceAreFound(bool useCompiledXaml)
public void RelativeAndAbsoluteURI(bool useCompiledXaml)
{
var layout = new ResourceDictionaryWithSource(useCompiledXaml);
Assert.That(((ResourceDictionary)layout.Resources["relURI"]).Source, Is.EqualTo(new Uri("./SharedResourceDictionary.xaml", UriKind.Relative)));
Assert.That(((ResourceDictionary)layout.Resources["relURI"]).Source, Is.EqualTo(new Uri("./SharedResourceDictionary.xaml;assembly=Xamarin.Forms.Xaml.UnitTests", UriKind.Relative)));
Assert.That(((ResourceDictionary)layout.Resources["relURI"])["sharedfoo"], Is.TypeOf<Style>());
Assert.That(((ResourceDictionary)layout.Resources["absURI"]).Source, Is.EqualTo(new Uri("/SharedResourceDictionary.xaml", UriKind.Relative)));
Assert.That(((ResourceDictionary)layout.Resources["absURI"]).Source, Is.EqualTo(new Uri("/SharedResourceDictionary.xaml;assembly=Xamarin.Forms.Xaml.UnitTests", UriKind.Relative)));
Assert.That(((ResourceDictionary)layout.Resources["absURI"])["sharedfoo"], Is.TypeOf<Style>());
Assert.That(((ResourceDictionary)layout.Resources["shortURI"]).Source, Is.EqualTo(new Uri("SharedResourceDictionary.xaml", UriKind.Relative)));
Assert.That(((ResourceDictionary)layout.Resources["shortURI"]).Source, Is.EqualTo(new Uri("SharedResourceDictionary.xaml;assembly=Xamarin.Forms.Xaml.UnitTests", UriKind.Relative)));
Assert.That(((ResourceDictionary)layout.Resources["shortURI"])["sharedfoo"], Is.TypeOf<Style>());
Assert.That(((ResourceDictionary)layout.Resources["Colors"])["MediumGrayTextColor"], Is.TypeOf<Color>());
Assert.That(((ResourceDictionary)layout.Resources["CompiledColors"])["MediumGrayTextColor"], Is.TypeOf<Color>());
Expand All @@ -73,6 +64,17 @@ public void CodeBehindIsGeneratedForRDWithXamlComp()
var rd = Activator.CreateInstance(type);
Assert.That(rd as ResourceDictionary, Is.Not.Null);
}

[TestCase(false), TestCase(true)]
public void LoadResourcesWithAssembly(bool useCompiledXaml)
{
var layout = new ResourceDictionaryWithSource(useCompiledXaml);
Assert.That(((ResourceDictionary)layout.Resources["inCurrentAssembly"]).Source, Is.EqualTo(new Uri("/AppResources/Colors.xaml;assembly=Xamarin.Forms.Xaml.UnitTests", UriKind.Relative)));
Assert.That(((ResourceDictionary)layout.Resources["inCurrentAssembly"])["MediumGrayTextColor"], Is.TypeOf<Color>());
Assert.That(((ResourceDictionary)layout.Resources["inOtherAssembly"]).Source, Is.EqualTo(new Uri("/AppResources.xaml;assembly=Xamarin.Forms.Controls", UriKind.Relative)));
Assert.That(((ResourceDictionary)layout.Resources["inOtherAssembly"])["notBlue"], Is.TypeOf<Color>());
}

}
}
}

0 comments on commit af2c1b0

Please sign in to comment.