Skip to content

Commit 4af77c6

Browse files
GitHubSync update
1 parent 132e306 commit 4af77c6

3 files changed

Lines changed: 336 additions & 2 deletions

File tree

deployment/cake/installers-squirrel.cake

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public class SquirrelInstaller : IInstaller
132132
Verbosity = NuGetVerbosity.Detailed,
133133
};
134134

135-
// Fix for target framework issues
135+
// Fix for target framework issues (platform == windows 7)
136136
nuGetSettings.Properties.Add("TargetPlatformVersion", "7.0");
137137

138138
// Create NuGet package
@@ -188,7 +188,7 @@ public class SquirrelInstaller : IInstaller
188188
BuildContext.CakeContext.Information($"Copying updated Squirrel files back to deployments share at '{releasesSourceDirectory}'");
189189

190190
// Copy the following files:
191-
// - [version]-full.nupkg
191+
// - [version]-delta.nupkg
192192
// - [version]-full.nupkg
193193
// - Setup.exe => Setup.exe & WpfApp.exe
194194
// - Setup.msi
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
#tool "dotnet:?package=vpk&version=0.0.1053"
2+
3+
//-------------------------------------------------------------
4+
5+
public class VelopackInstaller : IInstaller
6+
{
7+
public VelopackInstaller(BuildContext buildContext)
8+
{
9+
BuildContext = buildContext;
10+
11+
IsEnabled = BuildContext.BuildServer.GetVariableAsBool("VelopackEnabled", false, showValue: true);
12+
13+
if (IsEnabled)
14+
{
15+
IsAvailable = IsEnabled;
16+
}
17+
}
18+
19+
public BuildContext BuildContext { get; private set; }
20+
21+
public bool IsEnabled { get; private set; }
22+
23+
public bool IsAvailable { get; private set; }
24+
25+
//-------------------------------------------------------------
26+
27+
public async Task PackageAsync(string projectName, string channel)
28+
{
29+
if (!IsAvailable)
30+
{
31+
BuildContext.CakeContext.Information("Velopack is not enabled or available, skipping integration");
32+
return;
33+
}
34+
35+
// There are 2 flavors:
36+
//
37+
// 1: Non-grouped: /[app]/[channel] (e.g. /MyApp/alpha)
38+
// Updates will always be applied, even to new major versions
39+
//
40+
// 2: Grouped by major version: /[app]/[major_version]/[channel] (e.g. /MyApp/4/alpha)
41+
// Updates will only be applied to non-major updates. This allows manual migration to
42+
// new major versions, which is very useful when there are dependencies that need to
43+
// be updated before a new major version can be switched to.
44+
var velopackOutputRoot = System.IO.Path.Combine(BuildContext.General.OutputRootDirectory, "velopack", projectName);
45+
46+
if (BuildContext.Wpf.GroupUpdatesByMajorVersion)
47+
{
48+
velopackOutputRoot = System.IO.Path.Combine(velopackOutputRoot, BuildContext.General.Version.Major);
49+
}
50+
51+
velopackOutputRoot = System.IO.Path.Combine(velopackOutputRoot, channel);
52+
53+
var velopackReleasesRoot = System.IO.Path.Combine(velopackOutputRoot, "releases");
54+
55+
BuildContext.CakeContext.LogSeparator($"Packaging WPF app '{projectName}' using Velopack");
56+
57+
BuildContext.CakeContext.CreateDirectory(velopackReleasesRoot);
58+
59+
var setupSuffix = BuildContext.Installer.GetDeploymentChannelSuffix();
60+
61+
// Velopack does not seem to support . in the names (keeping same behavior as Squirrel)
62+
var projectSlug = GetProjectSlug(projectName, "_");
63+
64+
// Copy all files to the lib so Velopack knows what to do
65+
var appSourceDirectory = System.IO.Path.Combine(BuildContext.General.OutputRootDirectory, projectName);
66+
67+
// Note: there should be only a single target framework, but pick the highest
68+
var subDirectories = System.IO.Directory.GetDirectories(appSourceDirectory);
69+
appSourceDirectory = subDirectories.Last();
70+
71+
// Copy deployments share to the intermediate root so we can locally create the releases
72+
73+
var releasesSourceDirectory = GetDeploymentsShareRootDirectory(projectName, channel);
74+
var releasesTargetDirectory = velopackReleasesRoot;
75+
76+
BuildContext.CakeContext.CreateDirectory(releasesSourceDirectory);
77+
BuildContext.CakeContext.CreateDirectory(releasesTargetDirectory);
78+
79+
BuildContext.CakeContext.Information($"Copying releases from '{releasesSourceDirectory}' => '{releasesTargetDirectory}'");
80+
81+
BuildContext.CakeContext.CopyDirectory(releasesSourceDirectory, releasesTargetDirectory);
82+
83+
BuildContext.CakeContext.Information("Generating Velopack packages, this can take a while, especially when signing is enabled...");
84+
85+
// Pack using velopack (example command line: vpk pack -u YourAppId -v 1.0.0 -p publish -e yourMainBinary.exe)
86+
87+
var appId = $"{projectSlug}{setupSuffix}";
88+
89+
var argumentBuilder = new ProcessArgumentBuilder()
90+
.Append("pack")
91+
.Append("--verbose")
92+
.AppendSwitch("--packId", appId)
93+
.AppendSwitch("--packVersion", BuildContext.General.Version.NuGet)
94+
.AppendSwitch("--packDir", appSourceDirectory)
95+
.AppendSwitch("--packAuthors", BuildContext.General.Copyright.Company)
96+
.AppendSwitch("--delta", "BestSpeed")
97+
.AppendSwitch("--outputDir", velopackReleasesRoot);
98+
99+
// TODO: Consider adding splash image
100+
101+
// Note: this is not really generic, but this is where we store our icons file, we can
102+
// always change this in the future
103+
var iconFileName = System.IO.Path.Combine(".", "design", "logo", $"logo{setupSuffix}.ico");
104+
argumentBuilder = argumentBuilder
105+
.AppendSwitch("--icon", iconFileName);
106+
107+
// --signTemplate {{file}} will be substituted
108+
// Note that we need to replace / by \ on Windows
109+
var signToolExe = GetSignToolFileName(BuildContext).Replace("/", "\\");
110+
var signToolCommandLine = GetSignToolCommandLine(BuildContext);
111+
if (!string.IsNullOrWhiteSpace(signToolExe) &&
112+
!string.IsNullOrWhiteSpace(signToolCommandLine))
113+
{
114+
// In order to work around a double quote issue (C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe),
115+
// if 'signtool.exe' is used, use signParams instead
116+
if (signToolExe.EndsWith("\\signtool.exe"))
117+
{
118+
if (signToolCommandLine.StartsWith("sign "))
119+
{
120+
signToolCommandLine = signToolCommandLine.Substring("sign ".Length);
121+
}
122+
123+
argumentBuilder = argumentBuilder
124+
.AppendSwitch("--signParams", $"\"{signToolCommandLine}\"");
125+
}
126+
else
127+
{
128+
argumentBuilder = argumentBuilder
129+
.AppendSwitch("--signTemplate", $"\"{signToolExe} {signToolCommandLine} {{{{file}}}}\"");
130+
}
131+
}
132+
133+
var vpkToolExe = BuildContext.CakeContext.Tools.Resolve("vpk.exe");
134+
135+
var vpkToolExitCode = BuildContext.CakeContext.StartProcess(vpkToolExe,
136+
new ProcessSettings
137+
{
138+
Arguments = argumentBuilder
139+
}
140+
);
141+
142+
if (vpkToolExitCode != 0)
143+
{
144+
throw new Exception("Failed to pack application");
145+
}
146+
147+
// Copy setup
148+
BuildContext.CakeContext.CopyFile(System.IO.Path.Combine(velopackReleasesRoot, $"{appId}-win-Setup.exe"), System.IO.Path.Combine(velopackReleasesRoot, "Setup.exe"));
149+
150+
if (BuildContext.Wpf.UpdateDeploymentsShare)
151+
{
152+
BuildContext.CakeContext.Information($"Copying updated Velopack files back to deployments share at '{releasesSourceDirectory}'");
153+
154+
// Copy the following files:
155+
// - [version]-delta.nupkg
156+
// - [version]-full.nupkg
157+
// - Setup.exe => Setup.exe & WpfApp.exe
158+
// - releases.win.json
159+
// - RELEASES
160+
161+
// Note to consider in future: this stores (and uploads) the same file 4 times. Maybe we need to stop processing so many files
162+
// to save time on uploads (and eventually money on storage)
163+
var velopackFiles = BuildContext.CakeContext.GetFiles($"{velopackReleasesRoot}/{appId}-{BuildContext.General.Version.NuGet}*.nupkg");
164+
BuildContext.CakeContext.CopyFiles(velopackFiles, releasesSourceDirectory);
165+
BuildContext.CakeContext.CopyFile(System.IO.Path.Combine(velopackReleasesRoot, $"{appId}-win-Portable.exe"), System.IO.Path.Combine(releasesSourceDirectory, $"{appId}-win-Portable.exe"));
166+
BuildContext.CakeContext.CopyFile(System.IO.Path.Combine(velopackReleasesRoot, $"{appId}-win-Setup.exe"), System.IO.Path.Combine(releasesSourceDirectory, $"{appId}-win-Setup.exe"));
167+
BuildContext.CakeContext.CopyFile(System.IO.Path.Combine(velopackReleasesRoot, "Setup.exe"), System.IO.Path.Combine(releasesSourceDirectory, "Setup.exe"));
168+
BuildContext.CakeContext.CopyFile(System.IO.Path.Combine(velopackReleasesRoot, "Setup.exe"), System.IO.Path.Combine(releasesSourceDirectory, $"{projectName}.exe"));
169+
BuildContext.CakeContext.CopyFile(System.IO.Path.Combine(velopackReleasesRoot, "releases.win.json"), System.IO.Path.Combine(releasesSourceDirectory, "releases.win.json"));
170+
171+
// Note: RELEASES is there for backwards compatibility
172+
BuildContext.CakeContext.CopyFile(System.IO.Path.Combine(velopackReleasesRoot, "RELEASES"), System.IO.Path.Combine(releasesSourceDirectory, "RELEASES"));
173+
}
174+
}
175+
176+
//-------------------------------------------------------------
177+
178+
public async Task<DeploymentTarget> GenerateDeploymentTargetAsync(string projectName)
179+
{
180+
var deploymentTarget = new DeploymentTarget
181+
{
182+
Name = "Velopack"
183+
};
184+
185+
var channels = new []
186+
{
187+
"alpha",
188+
"beta",
189+
"stable"
190+
};
191+
192+
var deploymentGroupNames = new List<string>();
193+
var projectDeploymentShare = BuildContext.Wpf.GetDeploymentShareForProject(projectName);
194+
195+
if (BuildContext.Wpf.GroupUpdatesByMajorVersion)
196+
{
197+
// Check every directory that we can parse as number
198+
var directories = System.IO.Directory.GetDirectories(projectDeploymentShare);
199+
200+
foreach (var directory in directories)
201+
{
202+
var deploymentGroupName = new System.IO.DirectoryInfo(directory).Name;
203+
204+
if (int.TryParse(deploymentGroupName, out _))
205+
{
206+
deploymentGroupNames.Add(deploymentGroupName);
207+
}
208+
}
209+
}
210+
else
211+
{
212+
// Just a single group
213+
deploymentGroupNames.Add("all");
214+
}
215+
216+
foreach (var deploymentGroupName in deploymentGroupNames)
217+
{
218+
BuildContext.CakeContext.Information($"Searching for releases for deployment group '{deploymentGroupName}'");
219+
220+
var deploymentGroup = new DeploymentGroup
221+
{
222+
Name = deploymentGroupName
223+
};
224+
225+
var version = deploymentGroupName;
226+
if (version == "all")
227+
{
228+
version = string.Empty;
229+
}
230+
231+
foreach (var channel in channels)
232+
{
233+
BuildContext.CakeContext.Information($"Searching for releases for deployment channel '{deploymentGroupName}/{channel}'");
234+
235+
var deploymentChannel = new DeploymentChannel
236+
{
237+
Name = channel
238+
};
239+
240+
var targetDirectory = GetDeploymentsShareRootDirectory(projectName, channel, version);
241+
242+
BuildContext.CakeContext.Information($"Searching for release files in '{targetDirectory}'");
243+
244+
var fullNupkgFiles = System.IO.Directory.GetFiles(targetDirectory, "*-full.nupkg");
245+
246+
foreach (var fullNupkgFile in fullNupkgFiles)
247+
{
248+
BuildContext.CakeContext.Information($"Applying release based on '{fullNupkgFile}'");
249+
250+
var fullReleaseFileInfo = new System.IO.FileInfo(fullNupkgFile);
251+
var fullRelativeFileName = new DirectoryPath(projectDeploymentShare).GetRelativePath(new FilePath(fullReleaseFileInfo.FullName)).FullPath.Replace("\\", "/");
252+
253+
var releaseVersion = fullReleaseFileInfo.Name
254+
.Replace($"{projectName}_{channel}-", string.Empty)
255+
.Replace($"-full.nupkg", string.Empty);
256+
257+
// Exception for full releases, they don't contain the channel name
258+
if (channel == "stable")
259+
{
260+
releaseVersion = releaseVersion.Replace($"{projectName}-", string.Empty);
261+
}
262+
263+
var release = new DeploymentRelease
264+
{
265+
Name = releaseVersion,
266+
Timestamp = fullReleaseFileInfo.CreationTimeUtc
267+
};
268+
269+
// Full release
270+
release.Full = new DeploymentReleasePart
271+
{
272+
RelativeFileName = fullRelativeFileName,
273+
Size = (ulong)fullReleaseFileInfo.Length
274+
};
275+
276+
// Delta release
277+
var deltaNupkgFile = fullNupkgFile.Replace("-full.nupkg", "-delta.nupkg");
278+
if (System.IO.File.Exists(deltaNupkgFile))
279+
{
280+
var deltaReleaseFileInfo = new System.IO.FileInfo(deltaNupkgFile);
281+
var deltafullRelativeFileName = new DirectoryPath(projectDeploymentShare).GetRelativePath(new FilePath(deltaReleaseFileInfo.FullName)).FullPath.Replace("\\", "/");
282+
283+
release.Delta = new DeploymentReleasePart
284+
{
285+
RelativeFileName = deltafullRelativeFileName,
286+
Size = (ulong)deltaReleaseFileInfo.Length
287+
};
288+
}
289+
290+
deploymentChannel.Releases.Add(release);
291+
}
292+
293+
deploymentGroup.Channels.Add(deploymentChannel);
294+
}
295+
296+
deploymentTarget.Groups.Add(deploymentGroup);
297+
}
298+
299+
return deploymentTarget;
300+
}
301+
302+
//-------------------------------------------------------------
303+
304+
private string GetDeploymentsShareRootDirectory(string projectName, string channel)
305+
{
306+
var version = string.Empty;
307+
308+
if (BuildContext.Wpf.GroupUpdatesByMajorVersion)
309+
{
310+
version = BuildContext.General.Version.Major;
311+
}
312+
313+
return GetDeploymentsShareRootDirectory(projectName, channel, version);
314+
}
315+
316+
//-------------------------------------------------------------
317+
318+
private string GetDeploymentsShareRootDirectory(string projectName, string channel, string version)
319+
{
320+
var deploymentShare = BuildContext.Wpf.GetDeploymentShareForProject(projectName);
321+
322+
if (!string.IsNullOrWhiteSpace(version))
323+
{
324+
deploymentShare = System.IO.Path.Combine(deploymentShare, version);
325+
}
326+
327+
var installersOnDeploymentsShare = System.IO.Path.Combine(deploymentShare, channel);
328+
BuildContext.CakeContext.CreateDirectory(installersOnDeploymentsShare);
329+
330+
return installersOnDeploymentsShare;
331+
}
332+
}

deployment/cake/installers.cake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#l "installers-innosetup.cake"
33
#l "installers-msix.cake"
44
#l "installers-squirrel.cake"
5+
#l "installers-velopack.cake"
56

67
using System.Diagnostics;
78

@@ -116,6 +117,7 @@ public class InstallerIntegration : IntegrationBase
116117
_installers.Add(new InnoSetupInstaller(buildContext));
117118
_installers.Add(new MsixInstaller(buildContext));
118119
_installers.Add(new SquirrelInstaller(buildContext));
120+
_installers.Add(new VelopackInstaller(buildContext));
119121
}
120122

121123
public string GetDeploymentChannelSuffix(string prefix = "_", string suffix = "")

0 commit comments

Comments
 (0)