Skip to content

Commit

Permalink
fix: Workaround Android/iOS HR issues
Browse files Browse the repository at this point in the history
  • Loading branch information
Youssef1313 committed Jul 17, 2024
1 parent 3bcdbcd commit b992b48
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -371,7 +378,7 @@ private static PortableExecutableReference[] BuildUnoReferences()
#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"),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ private bool IsUnoFluentAssembly

internal ImmutableArray<ITypeProvider> TypeProviders { get; }

internal bool IsSkia { get; }

public XamlCodeGeneration(GeneratorExecutionContext context)
{
// To easily debug XAML code generation:
Expand Down Expand Up @@ -260,6 +262,7 @@ public XamlCodeGeneration(GeneratorExecutionContext context)
_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);
Expand Down Expand Up @@ -686,9 +689,10 @@ private SourceText GenerateGlobalResources(IEnumerable<XamlFileDefinition> files
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,19 +178,19 @@ private bool IsFrameworkElement(XamlType xamlType)
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)
Expand All @@ -205,7 +205,9 @@ private bool IsUIElement(INamedTypeSymbol? symbol)
/// <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ private SourceText InnerGenerateFile()

var controlBaseType = GetType(topLevelControl.Type);

WriteMetadataNewTypeAttribute(writer);
WriteMetadataNewTypeAttributeOnMarkerIfNativeView(writer, controlBaseType);

using (writer.BlockInvariant("partial class {0} : {1}", _xClassName.ClassName, controlBaseType.GetFullyQualifiedTypeIncludingGlobal()))
{
Expand Down Expand Up @@ -1396,9 +1396,35 @@ IDisposable WrapSingleton()

private void WriteMetadataNewTypeAttribute(IIndentedStringBuilder writer)
{
if (_isHotReloadEnabled)
if (_isHotReloadEnabled && !Generation.IsSkia)
{
// 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
{
//writer.AppendLineIndented("[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdate]");
// On non-Skia, we add the attribute directly if the type is not a native view
writer.AppendLineIndented("[global::System.Runtime.CompilerServices.CreateNewOnMetadataUpdate]");
}
}

Expand Down Expand Up @@ -3824,12 +3850,13 @@ private void BuildUiAutomationId(IIndentedStringBuilder writer, string closureNa
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);
}
Expand Down
18 changes: 18 additions & 0 deletions src/Uno.Foundation/Internal/UnoOriginalTypeAttribute.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -189,7 +190,7 @@ where debuggableAttribute is not null
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,
Expand All @@ -199,5 +200,14 @@ group type by originalType.OriginalType into g

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;
}
}

0 comments on commit b992b48

Please sign in to comment.