Skip to content

Commit c5e8353

Browse files
committed
Revert "Refactor EXE packaging to use byte sources (#134)"
This reverts commit bea83b6
1 parent 3228657 commit c5e8353

File tree

3 files changed

+120
-182
lines changed

3 files changed

+120
-182
lines changed
Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,14 @@
1-
using Zafiro.DivineBytes;
2-
31
namespace DotnetPackaging.Exe;
42

53
public static class BuildScript
64
{
7-
public static async Task Build()
5+
public static void Build()
86
{
9-
var stub = ByteSource.FromAsyncStreamFactory(() => Task.FromResult<Stream>(File.OpenRead("build/Stub.exe")));
10-
var installerPayload = ByteSource.FromAsyncStreamFactory(() => Task.FromResult<Stream>(File.OpenRead("build/installer_payload.zip")));
11-
var uninstallerPayload = ByteSource.FromAsyncStreamFactory(() => Task.FromResult<Stream>(File.OpenRead("build/uninstaller_payload.zip")));
12-
13-
Directory.CreateDirectory("artifacts");
7+
var stub = "build/Stub.exe"; // firmado
8+
var installerPayload = "build/installer_payload.zip";
9+
var uninstallerPayload = "build/uninstaller_payload.zip";
1410

15-
await Persist("artifacts/Uninstaller.exe", PayloadAppender.AppendPayload(stub, uninstallerPayload));
16-
await Persist("artifacts/Installer.exe", PayloadAppender.AppendPayload(stub, installerPayload));
17-
}
18-
19-
private static async Task Persist(string path, IByteSource source)
20-
{
21-
await using var input = source.ToStreamSeekable();
22-
await using var output = File.Create(path);
23-
await input.CopyToAsync(output);
11+
PayloadAppender.AppendPayload(stub, uninstallerPayload, "artifacts/Uninstaller.exe");
12+
PayloadAppender.AppendPayload(stub, installerPayload, "artifacts/Installer.exe");
2413
}
2514
}
Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,20 @@
11
using System.Text;
2-
using Zafiro.DivineBytes;
32

43
namespace DotnetPackaging.Exe;
54

65
public static class PayloadAppender
76
{
8-
public static IByteSource AppendPayload(IByteSource signedStub, IByteSource payload)
7+
public static void AppendPayload(string signedStubPath, string payloadZipPath, string outputPath)
98
{
10-
return ByteSource.FromAsyncStreamFactory(async () =>
11-
{
12-
var stubStream = signedStub.ToStreamSeekable();
13-
var payloadStream = payload.ToStreamSeekable();
14-
var output = new MemoryStream();
9+
var stubBytes = File.ReadAllBytes(signedStubPath);
10+
var payloadBytes = File.ReadAllBytes(payloadZipPath);
11+
var lengthBytes = BitConverter.GetBytes((long)payloadBytes.Length);
12+
var magicBytes = Encoding.ASCII.GetBytes("DPACKEXE1");
1513

16-
await using (stubStream)
17-
await using (payloadStream)
18-
{
19-
await stubStream.CopyToAsync(output);
20-
await payloadStream.CopyToAsync(output);
21-
22-
var lengthBytes = BitConverter.GetBytes(payloadStream.Length);
23-
var magicBytes = Encoding.ASCII.GetBytes("DPACKEXE1");
24-
25-
await output.WriteAsync(lengthBytes, 0, lengthBytes.Length);
26-
await output.WriteAsync(magicBytes, 0, magicBytes.Length);
27-
output.Position = 0;
28-
return output;
29-
}
30-
});
14+
using var output = File.Create(outputPath);
15+
output.Write(stubBytes);
16+
output.Write(payloadBytes);
17+
output.Write(lengthBytes);
18+
output.Write(magicBytes);
3119
}
3220
}
Lines changed: 104 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
using System.IO.Compression;
22
using System.Text.Json;
33
using CSharpFunctionalExtensions;
4-
using Zafiro.DivineBytes;
5-
using IOPath = System.IO.Path;
6-
using Zafiro.FileSystem.Core;
74

85
namespace DotnetPackaging.Exe;
96

