Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@
packagefamilyname
packageidentifier
packagename
PACKAGEVERSION
PACKAGESSCHEMA
paket
Params
Expand Down Expand Up @@ -369,8 +370,8 @@
uap
UBool
UBreak
ubrk

Check warning on line 373 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Entry has inconsistent line endings (unexpected-line-ending)

Check warning on line 373 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Report (PR)

Entry has inconsistent line endings (unexpected-line-ending)

Check warning on line 373 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Report (PR)

Entry has inconsistent line endings (unexpected-line-ending)
ucol

Check warning on line 374 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Entry has inconsistent line endings (unexpected-line-ending)

Check warning on line 374 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Report (PR)

Entry has inconsistent line endings (unexpected-line-ending)

Check warning on line 374 in .github/actions/spelling/allow.txt

View workflow job for this annotation

GitHub Actions / Report (PR)

Entry has inconsistent line endings (unexpected-line-ending)
UCollation
UCollator
UError
Expand Down Expand Up @@ -438,3 +439,3 @@
XResource
XTOKEN
xunit
13 changes: 12 additions & 1 deletion doc/ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
## New in v1.29

Nothing yet.

### Manifest `<PACKAGEVERSION>` token expansion

Added `<PACKAGEVERSION>` token which will use the `PackageVersion` from the manifest in place of the token. This applies to the following manifest fields:

* `NestedInstallerFiles.RelativeFilePath`
* `ProductCode`
* `AppsAndFeaturesEntries.DisplayName`
* `AppsAndFeaturesEntries.ProductCode`
* `InstallationMetadata.DefaultInstallLocation`
* `InstallationMetadata.Files.RelativeFilePath`
* `ReleaseNotesUrl`

## Bug Fixes

Expand Down
3 changes: 3 additions & 0 deletions src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,9 @@
<CopyFileToFolders Include="TestData\Manifest-Good-NoArpVersionDeclared.yaml">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\Manifest-Good-PackageVersionToken.yaml">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
<CopyFileToFolders Include="TestData\Manifest-Good-SingleArpVersionDeclared.yaml">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
Expand Down
32 changes: 16 additions & 16 deletions src/AppInstallerCLITests/RestInterface_1_4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ namespace
],
"Moniker": "FooBarMoniker",
"ReleaseNotes": "Default release notes",
"ReleaseNotesUrl": "https://DefaultReleaseNotes.net",
"ReleaseNotesUrl": "https://DefaultReleaseNotes.net/<PACKAGEVERSION>",
"Agreements": [{
"AgreementLabel": "DefaultLabel",
"Agreement": "DefaultText",
Expand Down Expand Up @@ -103,7 +103,7 @@ namespace
"BarFr"
],
"ReleaseNotes": "Release notes",
"ReleaseNotesUrl": "https://ReleaseNotes.net",
"ReleaseNotesUrl": "https://ReleaseNotes.net/<PACKAGEVERSION>",
"Agreements": [{
"AgreementLabel": "Label",
"Agreement": "Text",
Expand Down Expand Up @@ -171,18 +171,18 @@ namespace
"FooBarBaz"
]
},
"ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d",
"ProductCode": "5b6e0f8a-<PACKAGEVERSION>",
"ReleaseDate": "2021-01-01",
"InstallerAbortsTerminal": true,
"InstallLocationRequired": true,
"RequireExplicitUpgrade": true,
"UnsupportedOSArchitectures": [ "arm" ],
"ElevationRequirement": "elevatesSelf",
"AppsAndFeaturesEntries": [{
"DisplayName": "DisplayName",
"DisplayName": "DisplayName-<PACKAGEVERSION>",
"DisplayVersion": "DisplayVersion",
"Publisher": "Publisher",
"ProductCode": "ProductCode",
"ProductCode": "ProductCode-<PACKAGEVERSION>",
"UpgradeCode": "UpgradeCode",
"InstallerType": "exe"
}],
Expand All @@ -198,13 +198,13 @@ namespace
"DisplayInstallWarnings": true,
"UnsupportedArguments": [ "log" ],
"NestedInstallerFiles": [{
"RelativeFilePath": "test\\app.exe",
"RelativeFilePath": "test\\app.<PACKAGEVERSION>.exe",
"PortableCommandAlias": "test.exe"
}],
"InstallationMetadata": {
"DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation",
"DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation\\<PACKAGEVERSION>",
"Files": [{
"RelativeFilePath": "test\\app.exe",
"RelativeFilePath": "test\\app.<PACKAGEVERSION>.exe",
"FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6",
"FileType": "launch",
"InvocationParameter": "/parameter",
Expand Down Expand Up @@ -241,7 +241,7 @@ namespace
REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(1) == "Foo");
REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(2) == "Bar");
REQUIRE(manifest.DefaultLocalization.Get<Localization::ReleaseNotes>() == "Default release notes");
REQUIRE(manifest.DefaultLocalization.Get<Localization::ReleaseNotesUrl>() == "https://DefaultReleaseNotes.net");
REQUIRE(manifest.DefaultLocalization.Get<Localization::ReleaseNotesUrl>() == "https://DefaultReleaseNotes.net/3.0.0abc");
REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().size() == 1);
REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().at(0).Label == "DefaultLabel");
REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().at(0).AgreementText == "DefaultText");
Expand Down Expand Up @@ -273,7 +273,7 @@ namespace
REQUIRE(frenchLocalization.Get<Localization::Tags>().at(1) == "FooFr");
REQUIRE(frenchLocalization.Get<Localization::Tags>().at(2) == "BarFr");
REQUIRE(frenchLocalization.Get<Localization::ReleaseNotes>() == "Release notes");
REQUIRE(frenchLocalization.Get<Localization::ReleaseNotesUrl>() == "https://ReleaseNotes.net");
REQUIRE(frenchLocalization.Get<Localization::ReleaseNotesUrl>() == "https://ReleaseNotes.net/3.0.0abc");
REQUIRE(frenchLocalization.Get<Localization::Agreements>().size() == 1);
REQUIRE(frenchLocalization.Get<Localization::Agreements>().at(0).Label == "Label");
REQUIRE(frenchLocalization.Get<Localization::Agreements>().at(0).AgreementText == "Text");
Expand Down Expand Up @@ -320,7 +320,7 @@ namespace
REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0"));
REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz"));
REQUIRE(actualInstaller.PackageFamilyName == "");
REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d");
REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3.0.0abc");
REQUIRE(actualInstaller.ReleaseDate == "2021-01-01");
REQUIRE(actualInstaller.InstallerAbortsTerminal);
REQUIRE(actualInstaller.InstallLocationRequired);
Expand All @@ -329,10 +329,10 @@ namespace
REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1);
REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm);
REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1);
REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName");
REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName-3.0.0abc");
REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion");
REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher");
REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode");
REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode-3.0.0abc");
REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode");
REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe);
REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1);
Expand All @@ -344,11 +344,11 @@ namespace
REQUIRE(actualInstaller.UnsupportedArguments.size() == 1);
REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log);
REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1);
REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe");
REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.3.0.0abc.exe");
REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe");
REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation");
REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation\\3.0.0abc");
REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1);
REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe");
REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.3.0.0abc.exe");
REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch);
REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6"));
REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json

