Skip to content

Commit 2252131

Browse files
[Fusion] Make FusionArchive generation deterministic (#8994)
1 parent e66036f commit 2252131

File tree

1 file changed

+24
-28
lines changed

1 file changed

+24
-28
lines changed

src/HotChocolate/Fusion-vnext/src/Fusion.Packaging/ArchiveSession.cs

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -126,52 +126,48 @@ public void SetMode(FusionArchiveMode mode)
126126

127127
public async Task CommitAsync(CancellationToken cancellationToken)
128128
{
129-
foreach (var file in _files.Values)
129+
foreach (var file in _files.Values.OrderBy(f => f.Path, StringComparer.Ordinal))
130130
{
131-
#if NET10_0_OR_GREATER
132131
switch (file.State)
133132
{
134133
case FileState.Created:
135-
await _archive.CreateEntryFromFileAsync(
136-
file.TempPath,
137-
file.Path,
138-
cancellationToken: cancellationToken);
134+
await CreateEntryFromFileAsync(file.TempPath, file.Path, cancellationToken);
139135
break;
140136

141137
case FileState.Replaced:
142138
_archive.GetEntry(file.Path)?.Delete();
143-
await _archive.CreateEntryFromFileAsync(
144-
file.TempPath,
145-
file.Path,
146-
cancellationToken);
139+
await CreateEntryFromFileAsync(file.TempPath, file.Path, cancellationToken);
147140
break;
148141

149142
case FileState.Deleted:
150143
_archive.GetEntry(file.Path)?.Delete();
151144
break;
152145
}
153-
#else
154-
switch (file.State)
155-
{
156-
case FileState.Created:
157-
_archive.CreateEntryFromFile(file.TempPath, file.Path);
158-
break;
159146

160-
case FileState.Replaced:
161-
_archive.GetEntry(file.Path)?.Delete();
162-
_archive.CreateEntryFromFile(file.TempPath, file.Path);
163-
break;
147+
file.MarkRead();
148+
}
149+
}
164150

165-
case FileState.Deleted:
166-
_archive.GetEntry(file.Path)?.Delete();
167-
break;
168-
}
151+
/// <summary>
152+
/// Creates a ZIP entry from a file with a deterministic timestamp.
153+
/// Using a fixed timestamp ensures binary reproducibility of the archive.
154+
/// </summary>
155+
private async Task CreateEntryFromFileAsync(
156+
string sourceFileName,
157+
string entryName,
158+
CancellationToken cancellationToken)
159+
{
160+
var entry = _archive.CreateEntry(entryName);
161+
// Use a fixed timestamp to ensure deterministic archive output
162+
entry.LastWriteTime = new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero);
169163

170-
await Task.CompletedTask;
164+
await using var source = File.OpenRead(sourceFileName);
165+
#if NET10_0_OR_GREATER
166+
await using var destination = await entry.OpenAsync(cancellationToken);
167+
#else
168+
await using var destination = entry.Open();
171169
#endif
172-
173-
file.MarkRead();
174-
}
170+
await source.CopyToAsync(destination, cancellationToken);
175171
}
176172

177173
private static async Task ExtractFileAsync(

0 commit comments

Comments
 (0)