-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathDeltaProject.cs
126 lines (103 loc) · 5.28 KB
/
DeltaProject.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.DotNet.HotReload.Utils.Generator;
/// Drives the creation of deltas from textual changes.
public class DeltaProject
{
readonly EnC.ChangeMakerService _changeMakerService;
readonly Solution _solution;
readonly ProjectId _baseProjectId;
readonly DeltaNaming _nextName;
public DeltaProject(BaselineArtifacts artifacts) {
_changeMakerService = artifacts.ChangeMakerService;
_solution = artifacts.BaselineSolution;
_baseProjectId = artifacts.BaselineProjectId;
_nextName = new DeltaNaming(artifacts.BaselineOutputAsmPath, 1);
}
internal DeltaProject (DeltaProject prev, Solution newSolution)
{
_changeMakerService = prev._changeMakerService;
_solution = newSolution;
_baseProjectId = prev._baseProjectId;
_nextName = prev._nextName.Next ();
}
public Solution Solution => _solution;
public ProjectId BaseProjectId => _baseProjectId;
/// The default output function
/// Creates files with the specified DeltaNaming without any other side-effects
public static DeltaOutputStreams DefaultMakeFileOutputs (DeltaNaming dinfo) {
var metaStream = File.Create(dinfo.Dmeta);
var ilStream = File.Create(dinfo.Dil);
var pdbStream = File.Create(dinfo.Dpdb);
var updateHandlerInfoStream = File.Create(dinfo.UpdateHandlerInfo);
return new DeltaOutputStreams(metaStream, ilStream, pdbStream, updateHandlerInfoStream);
}
/// Builds a delta for the specified document given a path to its updated contents and a revision count
/// On failure throws a DiffyException and with exitStatus > 0
public async Task<DeltaProject> BuildDelta (Delta delta, bool ignoreUnchanged = false,
Func<DeltaNaming, DeltaOutputStreams>? makeOutputs = default,
Action<DeltaNaming, DeltaOutputStreams>? outputsReady = default,
CancellationToken ct = default)
{
var change = delta.Change;
var dinfo = _nextName;
Console.WriteLine ($"parsing patch #{dinfo.Rev} from {change.Update} and creating delta");
Project oldProject = Solution.GetProject(BaseProjectId)!;
DocumentId baseDocumentId = change.Document;
Document oldDocument = oldProject.GetDocument(baseDocumentId)!;
Document updatedDocument;
Solution updatedSolution;
await using (var contents = File.OpenRead (change.Update)) {
updatedSolution = Solution.WithDocumentText (baseDocumentId, SourceText.From (contents, Encoding.UTF8));
updatedDocument = updatedSolution.GetDocument(baseDocumentId)!;
}
if (updatedDocument.Project.Id != BaseProjectId)
throw new Exception ("Unexpectedly, project Id of the delta != base project Id");
if (updatedDocument.Id != baseDocumentId)
throw new Exception ("Unexpectedly, document Id of the delta != base document Id");
var changes = await updatedDocument.GetTextChangesAsync (oldDocument, ct);
if (!changes.Any()) {
Console.WriteLine ("no changes found");
if (ignoreUnchanged)
return this;
//FIXME can continue here and just ignore the revision
throw new DiffyException ($"no changes in revision {dinfo.Rev}", exitStatus: 5);
}
Console.WriteLine ($"Found changes in {oldDocument.Name}");
(var fancyChanges, var diagnostics) = await _changeMakerService.EmitSolutionUpdateAsync (updatedSolution, ct);
if (diagnostics.Any()) {
var sb = new StringBuilder();
foreach (var diag in diagnostics) {
sb.AppendLine (diag.ToString ());
}
throw new DiffyException ($"Failed to emit delta for {oldDocument.Name}: {sb}", exitStatus: 8);
}
foreach (var fancyChange in fancyChanges)
{
Console.WriteLine("change service made {0}", fancyChange.ModuleId);
}
await using (var output = makeOutputs != null ? makeOutputs(dinfo) : DefaultMakeFileOutputs(dinfo)) {
if (fancyChanges.Length != 1) {
throw new DiffyException($"Expected only one module in the delta, got {fancyChanges.Length}", exitStatus: 10);
}
var update = fancyChanges.First();
output.MetaStream.Write(update.MetadataDelta.AsSpan());
output.IlStream.Write(update.ILDelta.AsSpan());
output.PdbStream.Write(update.PdbDelta.AsSpan());
System.Text.Json.JsonSerializer.Serialize(output.UpdateHandlerInfoStream, new UpdateHandlerInfo (update.UpdatedTypes));
outputsReady?.Invoke(dinfo, output);
}
Console.WriteLine($"wrote {dinfo.Dmeta}");
// return a new deltaproject that can build the next update
return new DeltaProject(this, updatedSolution);
}
}