@@ -18,183 +15,147 @@ public static async Task<Result> Build(
1815
Maybe<byte[]> logoBytes,
1916
string outputPath)
2017
{
21-
if (!File.Exists(stubPath))
18+
var tempRoot = string.Empty;
19+
try
2220
{
23-
return Result.Failure($"Stub not found: {stubPath}");
24-
}
21+
if (!File.Exists(stubPath))
22+
{
23+
return Result.Failure($"Stub not found: {stubPath}");
24+
}
2525

26-
if (!Directory.Exists(publishDir))
27-
{
28-
return Result.Failure($"Publish directory not found: {publishDir}");
29-
}
26+
if (!Directory.Exists(publishDir))
27+
{
28+
return Result.Failure($"Publish directory not found: {publishDir}");
29+
}
3030

31-
var outputDirectory = IOPath.GetDirectoryName(outputPath);
32-
if (string.IsNullOrWhiteSpace(outputDirectory))
33-
{
34-
return Result.Failure("Output directory cannot be determined.");
35-
}
31+
var outputDirectory = Path.GetDirectoryName(outputPath);
32+
if (string.IsNullOrWhiteSpace(outputDirectory))
33+
{
34+
return Result.Failure("Output directory cannot be determined.");
35+
}
3636

37-
var publishContainerResult = BuildContainerFromDirectory(publishDir);
38-
if (publishContainerResult.IsFailure)
39-
{
40-
return Result.Failure(publishContainerResult.Error);
41-
}
37+
Directory.CreateDirectory(outputDirectory);
4238

43-
var stubSource = ByteSource.FromAsyncStreamFactory(() => Task.FromResult<Stream>(File.OpenRead(stubPath)));
44-
var logoSource = logoBytes.Map(bytes => (IByteSource)ByteSource.FromBytes(bytes));
45-
var bundleResult = await Build(stubSource, publishContainerResult.Value, metadata, logoSource);
46-
if (bundleResult.IsFailure)
47-
{
48-
return Result.Failure(bundleResult.Error);
49-
}
39+
tempRoot = Path.Combine(Path.GetTempPath(), "dp-exe-" + Guid.NewGuid());
40+
Directory.CreateDirectory(tempRoot);
5041

51-
Directory.CreateDirectory(outputDirectory);
52-
var uninstallerPath = IOPath.Combine(outputDirectory, "Uninstaller.exe");
53-
await Persist(bundleResult.Value.Installer, outputPath);
54-
await Persist(bundleResult.Value.Uninstaller, uninstallerPath);
42+
var uninstallerPayloadRoot = Path.Combine(tempRoot, "uninstaller_payload");
43+
Directory.CreateDirectory(uninstallerPayloadRoot);
44+
await WriteMetadata(uninstallerPayloadRoot, metadata);
45+
await WriteSupportStub(uninstallerPayloadRoot, stubPath);
5546

56-
return Result.Success();
57-
}
47+
var uninstallerPayloadZip = Path.Combine(outputDirectory, "uninstaller_payload.zip");
48+
CreatePayloadZip(uninstallerPayloadRoot, uninstallerPayloadZip);
5849

59-
public static async Task<Result<SimpleExeBundle>> Build(
60-
IByteSource stub,
61-
IContainer publishContent,
62-
InstallerMetadata metadata,
63-
Maybe<IByteSource> logoBytes)
64-
{
65-
var metadataSource = Serialize(metadata);
66-
var uninstallerPayloadResult = await BuildUninstallerPayload(stub, metadataSource);
67-
if (uninstallerPayloadResult.IsFailure)
68-
{
69-
return Result.Failure<SimpleExeBundle>(uninstallerPayloadResult.Error);
70-
}
50+
var uninstallerOutput = Path.Combine(outputDirectory, "Uninstaller.exe");
51+
PayloadAppender.AppendPayload(stubPath, uninstallerPayloadZip, uninstallerOutput);
7152

72-
var uninstaller = new Resource("Uninstaller.exe", PayloadAppender.AppendPayload(stub, uninstallerPayloadResult.Value));
53+
var installerPayloadRoot = Path.Combine(tempRoot, "installer_payload");
54+
Directory.CreateDirectory(installerPayloadRoot);
55+
await WriteMetadata(installerPayloadRoot, metadata);
56+
CopyDirectory(publishDir, Path.Combine(installerPayloadRoot, "Content"));
57+
CopySupportBinary(uninstallerOutput, Path.Combine(installerPayloadRoot, "Support"));
58+
await WriteLogo(installerPayloadRoot, logoBytes);
7359

74-
var installerPayloadResult = await BuildInstallerPayload(metadataSource, publishContent, logoBytes, uninstaller);
75-
if (installerPayloadResult.IsFailure)
60+
var installerPayloadZip = Path.Combine(outputDirectory, "installer_payload.zip");
61+
CreatePayloadZip(installerPayloadRoot, installerPayloadZip);
62+
63+
PayloadAppender.AppendPayload(stubPath, installerPayloadZip, outputPath);
64+
return Result.Success();
65+
}
66+
catch (Exception ex)
67+
{
68+
return Result.Failure(ex.Message);
69+
}
70+
finally
7671
{
77-
return Result.Failure<SimpleExeBundle>(installerPayloadResult.Error);
72+
TryDeleteTempDirectories();
7873
}
7974

