Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add architecture override option to Update command #206

Merged
merged 10 commits into from
Dec 1, 2021
6 changes: 5 additions & 1 deletion doc/update.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ Search for an existing manifest and update the installer url:

Save and publish updated manifest:

`wingetcreate.exe update --out <OutputDirectory> --token <GitHubPersonalAccessToken> --version <Version> <PackageIdentifier>`
`wingetcreate.exe update --out <OutputDirectory> --token <GitHubPersonalAccessToken> --version <Version> <PackageIdentifier>`

Override the architecture of an installer:

`wingetcreate.exe update --urls <InstallerUrl1>|<InstallerArchitecture> --version <Version> <PackageIdentifier>`

## Arguments

Expand Down
38 changes: 9 additions & 29 deletions src/WingetCreateCLI/Commands/BaseCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,19 +213,22 @@ protected static bool ValidateManifestsInTempDir(Manifests manifests)
/// <summary>
/// Displays the appropriate warning messages for installers with detected architecture mismatches.
/// </summary>
/// <param name="detectedArchs">List of DetectedArch objects that represent each installers detected architectures.</param>
protected static void DisplayMismatchedArchitectures(List<PackageParser.DetectedArch> detectedArchs)
/// <param name="installerMetadataList">List of <see cref="InstallerMetadata"/>.</param>
protected static void DisplayMismatchedArchitectures(List<InstallerMetadata> installerMetadataList)
{
var mismatchedArchInstallers = detectedArchs.Where(i => i.UrlArch.HasValue && i.UrlArch != i.BinaryArch);
var mismatchedArchInstallers = installerMetadataList.Where(
i => i.UrlArchitecture.HasValue &&
i.BinaryArchitecture.HasValue &&
i.UrlArchitecture != i.BinaryArchitecture);

if (mismatchedArchInstallers.Any())
{
Logger.WarnLocalized(nameof(Resources.DetectedArchMismatch_Message));
Console.WriteLine();
foreach (var installer in mismatchedArchInstallers)
foreach (var mismatch in mismatchedArchInstallers)
{
Logger.WarnLocalized(nameof(Resources.InstallerBinaryMismatch_Message), installer.UrlArch, installer.BinaryArch);
Logger.Warn($"{installer.Url}");
Logger.WarnLocalized(nameof(Resources.InstallerBinaryMismatch_Message), mismatch.UrlArchitecture, mismatch.BinaryArchitecture);
Logger.Warn($"{mismatch.InstallerUrl}");
Console.WriteLine();
}
}
Expand Down Expand Up @@ -314,29 +317,6 @@ protected static async Task<string> DownloadPackageFile(string installerUrl)
}
}

/// <summary>
/// Download all packages specified by urls, and return list of downloaded file paths.
/// </summary>
/// <param name="urls">Urls for packages to download.</param>
/// <returns>List of file paths downloaded.</returns>
protected static async Task<IList<string>> DownloadInstallers(IEnumerable<string> urls)
{
var packageFiles = new List<string>();

foreach (var url in urls)
{
string packageFile = await DownloadPackageFile(url);
if (string.IsNullOrEmpty(packageFile))
{
return null;
}

packageFiles.Add(packageFile);
}

return packageFiles;
}

/// <summary>
/// Utilizes WingetUtil to validate a specified manifest.
/// </summary>
Expand Down
17 changes: 12 additions & 5 deletions src/WingetCreateCLI/Commands/NewCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,23 @@ public override async Task<bool> Execute()
Console.Clear();
}

var packageFiles = await DownloadInstallers(this.InstallerUrls);
if (packageFiles == null)
List<InstallerMetadata> installerUpdateList = new List<InstallerMetadata>();

foreach (var installerUrl in this.InstallerUrls)
{
return false;
string packageFile = await DownloadPackageFile(installerUrl);
if (string.IsNullOrEmpty(packageFile))
{
return false;
}

installerUpdateList.Add(new InstallerMetadata { InstallerUrl = installerUrl, PackageFile = packageFile });
}

