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

Commit af2c1b0

Browse files
[X] Accept RD.Source with assembly (#13484)
this also always add the assembly part in the Source url - fixes #13187 - fixes #2660
1 parent 11b3525 commit af2c1b0

File tree

5 files changed

+78
-24
lines changed

5 files changed

+78
-24
lines changed

Xamarin.Forms.Build.Tasks/CompiledConverters/RDSourceTypeConverter.cs

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using Mono.Cecil;
45
using Mono.Cecil.Cil;
56
using Xamarin.Forms.Build.Tasks;
@@ -13,7 +14,7 @@ class RDSourceTypeConverter : ICompiledTypeConverter
1314
{
1415
public IEnumerable<Instruction> ConvertFromString(string value, ILContext context, BaseNode node)
1516
{
16-
var module = context.Body.Method.Module;
17+
var currentModule = context.Body.Method.Module;
1718
var body = context.Body;
1819

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

2324
var rdNode = node.Parent as IElementNode;
2425

25-
var rootTargetPath = XamlCTask.GetPathForType(module, ((ILRootNode)rootNode).TypeReference);
26+
var rootTargetPath = XamlCTask.GetPathForType(currentModule, ((ILRootNode)rootNode).TypeReference);
27+
28+
var module = currentModule;
29+
string asmName = null;
30+
if (value.Contains(";assembly="))
31+
{
32+
var parts = value.Split(new[] { ";assembly=" }, StringSplitOptions.RemoveEmptyEntries);
33+
value = parts[0];
34+
asmName = parts[1];
35+
if (currentModule.Assembly.Name.Name != asmName)
36+
{
37+
var ar = currentModule.AssemblyReferences.FirstOrDefault(ar => ar.Name == asmName);
38+
if (ar == null)
39+
throw new BuildException(BuildExceptionCode.ResourceMissing, node, null, value);
40+
module = currentModule.AssemblyResolver.Resolve(ar).MainModule;
41+
}
42+
}
2643
var uri = new Uri(value, UriKind.Relative);
2744

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

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

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

73+
if (!string.IsNullOrEmpty(asmName))
74+
{
75+
yield return Create(Ldstr, asmName);
76+
yield return Create(Call, currentModule.ImportMethodReference(("mscorlib", "System.Reflection", "Assembly"), methodName: "Load", parameterTypes: new[] { ("mscorlib", "System", "String") }, isStatic: true));
77+
}
78+
else //we could use assembly.Load in the 'else' part too, but I don't want to change working code right now
79+
{
80+
yield return Create(Ldtoken, currentModule.ImportReference(((ILRootNode)rootNode).TypeReference));
81+
yield return Create(Call, currentModule.ImportMethodReference(("mscorlib", "System", "Type"), methodName: "GetTypeFromHandle", parameterTypes: new[] { ("mscorlib", "System", "RuntimeTypeHandle") }, isStatic: true));
82+
yield return Create(Call, currentModule.ImportMethodReference(("mscorlib", "System.Reflection", "IntrospectionExtensions"), methodName: "GetTypeInfo", parameterTypes: new[] { ("mscorlib", "System", "Type") }, isStatic: true));
83+
yield return Create(Callvirt, currentModule.ImportPropertyGetterReference(("mscorlib", "System.Reflection", "TypeInfo"), propertyName: "Assembly", flatten: true));
84+
}
5585
foreach (var instruction in node.PushXmlLineInfo(context))
5686
yield return instruction; //lineinfo
57-
yield return Create(Callvirt, module.ImportMethodReference(resourceDictionaryType,
87+
yield return Create(Callvirt, currentModule.ImportMethodReference(resourceDictionaryType,
5888
methodName: "SetAndLoadSource",
5989
parameterTypes: new[] { ("System", "System", "Uri"), ("mscorlib", "System", "String"), ("mscorlib", "System.Reflection", "Assembly"), ("System.Xml.ReaderWriter", "System.Xml", "IXmlLineInfo") }));
6090
//ldloc the stored uri as return value
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<ResourceDictionary
3+
xmlns="http://xamarin.com/schemas/2014/forms"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
5+
<Color x:Key="notBlue">Red</Color>
6+
<Color x:Key="AccentColor">#FF4B14</Color>
7+
</ResourceDictionary>

Xamarin.Forms.Core/ResourceDictionary.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,10 +369,23 @@ object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceP
369369

370370
var lineInfo = (serviceProvider.GetService(typeof(Xaml.IXmlLineInfoProvider)) as Xaml.IXmlLineInfoProvider)?.XmlLineInfo;
371371
var rootTargetPath = XamlResourceIdAttribute.GetPathForType(rootObjectType);
372+
var assembly = rootObjectType.GetTypeInfo().Assembly;
373+
#if NETSTANDARD2_0
374+
if (value.Contains(";assembly="))
375+
{
376+
var parts = value.Split(new[] { ";assembly=" }, StringSplitOptions.RemoveEmptyEntries);
377+
value = parts[0];
378+
var asmName = parts[1];
379+
assembly = Assembly.Load(asmName);
380+
}
381+
#endif
372382
var uri = new Uri(value, UriKind.Relative); //we don't want file:// uris, even if they start with '/'
373383
var resourcePath = GetResourcePath(uri, rootTargetPath);
374384

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

Xamarin.Forms.Xaml.UnitTests/ResourceDictionaryWithSource.xaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
<ResourceDictionary Source="SharedResourceDictionary.xaml" x:Key="shortURI"/>
1111
<ResourceDictionary Source="./AppResources/Colors.xaml" x:Key="Colors" />
1212
<ResourceDictionary Source="./AppResources/CompiledColors.xaml" x:Key="CompiledColors" />
13+
<ResourceDictionary Source="/AppResources/Colors.xaml;assembly=Xamarin.Forms.Xaml.UnitTests" x:Key="inCurrentAssembly" />
14+
<ResourceDictionary Source="/AppResources.xaml;assembly=Xamarin.Forms.Controls" x:Key="inOtherAssembly" />
1315
</ResourceDictionary>
1416
</ContentPage.Resources>
1517
<Label x:Name="label" Style="{StaticResource sharedfoo}"/>

Xamarin.Forms.Xaml.UnitTests/ResourceDictionaryWithSource.xaml.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,8 @@ public ResourceDictionaryWithSource(bool useCompiledXaml)
1919
[TestFixture]
2020
public class Tests
2121
{
22-
[SetUp]
23-
public void Setup()
24-
{
25-
Device.PlatformServices = new MockPlatformServices();
26-
}
27-
28-
[TearDown]
29-
public void TearDown()
30-
{
31-
Device.PlatformServices = null;
32-
}
22+
[SetUp] public void Setup() => Device.PlatformServices = new MockPlatformServices();
23+
[TearDown] public void TearDown() => Device.PlatformServices = null;
3324

3425
[TestCase(false), TestCase(true)]
3526
public void RDWithSourceAreFound(bool useCompiledXaml)
@@ -42,11 +33,11 @@ public void RDWithSourceAreFound(bool useCompiledXaml)
4233
public void RelativeAndAbsoluteURI(bool useCompiledXaml)
4334
{
4435
var layout = new ResourceDictionaryWithSource(useCompiledXaml);
45-
Assert.That(((ResourceDictionary)layout.Resources["relURI"]).Source, Is.EqualTo(new Uri("./SharedResourceDictionary.xaml", UriKind.Relative)));
36+
Assert.That(((ResourceDictionary)layout.Resources["relURI"]).Source, Is.EqualTo(new Uri("./SharedResourceDictionary.xaml;assembly=Xamarin.Forms.Xaml.UnitTests", UriKind.Relative)));
4637
Assert.That(((ResourceDictionary)layout.Resources["relURI"])["sharedfoo"], Is.TypeOf<Style>());
47-
Assert.That(((ResourceDictionary)layout.Resources["absURI"]).Source, Is.EqualTo(new Uri("/SharedResourceDictionary.xaml", UriKind.Relative)));
38+
Assert.That(((ResourceDictionary)layout.Resources["absURI"]).Source, Is.EqualTo(new Uri("/SharedResourceDictionary.xaml;assembly=Xamarin.Forms.Xaml.UnitTests", UriKind.Relative)));
4839
Assert.That(((ResourceDictionary)layout.Resources["absURI"])["sharedfoo"], Is.TypeOf<Style>());
49-
Assert.That(((ResourceDictionary)layout.Resources["shortURI"]).Source, Is.EqualTo(new Uri("SharedResourceDictionary.xaml", UriKind.Relative)));
40+
Assert.That(((ResourceDictionary)layout.Resources["shortURI"]).Source, Is.EqualTo(new Uri("SharedResourceDictionary.xaml;assembly=Xamarin.Forms.Xaml.UnitTests", UriKind.Relative)));
5041
Assert.That(((ResourceDictionary)layout.Resources["shortURI"])["sharedfoo"], Is.TypeOf<Style>());
5142
Assert.That(((ResourceDictionary)layout.Resources["Colors"])["MediumGrayTextColor"], Is.TypeOf<Color>());
5243
Assert.That(((ResourceDictionary)layout.Resources["CompiledColors"])["MediumGrayTextColor"], Is.TypeOf<Color>());
@@ -73,6 +64,17 @@ public void CodeBehindIsGeneratedForRDWithXamlComp()
7364
var rd = Activator.CreateInstance(type);
7465
Assert.That(rd as ResourceDictionary, Is.Not.Null);
7566
}
67+
68+
[TestCase(false), TestCase(true)]
69+
public void LoadResourcesWithAssembly(bool useCompiledXaml)
70+
{
71+
var layout = new ResourceDictionaryWithSource(useCompiledXaml);
72+
Assert.That(((ResourceDictionary)layout.Resources["inCurrentAssembly"]).Source, Is.EqualTo(new Uri("/AppResources/Colors.xaml;assembly=Xamarin.Forms.Xaml.UnitTests", UriKind.Relative)));
73+
Assert.That(((ResourceDictionary)layout.Resources["inCurrentAssembly"])["MediumGrayTextColor"], Is.TypeOf<Color>());
74+
Assert.That(((ResourceDictionary)layout.Resources["inOtherAssembly"]).Source, Is.EqualTo(new Uri("/AppResources.xaml;assembly=Xamarin.Forms.Controls", UriKind.Relative)));
75+
Assert.That(((ResourceDictionary)layout.Resources["inOtherAssembly"])["notBlue"], Is.TypeOf<Color>());
76+
}
77+
7678
}
7779
}
7880
}

0 commit comments

Comments
 (0)