Skip to content

Commit

Permalink
fix: Adjust support for single-instance generator
Browse files Browse the repository at this point in the history
The change take into account the fact that generators may be instanciated only once per solution build, causing incorrect reuse of generation run across the solution. Runs and hashes are now segmented per targetframework and project file.

The clearing of runs is also restored as a new build initiated by VS needs to be detected as the base line for hot reload.
  • Loading branch information
jeromelaban committed Mar 1, 2023
1 parent 64a4eac commit 864a27e
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
)
)">false</UnoForceHotReloadCodeGen>

<!-- Use Application.LoadComponent for Hot Reload scenarios -->
<UnoUseXamlReaderHotReload>true</UnoUseXamlReaderHotReload>

<XamarinProjectType Condition="
'$(ProjectTypeGuids)'=='{FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}'
or $(TargetFramework.ToLower().StartsWith('xamarinios'))
Expand Down Expand Up @@ -426,6 +429,8 @@
<Message Importance="low"
Condition="'$(UnoUIUseRoslynSourceGenerators)'==''"
Text="Uno.UI is using Uno.SourceGenerators" />

<WriteLinesToFile File="$(IntermediateOutputPath)\build-time-generator.touch" />
</Target>

<Target Name="_RemoveRoslynUnoSourceGeneration" BeforeTargets="CoreCompile;XamlPreCompile" Condition="'$(UnoUIUseRoslynSourceGenerators)'=='false' or '$(ShouldRunGenerator)'!='true'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Uno.UI.SourceGenerators.XamlGenerator
{
internal record GenerationRunInfo(GenerationRunInfoManager Manager, int AdditionalFilesHash)
internal record GenerationRunInfo(GenerationRunInfoManager Manager, string ProjectFile, string TargetFramework, int AdditionalFilesHash)
{
private ConcurrentDictionary<string, GenerationRunFileInfo> _fileInfo = new();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,33 @@ namespace Uno.UI.SourceGenerators.XamlGenerator
internal class GenerationRunInfoManager
{
private List<GenerationRunInfo> _runs = new List<GenerationRunInfo>();
private Dictionary<(string projectFile, string targetFramework), DateTimeOffset> _previousWriteTime = new();

/// <summary>
/// Definition of a hash information based on a project and target framework. This is needed
/// since generators can be instantiated only once and kept active in VBCSCompiler.
/// </summary>
private record class HashInfo(string ProjectFile, string TargetFramework, int Hash);

/// <summary>
/// A list of known hashes for the current process to avoid removing previously
/// generated hashes and break Roslyn's metadata generator with inconsistent missing
/// methods.
/// </summary>
private static ConcurrentDictionary<int, object?> _knownAdditionalFilesHashes = new();
private static ConcurrentDictionary<HashInfo, object?> _knownAdditionalFilesHashes = new();

internal GenerationRunInfoManager()
{
foreach (var hash in _knownAdditionalFilesHashes.ToArray())
{
_runs.Add(new(this, hash.Key));
_runs.Add(new(this, hash.Key.ProjectFile, hash.Key.TargetFramework, hash.Key.Hash));
}
}

public IEnumerable<GenerationRunInfo> AllRuns
=> _runs.AsEnumerable();
public IEnumerable<GenerationRunInfo> GetAllRuns(GenerationRunInfo currentRun)
=> _runs
.Where(r => r.TargetFramework == currentRun.TargetFramework && r.ProjectFile == currentRun.ProjectFile)
.AsEnumerable();

internal GenerationRunInfo CreateRun(GeneratorExecutionContext context)
{
Expand All @@ -62,11 +71,14 @@ internal GenerationRunInfo CreateRun(GeneratorExecutionContext context)
}
else
{
var runInfo = new GenerationRunInfo(this, hash);
var projectFullPath = context.GetMSBuildPropertyValue("MSBuildProjectFullPath");
var targetFramework = context.GetMSBuildPropertyValue("TargetFramework");

var runInfo = new GenerationRunInfo(this, projectFullPath, targetFramework, hash);

_runs.Add(runInfo);

_knownAdditionalFilesHashes.TryAdd(hash, null);
_knownAdditionalFilesHashes.TryAdd(new(projectFullPath, targetFramework, hash), null);

return runInfo;
}
Expand All @@ -88,5 +100,72 @@ private static void ReadProjectConfiguration(GeneratorExecutionContext context,
useHotReload = string.Equals(configuration, "Debug", StringComparison.OrdinalIgnoreCase);
}
}

internal void Update(GeneratorExecutionContext context)
{
var intermediateOutputPath = context.GetMSBuildPropertyValue("IntermediateOutputPath");
var projectFullPath = context.GetMSBuildPropertyValue("MSBuildProjectFullPath");
var targetFramework = context.GetMSBuildPropertyValue("TargetFramework");

if (intermediateOutputPath != null)
{
intermediateOutputPath = Path.IsPathRooted(intermediateOutputPath)
? intermediateOutputPath
: Path.Combine(Path.GetDirectoryName(projectFullPath)!, intermediateOutputPath);

var runFilePath = Path.Combine(intermediateOutputPath, "build-time-generator.touch");

if (File.Exists(runFilePath))
{
var lastWriteTime = new FileInfo(runFilePath).LastWriteTime;
var writeTimeKey = (projectFullPath, targetFramework);

lock (_previousWriteTime)
{
_ = _previousWriteTime.TryGetValue(writeTimeKey, out var previousWriteTime);

if (lastWriteTime > previousWriteTime)
{
_previousWriteTime[writeTimeKey] = lastWriteTime;

// Clear the existing runs if a full build has been started
ClearRuns(projectFullPath, targetFramework);
}
}
}
}

if (GetIsDesignTimeBuild(context)
&& !GetIsHotReloadHost(context)
&& !Process.GetCurrentProcess().ProcessName.Equals("devenv", StringComparison.InvariantCultureIgnoreCase))
{
// Design-time builds need to clear runs for the x:Name values to be regenerated, in the context of OmniSharp.
// In the context of HotReload, we need to skip this, as the HotReload service sets DesignTimeBuild to build
// to true, preventing existing runs to be kept active.
//
// Devenv is also added to the conditions as there's no explicit way
// for knowing that we're in a hot-reload session.
//
ClearRuns(projectFullPath, targetFramework);
}
}

private void ClearRuns(string projectFullPath, string targetFramework)
=> _runs.Remove(r => r.TargetFramework == targetFramework && r.ProjectFile == projectFullPath);

private bool GetIsDesignTimeBuild(GeneratorExecutionContext context)
=> bool.TryParse(context.GetMSBuildPropertyValue("DesignTimeBuild"), out var value) && value;

private bool GetIsHotReloadHost(GeneratorExecutionContext context)
=> bool.TryParse(context.GetMSBuildPropertyValue("IsHotReloadHost"), out var value) && value;

internal bool IsFirstRun(GenerationRunFileInfo generationRunFileInfo)
=> GetAllRuns(generationRunFileInfo.RunInfo).Count() == 1;

internal GenerationRunInfo GetFirstValidRun(GenerationRunFileInfo generationRunFileInfo, string fileUniqueId)
=> GetAllRuns(generationRunFileInfo.RunInfo).FirstOrDefault(r => r.GetRunFileInfo(fileUniqueId)?.ComponentCode != null);

internal IEnumerable<GenerationRunInfo> GetAllRunsWithoutSelf(GenerationRunFileInfo generationRunFileInfo)
=> GetAllRuns(generationRunFileInfo.RunInfo).Except(generationRunFileInfo.RunInfo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public void Execute(GeneratorExecutionContext context)
{
if (PlatformHelper.IsValidPlatform(context))
{
_generationRunInfoManager.Update(context);

var gen = new XamlCodeGeneration(context);
var generatedTrees = gen.Generate(_generationRunInfoManager.CreateRun(context));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ private string InnerGenerateFile()
{
_isTopLevelDictionary = true;

if (_generationRunFileInfo.RunInfo.Manager.AllRuns.None() || !_useXamlReaderHotReload)
if (_generationRunFileInfo.RunInfo.Manager.IsFirstRun(_generationRunFileInfo) || !_useXamlReaderHotReload)
{
// On the first run, or if XamlReader hot reload is disabled, generate the full code.

Expand All @@ -428,7 +428,7 @@ private string InnerGenerateFile()
else
{
// if XamlReader hot reload is enabled, generate partial code
if (_generationRunFileInfo.RunInfo.Manager.AllRuns.FirstOrDefault(r => r.GetRunFileInfo(_fileUniqueId)?.ComponentCode != null) is { } runFileInfo)
if (_generationRunFileInfo.RunInfo.Manager.GetFirstValidRun(_generationRunFileInfo, _fileUniqueId) is { } runFileInfo)
{
var generationRunFileInfo = runFileInfo.GetRunFileInfo(_fileUniqueId);

Expand Down Expand Up @@ -458,7 +458,7 @@ private string InnerGenerateFile()

using (Scope(_xClassName.Namespace, _xClassName.ClassName))
{
if (_generationRunFileInfo.RunInfo.Manager.AllRuns.None() || !_useXamlReaderHotReload)
if (_generationRunFileInfo.RunInfo.Manager.IsFirstRun(_generationRunFileInfo) || !_useXamlReaderHotReload)
{
var componentBuilder = new IndentedStringBuilder();

Expand Down Expand Up @@ -496,7 +496,7 @@ private string InnerGenerateFile()
}
else
{
if (_generationRunFileInfo.RunInfo.Manager.AllRuns.FirstOrDefault(r => r.GetRunFileInfo(_fileUniqueId)?.ComponentCode != null) is { } runFileInfo)
if (_generationRunFileInfo.RunInfo.Manager.GetFirstValidRun(_generationRunFileInfo, _fileUniqueId) is { } runFileInfo)
{
var generationRunFileInfo = runFileInfo.GetRunFileInfo(_fileUniqueId);

Expand Down Expand Up @@ -544,7 +544,7 @@ private void BuildInitializeComponent(IndentedStringBuilder writer, XamlObjectDe

if (_isHotReloadEnabled)
{
foreach (var previousRun in _generationRunFileInfo.RunInfo.Manager.AllRuns.Except(_generationRunFileInfo.RunInfo))
foreach (var previousRun in _generationRunFileInfo.RunInfo.Manager.GetAllRunsWithoutSelf(_generationRunFileInfo))
{
using (writer.BlockInvariant($"private void InitializeComponent_{previousRun.ToRunIdentifierString()}()"))
{
Expand Down

0 comments on commit 864a27e

Please sign in to comment.