try
{
PackageParser.ParsePackages(packageFiles, this.InstallerUrls, manifests, out List<PackageParser.DetectedArch> detectedArchs);
DisplayMismatchedArchitectures(detectedArchs);
PackageParser.ParsePackages(installerUpdateList, manifests);
DisplayMismatchedArchitectures(installerUpdateList);
}
catch (IOException iOException) when (iOException.HResult == -2147024671)
{
Expand Down
100 changes: 86 additions & 14 deletions src/WingetCreateCLI/Commands/UpdateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace Microsoft.WingetCreateCLI.Commands
using Microsoft.WingetCreateCLI.Telemetry;
using Microsoft.WingetCreateCLI.Telemetry.Events;
using Microsoft.WingetCreateCore;
using Microsoft.WingetCreateCore.Common;
using Microsoft.WingetCreateCore.Common.Exceptions;
using Microsoft.WingetCreateCore.Models;
using Microsoft.WingetCreateCore.Models.DefaultLocale;
Expand All @@ -40,6 +41,7 @@ public static IEnumerable<Example> Examples
{
yield return new Example(Resources.Example_UpdateCommand_SearchAndUpdateVersionAndInstallerURL, new UpdateCommand { Id = "<PackageIdentifier>", InstallerUrls = new string[] { "<InstallerUrl1>", "<InstallerUrl2>" }, Version = "<Version>" });
yield return new Example(Resources.Example_UpdateCommand_SaveAndPublish, new UpdateCommand { Id = "<PackageIdentifier>", Version = "<Version>", OutputDir = "<OutputDirectory>", GitHubToken = "<GitHubPersonalAccessToken>" });
yield return new Example(Resources.Example_UpdateCommand_OverrideArchitecture, new UpdateCommand { Id = "<PackageIdentifier>", InstallerUrls = new string[] { "<InstallerUrl1>|<InstallerArchitecture>" }, Version = "<Version>" });
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down Expand Up @@ -220,32 +222,48 @@ public async Task<Manifests> UpdateManifestsAutonomously(Manifests manifests)
this.InstallerUrls = installerManifest.Installers.Select(i => i.InstallerUrl).Distinct().ToArray();
}

// Generate list of InstallerUpdate objects and parse out any specified architecture overrides.
List<InstallerMetadata> installerMetadataList = this.ParseInstallerUrlsForArchOverride(this.InstallerUrls.ToList());

// If the installer update list is null there was an issue when parsing for architecture override.
if (installerMetadataList == null)
{
return null;
}

// Reassign list with parsed installer URLs without architecture overrides.
this.InstallerUrls = installerMetadataList.Select(x => x.InstallerUrl).ToList();

foreach (var installerUpdate in installerMetadataList)
{
if (installerUpdate.OverrideArchitecture.HasValue)
{
Logger.WarnLocalized(nameof(Resources.OverridingArchitecture_Warning), installerUpdate.InstallerUrl, installerUpdate.OverrideArchitecture);
}
}

// We only support updates with same number of installer URLs
if (this.InstallerUrls.Distinct().Count() != installerManifest.Installers.Select(i => i.InstallerUrl).Distinct().Count())
{
Logger.ErrorLocalized(nameof(Resources.MultipleInstallerUpdateDiscrepancy_Error));
return null;
}

var packageFiles = await DownloadInstallers(this.InstallerUrls);
if (packageFiles == null)
foreach (var installerUpdate in installerMetadataList)
{
return null;
}
string packageFile = await DownloadPackageFile(installerUpdate.InstallerUrl);
if (string.IsNullOrEmpty(packageFile))
{
return null;
}

List<PackageParser.DetectedArch> detectedArchOfInstallers;
List<Installer> newInstallers = new List<Installer>();
installerUpdate.PackageFile = packageFile;
}

try
{
PackageParser.UpdateInstallerNodesAsync(
installerManifest,
this.InstallerUrls,
packageFiles,
out detectedArchOfInstallers,
out newInstallers);

DisplayMismatchedArchitectures(detectedArchOfInstallers);
PackageParser.UpdateInstallerNodesAsync(installerMetadataList, installerManifest);
DisplayMismatchedArchitectures(installerMetadataList);
}
catch (InvalidOperationException)
{
Expand All @@ -267,6 +285,12 @@ public async Task<Manifests> UpdateManifestsAutonomously(Manifests manifests)
Logger.ErrorLocalized(nameof(Resources.NewInstallerUrlMustMatchExisting_Message));
installerMatchException.MultipleMatchedInstallers.ForEach(i => Logger.ErrorLocalized(nameof(Resources.UnmatchedInstaller_Error), i.Architecture, i.InstallerType, i.InstallerUrl));
installerMatchException.UnmatchedInstallers.ForEach(i => Logger.ErrorLocalized(nameof(Resources.MultipleMatchedInstaller_Error), i.Architecture, i.InstallerType, i.InstallerUrl));

if (installerMatchException.IsArchitectureOverride)
{
Logger.WarnLocalized(nameof(Resources.ArchitectureOverride_Warning));
}

return null;
}

Expand Down Expand Up @@ -419,6 +443,54 @@ private static void DisplayManifestsAsMenuSelection(Manifests manifests)
}
}