PackageIdentifier: microsoft.msixsdk
PackageVersion: 1.2.3.4
PackageLocale: en-US
PackageName: AppInstaller Test Installer
Publisher: Microsoft Corporation
License: Test
ShortDescription: Test manifest for PACKAGEVERSION token expansion.
ReleaseNotesUrl: https://example.com/releases/<PACKAGEVERSION>
Installers:
- Architecture: x64
InstallerUrl: https://ThisIsNotUsed
InstallerType: zip
InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B
ProductCode: Product-<PACKAGEVERSION>
AppsAndFeaturesEntries:
- DisplayName: DisplayName-<PACKAGEVERSION>
ProductCode: ArpProduct-<PACKAGEVERSION>
NestedInstallerType: exe
NestedInstallerFiles:
- RelativeFilePath: setup\AppInstallerTestExeInstaller.<PACKAGEVERSION>.exe
InstallationMetadata:
DefaultInstallLocation: C:\Program Files\Test\<PACKAGEVERSION>
Files:
- RelativeFilePath: app\AppInstallerTestExeInstaller.<PACKAGEVERSION>.exe
ManifestType: singleton
ManifestVersion: 1.4.0
18 changes: 18 additions & 0 deletions src/AppInstallerCLITests/YamlManifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,24 @@ TEST_CASE("ReadGoodManifestWithSpaces", "[ManifestValidation]")
REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "appxbundle", "msix", "msixbundle" });
}

TEST_CASE("ReadManifest_ExpandPackageVersionToken", "[ManifestValidation]")
{
Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-PackageVersionToken.yaml"));

REQUIRE(manifest.Version == "1.2.3.4");
REQUIRE(manifest.DefaultLocalization.Get<Localization::ReleaseNotesUrl>() == "https://example.com/releases/1.2.3.4");
REQUIRE(manifest.Installers.size() == 1);
REQUIRE(manifest.Installers[0].NestedInstallerFiles.size() == 1);
REQUIRE(manifest.Installers[0].NestedInstallerFiles[0].RelativeFilePath == "setup\\AppInstallerTestExeInstaller.1.2.3.4.exe");
REQUIRE(manifest.Installers[0].ProductCode == "Product-1.2.3.4");
REQUIRE(manifest.Installers[0].AppsAndFeaturesEntries.size() == 1);
REQUIRE(manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayName == "DisplayName-1.2.3.4");
REQUIRE(manifest.Installers[0].AppsAndFeaturesEntries[0].ProductCode == "ArpProduct-1.2.3.4");
REQUIRE(manifest.Installers[0].InstallationMetadata.DefaultInstallLocation == "C:\\Program Files\\Test\\1.2.3.4");
REQUIRE(manifest.Installers[0].InstallationMetadata.Files.size() == 1);
REQUIRE(manifest.Installers[0].InstallationMetadata.Files[0].RelativeFilePath == "app\\AppInstallerTestExeInstaller.1.2.3.4.exe");
}