80-
var installer = new Resource("Installer.exe", PayloadAppender.AppendPayload(stub, installerPayloadResult.Value));
75+
void TryDeleteTempDirectories()
76+
{
77+
if (string.IsNullOrWhiteSpace(tempRoot))
78+
{
79+
return;
80+
}
8181

82-
return Result.Success(new SimpleExeBundle(installer, uninstaller));
82+
try
83+
{
84+
Directory.Delete(tempRoot, true);
85+
}
86+
catch
87+
{
88+
// best effort cleanup
89+
}
90+
}
8391
}
8492

85-
private static async Task Persist(INamedByteSource artifact, string path)
93+
private static void CreatePayloadZip(string sourceDirectory, string destinationZip)
8694
{
87-
Directory.CreateDirectory(IOPath.GetDirectoryName(path)!);
88-
await using var input = artifact.ToStreamSeekable();
89-
await using var output = File.Create(path);
90-
await input.CopyToAsync(output);
95+
Directory.CreateDirectory(Path.GetDirectoryName(destinationZip)!);
96+
if (File.Exists(destinationZip))
97+
{
98+
File.Delete(destinationZip);
99+
}
100+
101+
ZipFile.CreateFromDirectory(sourceDirectory, destinationZip, CompressionLevel.Optimal, includeBaseDirectory: false);
91102
}
92103

