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

Support updating DisplayVersion in autonomous update #520

Merged
merged 3 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions doc/update.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,32 @@ e.g.,

`wingetcreate update <PackageIdentifier> --urls '<InstallerUrl1>|x64|user' '<InstallerUrl1>|x64|machine' '<InstallerUrl2>|x86|user' '<InstallerUrl2>|x86|machine'`

### Installer URL arguments

The following additional arguments can be provided with the installer URL(s):

#### Format

`'<InstallerUrl>|<Argument1>|<Argument2>...'`

#### Override Architecture

Winget-Create will attempt to determine the architecture of the installer package by performing a regex string match to identify the possible architecture in the installer url. If no match is found, Winget-Create will resort to obtaining the architecture from the downloaded installer. If Winget-Create fails to detect the architecture from the binary or the detected architecture does not match an architecture in the existing manifest, Winget-Create will fail to generate the manifest. In this case, you can explicitly provide the intended architecture and override the detected architecture using the following format:

`'<InstallerUrl>|<InstallerArchitecture>'`

#### Override Scope

In case there are multiple installers with the same architecture, it may mean the same installer is available for multiple scopes. In this case, you can explicitly provide the installer scope in the update command using the following following argument format:

`'<InstallerUrl>|<InstallerScope>'`

#### Display Version

In some cases, the publisher of the package may use a different marketing version than the actual version written to Apps & Features. In this case, the manifest will contain `DisplayVersion` field. You can update the `DisplayVersion` field using the `--display-version` CLI arg if all installers use the same display version. If the display version differs for each installer, you can use following argument format:

`'<InstallerUrl>|<DisplayVersion>'`

## Usage Examples

Search for an existing manifest and update the version:
Expand Down
93 changes: 75 additions & 18 deletions src/WingetCreateCLI/Commands/UpdateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ public static IEnumerable<Example> Examples
[Option('v', "version", Required = false, HelpText = "Version_HelpText", ResourceType = typeof(Resources))]
public string Version { get; set; }

/// <summary>
/// Gets or sets the new value used to update the display version field in the manifest.
/// </summary>
[Option('d', "display-version", Required = false, HelpText = "DisplayVersion_HelpText", ResourceType = typeof(Resources))]
public string DisplayVersion { get; set; }

/// <summary>
/// Gets or sets the outputPath where the generated manifest file should be saved to.
/// </summary>
Expand Down Expand Up @@ -300,16 +306,57 @@ 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 or scope overrides.
List<InstallerMetadata> installerMetadataList = this.ParseInstallerUrlsForOverrides(this.InstallerUrls.Select(i => i.Trim()).ToList());
// Generate list of InstallerUpdate objects and parse out any specified installer URL arguments.
List<InstallerMetadata> installerMetadataList = this.ParseInstallerUrlsForArguments(this.InstallerUrls.Select(i => i.Trim()).ToList());

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

// Reassign list with parsed installer URLs without architecture or scope overrides.
if (!string.IsNullOrEmpty(this.DisplayVersion))
{
// Use --display-version value if version was not provided as an argument.
foreach (InstallerMetadata installerUpdate in installerMetadataList)
{
if (string.IsNullOrEmpty(installerUpdate.DisplayVersion))
{
installerUpdate.DisplayVersion = this.DisplayVersion;
}
}
}

var originalAppsAndFeaturesEntries = installerManifest.Installers
.Where(i => i.AppsAndFeaturesEntries != null)
.SelectMany(i => i.AppsAndFeaturesEntries);

int originalDisplayVersionCount = originalAppsAndFeaturesEntries
.Count(entry => entry.DisplayVersion != null);

int newDisplayVersionCount = installerMetadataList
.Count(entry => entry.DisplayVersion != null);

if (newDisplayVersionCount < originalDisplayVersionCount)
{
Logger.WarnLocalized(nameof(Resources.UnchangedDisplayVersion_Warning));
}

// Check if any single installer has multiple display versions in the original manifest.
bool installerHasMultipleDisplayVersions = originalAppsAndFeaturesEntries
.Where(entry => entry.DisplayVersion != null)
.GroupBy(entry => entry.DisplayVersion)
.Any(group => group.Count() > 1);

// It is possible for a single installer to have multiple ARP entries having multiple display versions,
// but currently, we only take the primary ARP entry in the community repository. If such a case is detected,
// user will have to manually update the manifest.
if (installerHasMultipleDisplayVersions)
{
Logger.WarnLocalized(nameof(Resources.InstallerWithMultipleDisplayVersions_Warning));
}

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

foreach (var installerUpdate in installerMetadataList)
Expand Down Expand Up @@ -713,39 +760,42 @@ private string ObtainMatchingRelativeFilePath(string oldRelativeFilePath, string
}