TEST_CASE("ReadGoodManifests", "[ManifestValidation]")
{
ManifestTestCase TestCases[] =
Expand Down
59 changes: 59 additions & 0 deletions src/AppInstallerCommonCore/Manifest/Manifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@ namespace AppInstaller::Manifest
{
namespace
{
bool ReplacePackageVersionTokenInValue(std::string& value, const std::string& packageVersion)
{
return Utility::FindAndReplace(value, std::string{ PACKAGE_VERSION_TOKEN }, packageVersion);
}

// ReleaseNotesUrl is stored in ManifestLocalization's typed map; unlike plain struct fields,
// it must be read via Get<> and written back via Add<> after token expansion.
void ExpandPackageVersionTokenInReleaseNotesUrl(ManifestLocalization& localization, const std::string& packageVersion)
{
if (localization.Contains(Localization::ReleaseNotesUrl))
{
auto releaseNotesUrl = localization.Get<Localization::ReleaseNotesUrl>();
if (ReplacePackageVersionTokenInValue(releaseNotesUrl, packageVersion))
{
localization.Add<Localization::ReleaseNotesUrl>(std::move(releaseNotesUrl));
}
}
}

void AddFoldedStringToSetIfNotEmpty(std::set<string_t>& set, const string_t& value)
{
if (!value.empty())
Expand All @@ -18,6 +37,46 @@ namespace AppInstaller::Manifest
}
}

void ExpandManifestPackageVersionTokens(Manifest& manifest)
{
if (manifest.Version.empty())
{
return;
}

const std::string packageVersion = manifest.Version;

for (auto& installer : manifest.Installers)
{
for (auto& nestedInstallerFile : installer.NestedInstallerFiles)
{
ReplacePackageVersionTokenInValue(nestedInstallerFile.RelativeFilePath, packageVersion);
}

ReplacePackageVersionTokenInValue(installer.ProductCode, packageVersion);

for (auto& arpEntry : installer.AppsAndFeaturesEntries)
{
ReplacePackageVersionTokenInValue(arpEntry.DisplayName, packageVersion);
ReplacePackageVersionTokenInValue(arpEntry.ProductCode, packageVersion);
}

ReplacePackageVersionTokenInValue(installer.InstallationMetadata.DefaultInstallLocation, packageVersion);

for (auto& installedFile : installer.InstallationMetadata.Files)
{
ReplacePackageVersionTokenInValue(installedFile.RelativeFilePath, packageVersion);
}
}

ExpandPackageVersionTokenInReleaseNotesUrl(manifest.DefaultLocalization, packageVersion);

for (auto& localization : manifest.Localizations)
{
ExpandPackageVersionTokenInReleaseNotesUrl(localization, packageVersion);
}
}

void Manifest::ApplyLocale(const std::string& locale)
{
CurrentLocalization = DefaultLocalization;
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCommonCore/Manifest/YamlParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ namespace AppInstaller::Manifest::YamlParser

auto errors = ManifestYamlPopulator::PopulateManifest(manifestDoc, manifest, manifestVersion, validateOption, shadowNode);
std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end()));
ExpandManifestPackageVersionTokens(manifest);

// Extra semantic validations after basic validation and field population
if (validateOption.FullValidation)
Expand Down
10 changes: 9 additions & 1 deletion src/AppInstallerCommonCore/Public/winget/Manifest.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@
#include <winget/ManifestInstaller.h>
#include <winget/ManifestLocalization.h>

#include <string_view>
#include <vector>

namespace AppInstaller::Manifest
{
struct Manifest;

inline constexpr std::string_view PACKAGE_VERSION_TOKEN = "<PACKAGEVERSION>";

// Expands supported manifest string tokens using package-level manifest data.
void ExpandManifestPackageVersionTokens(Manifest& manifest);

// Representation of the parsed manifest file.
struct Manifest
{
Expand Down Expand Up @@ -72,4 +80,4 @@ namespace AppInstaller::Manifest
std::function<const string_t& (const ManifestInstaller&)> extractStringFromInstaller = {},
std::function<const string_t& (const AppsAndFeaturesEntry&)> extractStringFromAppsAndFeaturesEntry = {}) const;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0::Json
}
}

ExpandManifestPackageVersionTokens(manifest);
manifests.emplace_back(std::move(manifest));
}

Expand Down
Loading