93-
private static IByteSource Serialize(InstallerMetadata metadata)
104+
private static async Task WriteMetadata(string destinationDirectory, InstallerMetadata meta)
94105
{
95-
var json = JsonSerializer.Serialize(metadata, new JsonSerializerOptions
106+
Directory.CreateDirectory(destinationDirectory);
107+
var metadataPath = Path.Combine(destinationDirectory, "metadata.json");
108+
await using var stream = File.Create(metadataPath);
109+
await JsonSerializer.SerializeAsync(stream, meta, new JsonSerializerOptions
96110
{
97111
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
98112
WriteIndented = false
99113
});
100-
101-
return ByteSource.FromString(json);
102-
}
103-
104-
private static async Task<Result<IByteSource>> BuildUninstallerPayload(IByteSource stub, IByteSource metadataSource)
105-
{
106-
var entries = new Dictionary<string, IByteSource>(StringComparer.Ordinal)
107-
{
108-
["metadata.json"] = metadataSource,
109-
[$"Support/{"Uninstaller.exe"}"] = stub
110-
};
111-
112-
var containerResult = entries.ToRootContainer();
113-
if (containerResult.IsFailure)
114-
{
115-
return Result.Failure<IByteSource>(containerResult.Error);
116-
}
117-
118-
return await CreatePayloadZip(containerResult.Value);
119114
}
120115

121-
private static async Task<Result<IByteSource>> BuildInstallerPayload(
122-
IByteSource metadataSource,
123-
IContainer publishContent,
124-
Maybe<IByteSource> logoBytes,
125-
INamedByteSource uninstaller)
116+
private static async Task WriteLogo(string payloadRoot, Maybe<byte[]> logoBytes)
126117
{
127-
var entries = new Dictionary<string, IByteSource>(StringComparer.Ordinal)
128-
{
129-
["metadata.json"] = metadataSource,
130-
[$"Support/{uninstaller.Name}"] = uninstaller
131-
};
132-
133-
foreach (var file in publishContent.ResourcesWithPathsRecursive())
134-
{
135-
var entryPath = $"Content/{file.FullPath().ToString().Replace('\\', '/')}";
136-
entries[entryPath] = file;
137-
}
138-
139-
logoBytes.Execute(bytes => entries[BrandingLogoEntry] = bytes);
140-
141-
var containerResult = entries.ToRootContainer();
142-
if (containerResult.IsFailure)
143-
{
144-
return Result.Failure<IByteSource>(containerResult.Error);
145-
}
146-
147-
return await CreatePayloadZip(containerResult.Value);
118+
await logoBytes.Match(
119+
async bytes =>
120+
{
121+
var brandingDir = Path.Combine(payloadRoot, "Branding");
122+
Directory.CreateDirectory(brandingDir);
123+
var logoPath = Path.Combine(brandingDir, Path.GetFileName(BrandingLogoEntry));
124+
await File.WriteAllBytesAsync(logoPath, bytes);
125+
},
126+
() => Task.CompletedTask);
148127
}
149128

150-
private static Result<RootContainer> BuildContainerFromDirectory(string root)
129+
private static async Task WriteSupportStub(string payloadRoot, string stubPath)
151130
{
152-
try
153-
{
154-
var files = Directory
155-
.EnumerateFiles(root, "*", SearchOption.AllDirectories)
156-
.ToDictionary(
157-
file => IOPath.GetRelativePath(root, file).Replace("\\", "/"),
158-
file => (IByteSource)ByteSource.FromAsyncStreamFactory(() => Task.FromResult<Stream>(File.OpenRead(file))),
159-
StringComparer.Ordinal);
160-
161-
return files.ToRootContainer();
162-
}
163-
catch (Exception ex)
164-
{
165-
return Result.Failure<RootContainer>($"Failed to read directory '{root}': {ex.Message}");
166-
}
131+
var supportDir = Path.Combine(payloadRoot, "Support");
132+
Directory.CreateDirectory(supportDir);
133+
await using var input = File.OpenRead(stubPath);
134+
await using var output = File.Create(Path.Combine(supportDir, "Uninstaller.exe"));
135+
await input.CopyToAsync(output);
167136
}
168137

169-
private static Task<Result<IByteSource>> CreatePayloadZip(IContainer container)
138+
private static void CopySupportBinary(string uninstallerPath, string supportRoot)
170139
{
171-
return Task.FromResult(Result.Success(CreateZipSource(container)));
140+
Directory.CreateDirectory(supportRoot);
141+
var destination = Path.Combine(supportRoot, "Uninstaller.exe");
142+
File.Copy(uninstallerPath, destination, overwrite: true);
172143
}
173144

174-
private static IByteSource CreateZipSource(IContainer container)
145+
private static void CopyDirectory(string sourceDir, string destinationDir)
175146
{
176-
return ByteSource.FromAsyncStreamFactory(async () =>
147+
foreach (var directory in Directory.EnumerateDirectories(sourceDir, "*", SearchOption.AllDirectories))
177148
{
178-
var zipStream = new MemoryStream();
179-
180-
await using (var zip = new ZipArchive(zipStream, ZipArchiveMode.Create, leaveOpen: true))
181-
{
182-
foreach (var resource in container.ResourcesWithPathsRecursive())
183-
{
184-
var entry = zip.CreateEntry(resource.FullPath().ToString().Replace('\\', '/'), CompressionLevel.Optimal);
185-
await using var entryStream = entry.Open();
186-
await using var resourceStream = resource.ToStreamSeekable();
187-
await resourceStream.CopyToAsync(entryStream);
188-
}
189-
}
149+
var relative = Path.GetRelativePath(sourceDir, directory);
150+
Directory.CreateDirectory(Path.Combine(destinationDir, relative));
151+
}
190152

191-
zipStream.Position = 0;
192-
var final = new MemoryStream();
193-
await zipStream.CopyToAsync(final);
194-
final.Position = 0;
195-
return final;
196-
});
153+
foreach (var file in Directory.EnumerateFiles(sourceDir, "*", SearchOption.AllDirectories))
154+
{
155+
var relative = Path.GetRelativePath(sourceDir, file);
156+
var destination = Path.Combine(destinationDir, relative);
157+
Directory.CreateDirectory(Path.GetDirectoryName(destination)!);
158+
File.Copy(file, destination, overwrite: true);
159+
}
197160
}
198161
}
199-
200-
public sealed record SimpleExeBundle(INamedByteSource Installer, INamedByteSource Uninstaller);

0 commit comments

Comments
 (0)