/// <summary>
/// Parses the installer urls for any architecture or scope overrides.
/// Parses the installer urls for any additional arguments.
/// </summary>
/// <param name="installerUrlsToBeParsed">List of installer URLs to be parsed for architecture overrides.</param>
/// <param name="installerUrlsToBeParsed">List of installer URLs to be parsed for additional arguments.</param>
/// <returns>List of <see cref="InstallerMetadata"/> helper objects used for updating the installers.</returns>
private List<InstallerMetadata> ParseInstallerUrlsForOverrides(List<string> installerUrlsToBeParsed)
private List<InstallerMetadata> ParseInstallerUrlsForArguments(List<string> installerUrlsToBeParsed)
{
// There can be at most 4 elements at one time (installerUrl|archOverride|scopeOverride|displayVersion)
const int MaxUrlArgumentLimit = 4;

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('|');
// '|' character indicates that user is providing additional arguments for the installer URL.
string[] installerUrlArguments = item.Split('|');

// There can be at most 3 elements at one time (installerUrl|archOverride|scopeOverride)
if (installerUrlOverride.Length > 3)
if (installerUrlArguments.Length > MaxUrlArgumentLimit)
{
Logger.ErrorLocalized(nameof(Resources.OverrideLimitExceeded_Error), item);
Logger.ErrorLocalized(nameof(Resources.ArgumentLimitExceeded_Error), item);
return null;
}

installerMetadata.InstallerUrl = installerUrlOverride[0];
installerMetadata.InstallerUrl = installerUrlArguments[0];

bool archOverridePresent = false;
bool scopeOverridePresent = false;
bool displayVersionPresent = false;

for (int i = 1; i < installerUrlOverride.Length; i++)
for (int i = 1; i < installerUrlArguments.Length; i++)
{
string overrideString = installerUrlOverride[i];
Architecture? overrideArch = overrideString.ToEnumOrDefault<Architecture>();
Scope? overrideScope = overrideString.ToEnumOrDefault<Scope>();
string argumentString = installerUrlArguments[i];
Architecture? overrideArch = argumentString.ToEnumOrDefault<Architecture>();
Scope? overrideScope = argumentString.ToEnumOrDefault<Scope>();

if (overrideArch.HasValue)
{
Expand Down Expand Up @@ -773,9 +823,16 @@ private List<InstallerMetadata> ParseInstallerUrlsForOverrides(List<string> inst
installerMetadata.OverrideScope = overrideScope.Value;
}
}

// If value is not a convertible enum, it is assumed to be a display version.
else if (!string.IsNullOrEmpty(argumentString) && !displayVersionPresent)
{
displayVersionPresent = true;
installerMetadata.DisplayVersion = argumentString;
}
else
{
Logger.ErrorLocalized(nameof(Resources.UnableToParseOverride_Error), overrideString);
Logger.ErrorLocalized(nameof(Resources.UnableToParseArgument_Error), argumentString);
return null;
}
}
Expand Down
60 changes: 48 additions & 12 deletions src/WingetCreateCLI/Properties/Resources.Designer.cs

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

24 changes: 19 additions & 5 deletions src/WingetCreateCLI/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -803,9 +803,9 @@
<data name="MultipleArchitectureOverride_Error" xml:space="preserve">
<value>Multiple architectures detected. Only one architecture can be specified for an override.</value>
</data>
<data name="UnableToParseOverride_Error" xml:space="preserve">
<value>Unable to parse the specified override {0}.</value>
<comment>{0} - represents the override string that failed to parse.</comment>
<data name="UnableToParseArgument_Error" xml:space="preserve">
<value>Unable to parse the specified argument {0}.</value>
<comment>{0} - represents the url argument that failed to parse.</comment>
</data>
<data name="Example_UpdateCommand_OverrideArchitecture" xml:space="preserve">
<value>Override the architecture of an installer</value>
Expand Down Expand Up @@ -995,8 +995,8 @@
<data name="MultipleScopeOverride_Error" xml:space="preserve">
<value>Multiple scopes detected. Only one scope can be specified for an override.</value>
</data>
<data name="OverrideLimitExceeded_Error" xml:space="preserve">
<value>Too many overrides specified for the following installer URL: {0}</value>
<data name="ArgumentLimitExceeded_Error" xml:space="preserve">
<value>Too many arguments specified for the following installer URL: {0}</value>
<comment>{0} - represents the installer URL argument that the user is providing. The installer URL can be modified by appending '|scope|architecture' to override the detected values for that particular installer.</comment>
</data>
<data name="OverridingScope_Warning" xml:space="preserve">
Expand Down Expand Up @@ -1357,4 +1357,18 @@
<data name="Example_TokenCommand_StoreNewToken" xml:space="preserve">
<value>Store a new GitHub token in your local cache</value>
</data>
<data name="UnchangedDisplayVersion_Warning" xml:space="preserve">
<value>Base manifest contains DisplayVersion that has not been updated. Use --display-version CLI arg or provide the version in the installer URL.</value>
</data>
<data name="DisplayVersion_HelpText" xml:space="preserve">
<value>Version to be used when updating the display version field. Version provided in the installer URL arguments will take precendence over this value.</value>
</data>
<data name="UsingDisplayVersion_Message" xml:space="preserve">
<value>Using display version '{0}' for {1}</value>
<comment>{0} - will be replaced with the display version provided in the installer URL
{1} - will be replaced by the installer URL</comment>
</data>
<data name="InstallerWithMultipleDisplayVersions_Warning" xml:space="preserve">
<value>Single installer with multiple display versions detected. Winget-Create will only update the first DisplayVersion for a given installer.</value>
</data>
</root>
Loading
Loading