fix: Workaround Android/iOS HR issues

This commit is contained in:
Youssef Victor 2024-07-17 17:52:13 +03:00
Родитель 3bcdbcd368
Коммит b992b48512
6 изменённых файлов: 89 добавлений и 21 удалений

Просмотреть файл

@ -21,8 +21,15 @@ internal class HotReloadWorkspace
{
public record UpdateResult(ImmutableArray<Diagnostic> Diagnostics, ImmutableArray<WatchHotReloadService.Update> MetadataUpdates);
const string NetCoreCapsRaw = "Baseline AddMethodToExistingType AddStaticFieldToExistingType AddInstanceFieldToExistingType NewTypeDefinition ChangeCustomAttributes UpdateParameters";
const string MonoCapsRaw = "Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes";
#if NET8_0
const string NetCoreCapsRaw = "Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes AddInstanceFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod UpdateParameters GenericAddFieldToExistingType";
const string MonoCapsRaw = "Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes AddInstanceFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod UpdateParameters GenericAddFieldToExistingType";
#elif NET9_0
const string NetCoreCapsRaw = "Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes AddInstanceFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod UpdateParameters GenericAddFieldToExistingType";
const string MonoCapsRaw = "Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes AddInstanceFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod UpdateParameters GenericAddFieldToExistingType";
#else
#error Capabilities should be defined for the updated target framework.
#endif
private readonly string _baseWorkFolder;
private readonly bool _isDebugCompilation;
@ -371,7 +378,7 @@ internal class HotReloadWorkspace
#endif
var availableTargets = new[] {
Path.Combine("Uno.UI.Skia", configuration, "8.0"),
Path.Combine("Uno.UI.Skia", configuration, "net8.0"),
Path.Combine("Uno.UI.Reference", configuration, "net8.0"),
Path.Combine("Uno.UI.Tests", configuration, "net8.0"),
};

Просмотреть файл

@ -143,6 +143,8 @@ namespace Uno.UI.SourceGenerators.XamlGenerator
internal ImmutableArray<ITypeProvider> TypeProviders { get; }
internal bool IsSkia { get; }
public XamlCodeGeneration(GeneratorExecutionContext context)
{
// To easily debug XAML code generation:
@ -260,6 +262,7 @@ namespace Uno.UI.SourceGenerators.XamlGenerator
_defaultNamespace = context.GetMSBuildPropertyValue("RootNamespace");
_isWasm = context.GetMSBuildPropertyValue("DefineConstantsProperty")?.Contains("__WASM__") ?? false;
IsSkia = context.GetMSBuildPropertyValue("DefineConstantsProperty")?.Contains("__SKIA__") ?? false;
_isDesignTimeBuild = Helpers.DesignTimeHelper.IsDesignTime(context);
StringSymbol = GetSpecialTypeSymbolAsLazy(SpecialType.System_String);
@ -686,9 +689,10 @@ namespace Uno.UI.SourceGenerators.XamlGenerator
writer.AppendLineIndented("/// Contains all the static resources defined for the application");
writer.AppendLineIndented("/// </summary>");
if (_isDebug)
if (_isDebug && !IsSkia)
{
//writer.AppendLineIndented("[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdate]");
// On Skia, we don't use CreateNewOnMetadataUpdate at all.
writer.AppendLineIndented("[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdate]");
}
AnalyzerSuppressionsGenerator.Generate(writer, _analyzerSuppressions);

Просмотреть файл

@ -178,19 +178,19 @@ namespace Uno.UI.SourceGenerators.XamlGenerator
return IsType(xamlType, Generation.FrameworkElementSymbol.Value);
}
private bool IsAndroidView(XamlType xamlType)
private bool IsAndroidView(INamedTypeSymbol? type)
{
return IsType(xamlType, Generation.AndroidViewSymbol.Value);
return IsType(type, Generation.AndroidViewSymbol.Value);
}
private bool IsIOSUIView(XamlType xamlType)
private bool IsIOSUIView(INamedTypeSymbol? type)
{
return IsType(xamlType, Generation.IOSViewSymbol.Value);
return IsType(type, Generation.IOSViewSymbol.Value);
}
private bool IsMacOSNSView(XamlType xamlType)
private bool IsMacOSNSView(INamedTypeSymbol? type)
{
return IsType(xamlType, Generation.AppKitViewSymbol.Value);
return IsType(type, Generation.AppKitViewSymbol.Value);
}
private bool IsDependencyObject(XamlObjectDefinition component)
@ -205,7 +205,9 @@ namespace Uno.UI.SourceGenerators.XamlGenerator
/// <summary>
/// Is the type derived from the native view type on a Xamarin platform?
/// </summary>
private bool IsNativeView(XamlType xamlType) => IsAndroidView(xamlType) || IsIOSUIView(xamlType) || IsMacOSNSView(xamlType);
private bool IsNativeView(XamlType xamlType) => IsNativeView(FindType(xamlType));
private bool IsNativeView(INamedTypeSymbol? type) => IsAndroidView(type) || IsIOSUIView(type) || IsMacOSNSView(type);
/// <summary>
/// Is the type one of the base view types in WinUI? (UIElement is most commonly used to mean 'any WinUI view type,' but

Просмотреть файл

@ -372,7 +372,7 @@ namespace Uno.UI.SourceGenerators.XamlGenerator
var controlBaseType = GetType(topLevelControl.Type);
WriteMetadataNewTypeAttribute(writer);
WriteMetadataNewTypeAttributeOnMarkerIfNativeView(writer, controlBaseType);
using (writer.BlockInvariant("partial class {0} : {1}", _xClassName.ClassName, controlBaseType.GetFullyQualifiedTypeIncludingGlobal()))
{
@ -1396,9 +1396,35 @@ namespace Uno.UI.SourceGenerators.XamlGenerator
private void WriteMetadataNewTypeAttribute(IIndentedStringBuilder writer)
{
if (_isHotReloadEnabled)
if (_isHotReloadEnabled && !Generation.IsSkia)
{
//writer.AppendLineIndented("[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdate]");
// On Skia, we don't use CreateNewOnMetadataUpdate at all.
writer.AppendLineIndented("[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdate]");
}
}
private void WriteMetadataNewTypeAttributeOnMarkerIfNativeView(IIndentedStringBuilder writer, INamedTypeSymbol originalType)
{
if (!_isHotReloadEnabled || Generation.IsSkia)
{
return;
}
if (IsNativeView(originalType))
{
var originalTypeFullyQualified = originalType.GetFullyQualifiedTypeIncludingGlobal();
// On non-Skia, we add the attribute to the marker class when the type is a native view
writer.AppendLineIndented("[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdate]");
writer.AppendLineIndented($"[global::Uno.Foundation.UnoOriginalType(typeof({originalTypeFullyQualified}))]");
using (writer.BlockInvariant($"internal sealed class __Uno_HotReload_Marker_{originalType.Name}_{_fileDefinition.UniqueID}_{HashBuilder.Build(originalTypeFullyQualified)}"))
{
writer.AppendLineIndented($"private const string HashCode = \"{_fileDefinition.Checksum}\";");
}
}
else
{
// On non-Skia, we add the attribute directly if the type is not a native view
writer.AppendLineIndented("[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdate]");
}
}
@ -3824,12 +3850,13 @@ namespace Uno.UI.SourceGenerators.XamlGenerator
writer.AppendLineInvariantIndented("// UI automation id: {0}", uiAutomationId);
// ContentDescription and AccessibilityIdentifier are used by Xamarin.UITest (Test Cloud) to identify visual elements
if (IsAndroidView(parent.Type))
var parentType = FindType(parent.Type);
if (IsAndroidView(parentType))
{
writer.AppendLineInvariantIndented("{0}.ContentDescription = \"{1}\";", closureName, uiAutomationId);
};
if (IsIOSUIView(parent.Type))
if (IsIOSUIView(parentType))
{
writer.AppendLineInvariantIndented("{0}.AccessibilityIdentifier = \"{1}\";", closureName, uiAutomationId);
}

Просмотреть файл

@ -0,0 +1,18 @@
using System.ComponentModel;
namespace Uno.Foundation;
/// <summary>
/// This attribute is used by XAML generator for HotReload purposes.
/// External users should not use this attribute.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class UnoOriginalTypeAttribute : Attribute
{
public UnoOriginalTypeAttribute(Type type)
{
OriginalType = type;
}
public Type OriginalType { get; }
}

Просмотреть файл

@ -2,16 +2,17 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
using Uno.Extensions;
using Uno.Foundation;
using Uno.Foundation.Logging;
using Uno.UI.RemoteControl.HotReload.Messages;
using System.Runtime.Loader;
using System.Runtime.CompilerServices;
using System.Diagnostics;
namespace Uno.UI.RemoteControl.HotReload
{
@ -189,7 +190,7 @@ namespace Uno.UI.RemoteControl.HotReload
from type in asm.GetTypes()
let originalType = type.GetCustomAttribute<MetadataUpdateOriginalTypeAttribute>()
where originalType is not null
group type by originalType.OriginalType into g
group type by GetOriginalType(type, originalType.OriginalType) into g
select new
{
Key = g.Key.FullName,
@ -199,5 +200,14 @@ namespace Uno.UI.RemoteControl.HotReload
return mappedTypes.ToDictionary(p => p.Key, p => p.LastMapped);
}
private static Type GetOriginalType(Type type, Type originalType)
// Normally, HotReload should work by reading the original type from MetadataUpdateOriginalTypeAttribute.
// It's currently a problem on Android and iOS that a new type can't be emitted as it ends up with two managed types pointing to the same native type.
// For this reason, we create a fake marker class with CreateNewOnMetadataUpdate attribute.
// The marker class has UnoOriginalTypeAttribute which points to the real original type.
// So, when we update the marker class, we should use the OriginalType specified by it.
// Otherwise, use the original type from MetadataUpdateOriginalTypeAttribute
=> type.GetCustomAttribute<UnoOriginalTypeAttribute>()?.OriginalType ?? originalType;
}
}