/// <summary>
/// Parse out architecture overrides included in the installer URLs and returns the parsed list of installer URLs.
/// </summary>
/// <param name="installerUrlsToBeParsed">List of installer URLs to be parsed for architecture overrides.</param>
/// <returns>List of <see cref="InstallerMetadata"/> helper objects used for updating the installers.</returns>
private List<InstallerMetadata> ParseInstallerUrlsForArchOverride(List<string> installerUrlsToBeParsed)
{
List<InstallerMetadata> installerMetadataList = new List<InstallerMetadata>();
foreach (string item in installerUrlsToBeParsed)
{
InstallerMetadata installerMetadata = new InstallerMetadata();

if (item.Contains('|'))
{
// '|' character indicates that an architecture override can be parsed from the installer.
string[] installerUrlOverride = item.Split('|');

if (installerUrlOverride.Length > 2)
{
Logger.ErrorLocalized(nameof(Resources.MultipleArchitectureOverride_Error));
return null;
}

string installerUrl = installerUrlOverride[0];
string overrideArchString = installerUrlOverride[1];
InstallerArchitecture? overrideArch = overrideArchString.ToEnumOrDefault<InstallerArchitecture>();
if (overrideArch.HasValue)
{
installerMetadata.InstallerUrl = installerUrl;
installerMetadata.OverrideArchitecture = overrideArch.Value;
}
else
{
Logger.ErrorLocalized(nameof(Resources.UnableToParseArchOverride_Error), overrideArchString);
return null;
}
}
else
{
installerMetadata.InstallerUrl = item;
}

installerMetadataList.Add(installerMetadata);
}

return installerMetadataList;
}

/// <summary>
/// Update flow for interactively updating the manifest.
/// </summary>s
Expand Down
45 changes: 45 additions & 0 deletions src/WingetCreateCLI/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions src/WingetCreateCLI/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -786,4 +786,23 @@
<data name="DownloadConnectionTimeout_Error" xml:space="preserve">
<value>Unable to complete the download request as the connection has timed out. Please verify your installer URL and try again.</value>
</data>
<data name="ArchitectureOverride_Warning" xml:space="preserve">
<value>If the installer you provided fails to match an existing installer even when overriding the architecture, you may need to edit the existing manifest manually. Make sure that the existing manifest has a single installer that matches the overriding architecture and installer type of the new installer. To modify an existing manifest, use the '--interactive' flag with the update command and submit the new changes. Once the changes are published, please try again.</value>
<comment>'--interactive' refers to a flag that can be included with the command-line arguments for this tool.</comment>
</data>
<data name="MultipleArchitectureOverride_Error" xml:space="preserve">
<value>Multiple architectures detected. Only one architecture can be specified for an override.</value>
</data>
<data name="UnableToParseArchOverride_Error" xml:space="preserve">
<value>Unable to parse the specified override architecture {0}.</value>
<comment>{0} - represents the override architecture that failed to parse.</comment>
</data>
<data name="Example_UpdateCommand_OverrideArchitecture" xml:space="preserve">
<value>Override the architecture of an installer</value>
</data>
<data name="OverridingArchitecture_Warning" xml:space="preserve">
<value>Overriding {0} with architecture {1}</value>
<comment>{0} - represents the InstallerUrl
{1} - represents the overriding architecture</comment>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ public class InstallerMatchException : Exception
/// </summary>
/// <param name="multipleMatchedInstaller">List of installers with multiple matches.</param>
/// <param name="unmatchedInstallers">List of installers with no matches.</param>
public InstallerMatchException(List<Installer> multipleMatchedInstaller, List<Installer> unmatchedInstallers)
/// <param name="isArchitectureOverride">Architecture of an installer was overridden by the user.</param>
public InstallerMatchException(List<Installer> multipleMatchedInstaller, List<Installer> unmatchedInstallers, bool isArchitectureOverride)
{
this.MultipleMatchedInstallers = multipleMatchedInstaller;
this.UnmatchedInstallers = unmatchedInstallers;
this.IsArchitectureOverride = isArchitectureOverride;
}

/// <summary>
Expand All @@ -32,5 +34,10 @@ public InstallerMatchException(List<Installer> multipleMatchedInstaller, List<In
/// Gets the list of installers with no matches.
/// </summary>
public List<Installer> UnmatchedInstallers { get; private set; }

/// <summary>
/// Gets a value indicating whether the user overrode the architecture of installer to be updated.
/// </summary>
public bool IsArchitectureOverride { get; private set; }
}
}
Loading