From 6ea869528b43193762f0c5ea29f679311b6e3fb4 Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Sat, 23 Jan 2021 18:46:40 -0800 Subject: [PATCH 01/18] vcxitems for valijson --- src/AppInstallerCLI.sln | 4 + src/Valijson/Valijson.vcxitems | 57 ++++++++++++ src/Valijson/Valijson.vcxitems.filters | 115 +++++++++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 src/Valijson/Valijson.vcxitems create mode 100644 src/Valijson/Valijson.vcxitems.filters diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index ee39aea80c..2561eb7c05 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -63,8 +63,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinGetYamlFuzzing", "WinGet EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalhostWebServer", "LocalhostWebServer\LocalhostWebServer.csproj", "{3BAF989F-7F65-465B-ACE8-BAFE42D1017E}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Valijson", "Valijson\Valijson.vcxitems", "{358BC478-0624-4AD1-A933-0422B5292AF8}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution + Valijson\Valijson.vcxitems*{358bc478-0624-4ad1-a933-0422b5292af8}*SharedItemsImports = 9 catch2\catch2.vcxitems*{5295e21e-9868-4de2-a177-fbb97b36579b}*SharedItemsImports = 9 binver\binver.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4 binver\binver.vcxitems*{6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}*SharedItemsImports = 9 @@ -414,6 +417,7 @@ Global {82B39FDA-E86B-4713-A873-9D56DE00247A} = {60618CAC-2995-4DF9-9914-45C6FC02C995} {1622DA16-914F-4F57-A259-D5169003CC8C} = {6D7776A8-42FE-46DD-B0F8-712F35EA0C79} {3BAF989F-7F65-465B-ACE8-BAFE42D1017E} = {EA8CD934-0702-4911-A2C5-A40600E616DE} + {358BC478-0624-4AD1-A933-0422B5292AF8} = {60618CAC-2995-4DF9-9914-45C6FC02C995} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B6FDB70C-A751-422C-ACD1-E35419495857} diff --git a/src/Valijson/Valijson.vcxitems b/src/Valijson/Valijson.vcxitems new file mode 100644 index 0000000000..9012409189 --- /dev/null +++ b/src/Valijson/Valijson.vcxitems @@ -0,0 +1,57 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {358bc478-0624-4ad1-a933-0422b5292af8} + + + + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Valijson/Valijson.vcxitems.filters b/src/Valijson/Valijson.vcxitems.filters new file mode 100644 index 0000000000..e094258b06 --- /dev/null +++ b/src/Valijson/Valijson.vcxitems.filters @@ -0,0 +1,115 @@ + + + + + {7a0239af-b4b5-48ba-a16c-4759e2fe7b43} + + + {f503d3b0-5fd9-4ef4-9db9-5e48fe68db82} + + + {bce3512d-8612-4a2e-a2ad-7156b0d2f607} + + + {e6b3c438-739c-4dbc-bfd0-f0dd8ff97467} + + + + + adapters + + + adapters + + + adapters + + + adapters + + + adapters + + + adapters + + + adapters + + + adapters + + + adapters + + + adapters + + + adapters + + + adapters + + + constraints + + + constraints + + + constraints + + + constraints + + + internal + + + internal + + + internal + + + internal + + + internal + + + internal + + + utils + + + utils + + + utils + + + utils + + + utils + + + utils + + + utils + + + utils + + + utils + + + utils + + + \ No newline at end of file From 6a598a98693964d1c7e0f1bfe56e96bc0850e4fa Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Sat, 23 Jan 2021 19:24:31 -0800 Subject: [PATCH 02/18] vcxitems for schemas --- src/AppInstallerCLI.sln | 4 + src/ManifestSchema/ManifestSchema.h | 12 + src/ManifestSchema/ManifestSchema.rc | 71 +++ src/ManifestSchema/ManifestSchema.vcxitems | 31 + .../ManifestSchema.vcxitems.filters | 28 + src/ManifestSchema/resource.h | 14 + src/ManifestSchema/schema/schema_v0.1.0.json | 316 ++++++++++ .../schema/schema_v1.0.0_defaultLocale.json | 142 +++++ .../schema/schema_v1.0.0_installer.json | 439 ++++++++++++++ .../schema/schema_v1.0.0_locale.json | 138 +++++ .../schema/schema_v1.0.0_singleton.json | 540 ++++++++++++++++++ .../schema/schema_v1.0.0_version.json | 43 ++ 12 files changed, 1778 insertions(+) create mode 100644 src/ManifestSchema/ManifestSchema.h create mode 100644 src/ManifestSchema/ManifestSchema.rc create mode 100644 src/ManifestSchema/ManifestSchema.vcxitems create mode 100644 src/ManifestSchema/ManifestSchema.vcxitems.filters create mode 100644 src/ManifestSchema/resource.h create mode 100644 src/ManifestSchema/schema/schema_v0.1.0.json create mode 100644 src/ManifestSchema/schema/schema_v1.0.0_defaultLocale.json create mode 100644 src/ManifestSchema/schema/schema_v1.0.0_installer.json create mode 100644 src/ManifestSchema/schema/schema_v1.0.0_locale.json create mode 100644 src/ManifestSchema/schema/schema_v1.0.0_singleton.json create mode 100644 src/ManifestSchema/schema/schema_v1.0.0_version.json diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index 2561eb7c05..e985381d8b 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -65,12 +65,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalhostWebServer", "Local EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Valijson", "Valijson\Valijson.vcxitems", "{358BC478-0624-4AD1-A933-0422B5292AF8}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ManifestSchema", "ManifestSchema\ManifestSchema.vcxitems", "{7D05F64D-CE5A-42AA-A2C1-E91458F061CF}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution Valijson\Valijson.vcxitems*{358bc478-0624-4ad1-a933-0422b5292af8}*SharedItemsImports = 9 catch2\catch2.vcxitems*{5295e21e-9868-4de2-a177-fbb97b36579b}*SharedItemsImports = 9 binver\binver.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4 binver\binver.vcxitems*{6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}*SharedItemsImports = 9 + ManifestSchema\ManifestSchema.vcxitems*{7d05f64d-ce5a-42aa-a2c1-e91458f061cf}*SharedItemsImports = 9 catch2\catch2.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 binver\binver.vcxitems*{fb313532-38b0-4676-9303-ab200aa13576}*SharedItemsImports = 4 EndGlobalSection @@ -418,6 +421,7 @@ Global {1622DA16-914F-4F57-A259-D5169003CC8C} = {6D7776A8-42FE-46DD-B0F8-712F35EA0C79} {3BAF989F-7F65-465B-ACE8-BAFE42D1017E} = {EA8CD934-0702-4911-A2C5-A40600E616DE} {358BC478-0624-4AD1-A933-0422B5292AF8} = {60618CAC-2995-4DF9-9914-45C6FC02C995} + {7D05F64D-CE5A-42AA-A2C1-E91458F061CF} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B6FDB70C-A751-422C-ACD1-E35419495857} diff --git a/src/ManifestSchema/ManifestSchema.h b/src/ManifestSchema/ManifestSchema.h new file mode 100644 index 0000000000..7151b481f3 --- /dev/null +++ b/src/ManifestSchema/ManifestSchema.h @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#define MANIFESTSCHEMA_RESOURCE_TYPE 200 + +#define IDX_MANIFEST_SCHEMA_PREVIEW 201 +#define IDX_MANIFEST_SCHEMA_V1_SINGLETON 202 +#define IDX_MANIFEST_SCHEMA_V1_VERSION 203 +#define IDX_MANIFEST_SCHEMA_V1_INSTALLER 204 +#define IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE 205 +#define IDX_MANIFEST_SCHEMA_V1_LOCALE 206 \ No newline at end of file diff --git a/src/ManifestSchema/ManifestSchema.rc b/src/ManifestSchema/ManifestSchema.rc new file mode 100644 index 0000000000..2de872b34f --- /dev/null +++ b/src/ManifestSchema/ManifestSchema.rc @@ -0,0 +1,71 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +#include "ManifestSchema.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE 9, 1 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// Manifest schema +// +IDX_MANIFEST_SCHEMA_PREVIEW MANIFESTSCHEMA_RESOURCE_TYPE "schema\\schema_v0.1.0.json" +IDX_MANIFEST_SCHEMA_V1_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "schema\\schema_v1.0.0_singleton.json" +IDX_MANIFEST_SCHEMA_V1_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "schema\\schema_v1.0.0_version.json" +IDX_MANIFEST_SCHEMA_V1_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "schema\\schema_v1.0.0_installer.json" +IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "schema\\schema_v1.0.0_defaultLocale.json" +IDX_MANIFEST_SCHEMA_V1_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "schema\\schema_v1.0.0_locale.json" diff --git a/src/ManifestSchema/ManifestSchema.vcxitems b/src/ManifestSchema/ManifestSchema.vcxitems new file mode 100644 index 0000000000..671144d5c5 --- /dev/null +++ b/src/ManifestSchema/ManifestSchema.vcxitems @@ -0,0 +1,31 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {7d05f64d-ce5a-42aa-a2c1-e91458f061cf} + + + + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ManifestSchema/ManifestSchema.vcxitems.filters b/src/ManifestSchema/ManifestSchema.vcxitems.filters new file mode 100644 index 0000000000..e9b69ffd28 --- /dev/null +++ b/src/ManifestSchema/ManifestSchema.vcxitems.filters @@ -0,0 +1,28 @@ + + + + + {d6998b42-8ce1-440a-ad12-8b505a284d7b} + + + + + schema + + + schema + + + schema + + + schema + + + schema + + + schema + + + \ No newline at end of file diff --git a/src/ManifestSchema/resource.h b/src/ManifestSchema/resource.h new file mode 100644 index 0000000000..0f5790ae89 --- /dev/null +++ b/src/ManifestSchema/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ManifestSchema.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/ManifestSchema/schema/schema_v0.1.0.json b/src/ManifestSchema/schema/schema_v0.1.0.json new file mode 100644 index 0000000000..b7e4d2b395 --- /dev/null +++ b/src/ManifestSchema/schema/schema_v0.1.0.json @@ -0,0 +1,316 @@ +{ + "$id": "https://aka.ms/winget-manifest.0.1.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A single-file manifest representing a package in winget community repo. v0.1.0 Preview", + "definitions": { + "InstallerType": { + "type": [ "string", "null" ], + "pattern": "^(([Ee][Xx][Ee])|([Mm][Ss][Ii])|([Mm][Ss][Ii][Xx])|([Ii][Nn][Nn][Oo])|([Ww][Ii][Xx])|([Nn][Uu][Ll][Ll][Ss][Oo][Ff][Tt])|([Aa][Pp][Pp][Xx])|([Zz][Ii][Pp])|([Bb][Uu][Rr][Nn]))$", + "description": "InstallerType is required under Installer node if it's not defined in root" + }, + "UpdateBehavior": { + "type": [ "string", "null" ], + "pattern": "^(([Ii][Nn][Ss][Tt][Aa][Ll][Ll])|([Uu][Nn][Ii][Nn][Ss][Tt][Aa][Ll][Ll][Pp][Rr][Ee][Vv][Ii][Oo][Uu][Ss]))$", + "description": "UpdateBehavior is used to specify desired action during package upgrade" + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "Description of the package" + }, + "Url": { + "type": [ "string", "null" ], + "format": "uri", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://", + "maxLength": 2000 + }, + "Homepage": { + "$ref": "#/definitions/Url", + "description": "Homepage is a Url where the user can find more information about the package" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "LicenseUrl provides a link to the license for the user to read" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "Custom switches will be passed directly to the installer by winget" + }, + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "Language": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Some installers include all localized resources. By specifying a Language switch, winget will pass the value of Language to the installer. This is not yet supported in Preview releases" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Update": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Update is the value that should be passed to the installer when user chooses an upgrade" + } + } + }, + "Installer": { + "type": "object", + "properties": { + "Arch": { + "type": "string", + "pattern": "^(([Aa][Rr][Mm])|([Xx]86)|([Xx]64)|([Aa][Rr][Mm]64)|([Nn][Ee][Uu][Tt][Rr][Aa][Ll]))$", + "description": "Arch is required. The installer architecture" + }, + "Url": { + "type": "string", + "format": "uri", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://", + "maxLength": 2000, + "description": "Url is required. The path to the installer." + }, + "Sha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "Language": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "Language is the specific language of the installer. Language must follow IETF language tag guidelines" + }, + "Scope": { + "type": [ "string", "null" ], + "pattern": "^(([Uu][Ss][Ee][Rr])|([Mm][Aa][Cc][Hh][Ii][Nn][Ee]))$", + "description": "Scope indicates if the installer is per user or per machine. Scope is not yet supported in Preview releases" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "UpdateBehavior": { + "$ref": "#/definitions/UpdateBehavior" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Switches": { + "$ref": "#/definitions/InstallerSwitches" + } + }, + "required": [ + "Arch", + "Url", + "Sha256" + ] + }, + "ManifestLocalization": { + "type": "object", + "properties": { + "Language": { + "type": "string", + "pattern": "^([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "Language is the specific language of the localization. Language must follow IETF language tag guidelines" + }, + "Description": { + "$ref": "#/definitions/Description" + }, + "Homepage": { + "$ref": "#/definitions/Homepage" + }, + "LicenseUrl": { + "$ref": "#/definitions/LicenseUrl" + } + }, + "required": [ + "Language" + ] + } + }, + "type": "object", + "properties": { + "ManifestVersion": { + "type": "string", + "default": "0.1.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + }, + "Id": { + "type": "string", + "pattern": "^[^\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+\\.[^\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 255, + "description": "Id is a required field. It MUST include the publisher name and package name separated by a period. For example: Publisher.Package" + }, + "Name": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "Name is a required field. The name of the package" + }, + "Version": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "minLength": 1, + "description": "Version is a required field. The version of the package" + }, + "Publisher": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "Publisher is a required field. The legal publisher name" + }, + "AppMoniker": { + "type": [ "string", "null" ], + "pattern": "^\\S+$", + "maxLength": 40, + "description": "AppMoniker is the common name someone may use to search for the package" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Channel a string representing the flight ring. For example: stable, beta, canary" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "The person or company responsible for authoring the package" + }, + "License": { + "type": "string", + "minLength": 1, + "maxLength": 40, + "description": "License is a required field. License provides the type of license the package is provided under" + }, + "MinOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "MinOSVersion uses the Windows version to limit installations on unsupported platforms" + }, + "Tags": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Tags is a comma separated list. They represent strings that user may use to search for the package" + }, + "Commands": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Commands is a comma separated list. They are the common executable or alias that user might type trying to run the package" + }, + "Protocols": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Protocols is a comma separated list. Protocols provides the list of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "FileExtensions is a comma separated list. FileExtensions provides the list of extensions the package can support" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "UpdateBehavior": { + "$ref": "#/definitions/UpdateBehavior" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Description": { + "$ref": "#/definitions/Description" + }, + "Homepage": { + "$ref": "#/definitions/Homepage" + }, + "LicenseUrl": { + "$ref": "#/definitions/LicenseUrl" + }, + "Switches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "uniqueItems": true + }, + "Localization": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/ManifestLocalization" + } + } + }, + "required": [ + "Id", + "Name", + "Version", + "Publisher", + "License", + "Installers", + "ManifestVersion" + ] +} \ No newline at end of file diff --git a/src/ManifestSchema/schema/schema_v1.0.0_defaultLocale.json b/src/ManifestSchema/schema/schema_v1.0.0_defaultLocale.json new file mode 100644 index 0000000000..9e4cad7dc8 --- /dev/null +++ b/src/ManifestSchema/schema/schema_v1.0.0_defaultLocale.json @@ -0,0 +1,142 @@ +{ + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.0.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.0.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "format": "uri", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://", + "maxLength": 2000, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 32, + "pattern": "^\\S+$", + "description": "Package moniker or tag" + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "pattern": "^([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 64, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 64, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "ManifestType": { + "type": "string", + "const": "defaultLocale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "ManifestType", + "ManifestVersion" + ] +} \ No newline at end of file diff --git a/src/ManifestSchema/schema/schema_v1.0.0_installer.json b/src/ManifestSchema/schema/schema_v1.0.0_installer.json new file mode 100644 index 0000000000..7a830a1307 --- /dev/null +++ b/src/ManifestSchema/schema/schema_v1.0.0_installer.json @@ -0,0 +1,439 @@ +{ + "$id": "https://aka.ms/winget-manifest.installer.1.0.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.0.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "Locale": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa" + ], + "description": "Enumeration of supported installer types" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Scope" + }, + "maxItems": 2, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "type": "integer", + "not": { + "enum": [ 0 ] + } + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of non-zero installer success exit codes" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[a-z][-a-z0-9\\.\\+]*$", + "maxLength": 39 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 40 + }, + "maxItems": 256, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": "object", + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/PackageIdentifier" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Installer": { + "type": "object", + "properties": { + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "format": "uri", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://", + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 128 + }, + "ManifestType": { + "type": "string", + "const": "singleton", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} \ No newline at end of file diff --git a/src/ManifestSchema/schema/schema_v1.0.0_locale.json b/src/ManifestSchema/schema/schema_v1.0.0_locale.json new file mode 100644 index 0000000000..b6c445bf09 --- /dev/null +++ b/src/ManifestSchema/schema/schema_v1.0.0_locale.json @@ -0,0 +1,138 @@ +{ + "$id": "https://aka.ms/winget-manifest.locale.1.0.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.0.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "format": "uri", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://", + "maxLength": 2000, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 32, + "pattern": "^\\S+$", + "description": "Package moniker or tag" + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "pattern": "^([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 64, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 64, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "ManifestType": { + "type": "string", + "const": "defaultLocale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "ManifestType", + "ManifestVersion" + ] +} \ No newline at end of file diff --git a/src/ManifestSchema/schema/schema_v1.0.0_singleton.json b/src/ManifestSchema/schema/schema_v1.0.0_singleton.json new file mode 100644 index 0000000000..891a7f2b2a --- /dev/null +++ b/src/ManifestSchema/schema/schema_v1.0.0_singleton.json @@ -0,0 +1,540 @@ +{ + "$id": "https://aka.ms/winget-manifest.singleton.1.0.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app in the OWC. v1.0.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Url": { + "type": [ "string", "null" ], + "format": "uri", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://", + "maxLength": 2000, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 32, + "pattern": "^\\S+$", + "description": "Package moniker or tag" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa" + ], + "description": "Enumeration of supported installer types" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Scope" + }, + "maxItems": 2, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "type": "integer", + "not": { + "enum": [ 0 ] + } + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of non-zero installer success exit codes" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[a-z][-a-z0-9\\.\\+]*$", + "maxLength": 39 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 40 + }, + "maxItems": 256, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": "object", + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/PackageIdentifier" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Installer": { + "type": "object", + "properties": { + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "format": "uri", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://", + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "$ref": "#/definitions/Locale", + "description": "The package meta-data locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 64, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 64, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1 + }, + "ManifestType": { + "type": "string", + "const": "singleton", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} \ No newline at end of file diff --git a/src/ManifestSchema/schema/schema_v1.0.0_version.json b/src/ManifestSchema/schema/schema_v1.0.0_version.json new file mode 100644 index 0000000000..70fdcde64b --- /dev/null +++ b/src/ManifestSchema/schema/schema_v1.0.0_version.json @@ -0,0 +1,43 @@ +{ + "$id": "https://aka.ms/winget-manifest.version.1.0.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.0.0", + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "DefaultLocale": { + "type": "string", + "pattern": "^([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The default package meta-data locale" + }, + "ManifestType": { + "type": "string", + "const": "version", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "DefaultLocale", + "ManifestType", + "ManifestVersion" + ] +} \ No newline at end of file From b2e14f79c9af6340a138096a10b08d12b36f92bb Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Mon, 25 Jan 2021 19:37:11 -0800 Subject: [PATCH 03/18] initial check --- src/AppInstallerCLI.sln | 3 +- .../AppInstallerCommonCore.vcxproj | 13 +- .../AppInstallerCommonCore.vcxproj.filters | 30 +- .../Manifest/Manifest.cpp | 88 ---- .../Manifest/ManifestCommon.cpp | 311 ++++++++++++++ .../Manifest/ManifestInstaller.cpp | 198 --------- .../Manifest/ManifestSchemaValidation.cpp | 168 ++++++++ .../Manifest/ManifestYamlPopulator.cpp | 96 +++++ .../Manifest/YamlParser.cpp | 403 ++++++++++++++---- .../Public/winget/Manifest.h | 76 +--- .../Public/winget/ManifestInstaller.h | 93 ++-- .../Public/winget/ManifestLocalization.h | 167 +++++++- .../Public/winget/ManifestSchemaValidation.h | 15 + .../Public/winget/ManifestTypes.h | 138 ++++++ .../Public/winget/ManifestValidation.h | 20 + .../Public/winget/ManifestYamlParser.h | 22 +- .../Public/winget/ManifestYamlPopulator.h | 49 +++ .../Public/winget/Yaml.h | 3 + src/AppInstallerCommonCore/Yaml.cpp | 8 + src/AppInstallerCommonCore/pch.h | 7 + .../ManifestSchema.vcxitems.filters | 7 + src/Valijson/Valijson.vcxitems | 2 +- src/Valijson/Valijson.vcxitems.filters | 7 + 23 files changed, 1398 insertions(+), 526 deletions(-) delete mode 100644 src/AppInstallerCommonCore/Manifest/Manifest.cpp create mode 100644 src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp delete mode 100644 src/AppInstallerCommonCore/Manifest/ManifestInstaller.cpp create mode 100644 src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp create mode 100644 src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp create mode 100644 src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h create mode 100644 src/AppInstallerCommonCore/Public/winget/ManifestTypes.h create mode 100644 src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index e985381d8b..e668e9e3d1 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -71,10 +71,11 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution Valijson\Valijson.vcxitems*{358bc478-0624-4ad1-a933-0422b5292af8}*SharedItemsImports = 9 catch2\catch2.vcxitems*{5295e21e-9868-4de2-a177-fbb97b36579b}*SharedItemsImports = 9 + ManifestSchema\ManifestSchema.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 + Valijson\Valijson.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 binver\binver.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4 binver\binver.vcxitems*{6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}*SharedItemsImports = 9 ManifestSchema\ManifestSchema.vcxitems*{7d05f64d-ce5a-42aa-a2c1-e91458f061cf}*SharedItemsImports = 9 - catch2\catch2.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 binver\binver.vcxitems*{fb313532-38b0-4676-9303-ab200aa13576}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj index 28da9287c3..c731adf8d0 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -90,7 +90,10 @@ - + + + + @@ -268,9 +271,12 @@ + + + @@ -303,9 +309,10 @@ true - - + + + true diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters index 1f5a663e35..6ed5490ea4 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -123,9 +123,6 @@ Public\winget - - Public\winget - Public\winget @@ -141,6 +138,18 @@ Public\winget + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + @@ -221,12 +230,6 @@ Source Files - - Manifest - - - Manifest - Manifest @@ -236,6 +239,15 @@ Source Files + + Manifest + + + Manifest + + + Manifest + diff --git a/src/AppInstallerCommonCore/Manifest/Manifest.cpp b/src/AppInstallerCommonCore/Manifest/Manifest.cpp deleted file mode 100644 index 9ceee4cc83..0000000000 --- a/src/AppInstallerCommonCore/Manifest/Manifest.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/Manifest.h" -#include "winget/ManifestValidation.h" - -namespace AppInstaller::Manifest -{ - ManifestVer::ManifestVer(std::string_view version) - { - bool validationSuccess = true; - - // Separate the extensions out - size_t hyphenPos = version.find_first_of('-'); - if (hyphenPos != std::string_view::npos) - { - // The first part is the main version - Assign(std::string{ version.substr(0, hyphenPos) }, "."); - - // The second part is the extensions - hyphenPos += 1; - while (hyphenPos < version.length()) - { - size_t newPos = version.find_first_of('-', hyphenPos); - - size_t length = (newPos == std::string::npos ? version.length() : newPos) - hyphenPos; - m_extensions.emplace_back(std::string{ version.substr(hyphenPos, length) }, "."); - - hyphenPos += length + 1; - } - } - else - { - Assign(std::string{ version }, "."); - } - - if (m_parts.size() > 3) - { - validationSuccess = false; - } - else - { - for (size_t i = 0; i < m_parts.size(); i++) - { - if (!m_parts[i].Other.empty()) - { - validationSuccess = false; - break; - } - } - - for (const Version& ext : m_extensions) - { - if (ext.GetParts().empty() || ext.GetParts()[0].Integer != 0) - { - validationSuccess = false; - break; - } - } - } - - if (!validationSuccess) - { - std::vector errors; - errors.emplace_back(ManifestError::InvalidFieldValue, "ManifestVersion", std::string{ version }); - THROW_EXCEPTION(ManifestException(std::move(errors))); - } - } - - bool ManifestVer::HasExtension() const - { - return !m_extensions.empty(); - } - - bool ManifestVer::HasExtension(std::string_view extension) const - { - for (const Version& ext : m_extensions) - { - const auto& parts = ext.GetParts(); - if (!parts.empty() && parts[0].Integer == 0 && parts[0].Other == extension) - { - return true; - } - } - - return false; - } -} diff --git a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp new file mode 100644 index 0000000000..13ee3ee500 --- /dev/null +++ b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp @@ -0,0 +1,311 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/ManifestTypes.h" +#include "winget/ManifestValidation.h" + +namespace AppInstaller::Manifest +{ + namespace + { + enum class CompatibilitySet + { + None, + Exe, + Msi, + Msix, + }; + + CompatibilitySet GetCompatibilitySet(InstallerTypeEnum type) + { + switch (type) + { + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + return CompatibilitySet::Exe; + case InstallerTypeEnum::Wix: + case InstallerTypeEnum::Msi: + return CompatibilitySet::Msi; + case InstallerTypeEnum::Msix: + case InstallerTypeEnum::MSStore: + return CompatibilitySet::Msix; + default: + return CompatibilitySet::None; + } + } + } + + ManifestVer::ManifestVer(std::string_view version) + { + bool validationSuccess = true; + + // Separate the extensions out + size_t hyphenPos = version.find_first_of('-'); + if (hyphenPos != std::string_view::npos) + { + // The first part is the main version + Assign(std::string{ version.substr(0, hyphenPos) }, "."); + + // The second part is the extensions + hyphenPos += 1; + while (hyphenPos < version.length()) + { + size_t newPos = version.find_first_of('-', hyphenPos); + + size_t length = (newPos == std::string::npos ? version.length() : newPos) - hyphenPos; + m_extensions.emplace_back(std::string{ version.substr(hyphenPos, length) }, "."); + + hyphenPos += length + 1; + } + } + else + { + Assign(std::string{ version }, "."); + } + + if (m_parts.size() > 3) + { + validationSuccess = false; + } + else + { + for (size_t i = 0; i < m_parts.size(); i++) + { + if (!m_parts[i].Other.empty()) + { + validationSuccess = false; + break; + } + } + + for (const Version& ext : m_extensions) + { + if (ext.GetParts().empty() || ext.GetParts()[0].Integer != 0) + { + validationSuccess = false; + break; + } + } + } + + if (!validationSuccess) + { + std::vector errors; + errors.emplace_back(ManifestError::InvalidFieldValue, "ManifestVersion", std::string{ version }); + THROW_EXCEPTION(ManifestException(std::move(errors))); + } + } + + bool ManifestVer::HasExtension() const + { + return !m_extensions.empty(); + } + + bool ManifestVer::HasExtension(std::string_view extension) const + { + for (const Version& ext : m_extensions) + { + const auto& parts = ext.GetParts(); + if (!parts.empty() && parts[0].Integer == 0 && parts[0].Other == extension) + { + return true; + } + } + + return false; + } + + InstallerTypeEnum ConvertToInstallerTypeEnum(const std::string& in) + { + std::string inStrLower = Utility::ToLower(in); + InstallerTypeEnum result = InstallerTypeEnum::Unknown; + + if (inStrLower == "inno") + { + result = InstallerTypeEnum::Inno; + } + else if (inStrLower == "wix") + { + result = InstallerTypeEnum::Wix; + } + else if (inStrLower == "msi") + { + result = InstallerTypeEnum::Msi; + } + else if (inStrLower == "nullsoft") + { + result = InstallerTypeEnum::Nullsoft; + } + else if (inStrLower == "zip") + { + result = InstallerTypeEnum::Zip; + } + else if (inStrLower == "appx" || inStrLower == "msix") + { + result = InstallerTypeEnum::Msix; + } + else if (inStrLower == "exe") + { + result = InstallerTypeEnum::Exe; + } + else if (inStrLower == "burn") + { + result = InstallerTypeEnum::Burn; + } + else if (inStrLower == "msstore") + { + result = InstallerTypeEnum::MSStore; + } + + return result; + } + + UpdateBehaviorEnum ConvertToUpdateBehaviorEnum(const std::string& in) + { + UpdateBehaviorEnum result = UpdateBehaviorEnum::Unknown; + + if (Utility::CaseInsensitiveEquals(in, "install")) + { + result = UpdateBehaviorEnum::Install; + } + else if (Utility::CaseInsensitiveEquals(in, "uninstallprevious")) + { + result = UpdateBehaviorEnum::UninstallPrevious; + } + + return result; + } + + ScopeEnum ConvertToScopeEnum(const std::string& in) + { + ScopeEnum result = ScopeEnum::Unknown; + + if (Utility::CaseInsensitiveEquals(in, "user")) + { + result = ScopeEnum::User; + } + else if (Utility::CaseInsensitiveEquals(in, "machine")) + { + result = ScopeEnum::Machine; + } + + return result; + } + + ManifestTypeEnum ConvertToManifestTypeEnum(const std::string& in) + { + if (in == "singleton") + { + return ManifestTypeEnum::Singleton; + } + else if (in == "version") + { + return ManifestTypeEnum::Version; + } + else if (in == "installer") + { + return ManifestTypeEnum::Installer; + } + else if (in == "defaultLocale") + { + return ManifestTypeEnum::DefaultLocale; + } + else if (in == "locale") + { + return ManifestTypeEnum::Locale; + } + else if (in == "merged") + { + return ManifestTypeEnum::Merged; + } + else + { + THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), "Unsupported ManifestType: %s", in.c_str()); + } + } + + std::string_view InstallerTypeToString(InstallerTypeEnum installerType) + { + switch (installerType) + { + case InstallerTypeEnum::Exe: + return "Exe"sv; + case InstallerTypeEnum::Inno: + return "Inno"sv; + case InstallerTypeEnum::Msi: + return "Msi"sv; + case InstallerTypeEnum::Msix: + return "Msix"sv; + case InstallerTypeEnum::Nullsoft: + return "Nullsoft"sv; + case InstallerTypeEnum::Wix: + return "Wix"sv; + case InstallerTypeEnum::Zip: + return "Zip"sv; + case InstallerTypeEnum::Burn: + return "Burn"sv; + case InstallerTypeEnum::MSStore: + return "MSStore"sv; + } + + return "Unknown"sv; + } + + std::string_view ScopeToString(ScopeEnum scope) + { + switch (scope) + { + case ScopeEnum::User: + return "User"sv; + case ScopeEnum::Machine: + return "Machine"sv; + } + + return "Unknown"sv; + } + + bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType) + { + return (installerType == InstallerTypeEnum::Msix || installerType == InstallerTypeEnum::MSStore); + } + + bool DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType) + { + return ( + installerType == InstallerTypeEnum::Exe || + installerType == InstallerTypeEnum::Inno || + installerType == InstallerTypeEnum::Msi || + installerType == InstallerTypeEnum::Nullsoft || + installerType == InstallerTypeEnum::Wix || + installerType == InstallerTypeEnum::Burn + ); + } + + bool IsInstallerTypeCompatible(InstallerTypeEnum type1, InstallerTypeEnum type2) + { + // Unknown type cannot be compatible with any other + if (type1 == InstallerTypeEnum::Unknown || type2 == InstallerTypeEnum::Unknown) + { + return false; + } + + // Not unknown, so must be compatible + if (type1 == type2) + { + return true; + } + + CompatibilitySet set1 = GetCompatibilitySet(type1); + CompatibilitySet set2 = GetCompatibilitySet(type2); + + // If either is none, they can't be compatible + if (set1 == CompatibilitySet::None || set2 == CompatibilitySet::None) + { + return false; + } + + return set1 == set2; + } +} diff --git a/src/AppInstallerCommonCore/Manifest/ManifestInstaller.cpp b/src/AppInstallerCommonCore/Manifest/ManifestInstaller.cpp deleted file mode 100644 index 28ff6efb48..0000000000 --- a/src/AppInstallerCommonCore/Manifest/ManifestInstaller.cpp +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/ManifestInstaller.h" - -namespace AppInstaller::Manifest -{ - namespace - { - enum class CompatibilitySet - { - None, - Exe, - Msi, - Msix, - }; - - CompatibilitySet GetCompatibilitySet(ManifestInstaller::InstallerTypeEnum type) - { - switch (type) - { - case ManifestInstaller::InstallerTypeEnum::Inno: - case ManifestInstaller::InstallerTypeEnum::Nullsoft: - case ManifestInstaller::InstallerTypeEnum::Exe: - case ManifestInstaller::InstallerTypeEnum::Burn: - return CompatibilitySet::Exe; - case ManifestInstaller::InstallerTypeEnum::Wix: - case ManifestInstaller::InstallerTypeEnum::Msi: - return CompatibilitySet::Msi; - case ManifestInstaller::InstallerTypeEnum::Msix: - case ManifestInstaller::InstallerTypeEnum::MSStore: - return CompatibilitySet::Msix; - default: - return CompatibilitySet::None; - } - } - } - - ManifestInstaller::InstallerTypeEnum ManifestInstaller::ConvertToInstallerTypeEnum(const std::string& in) - { - std::string inStrLower = Utility::ToLower(in); - InstallerTypeEnum result = InstallerTypeEnum::Unknown; - - if (inStrLower == "inno") - { - result = InstallerTypeEnum::Inno; - } - else if (inStrLower == "wix") - { - result = InstallerTypeEnum::Wix; - } - else if (inStrLower == "msi") - { - result = InstallerTypeEnum::Msi; - } - else if (inStrLower == "nullsoft") - { - result = InstallerTypeEnum::Nullsoft; - } - else if (inStrLower == "zip") - { - result = InstallerTypeEnum::Zip; - } - else if (inStrLower == "appx" || inStrLower == "msix") - { - result = InstallerTypeEnum::Msix; - } - else if (inStrLower == "exe") - { - result = InstallerTypeEnum::Exe; - } - else if (inStrLower == "burn") - { - result = InstallerTypeEnum::Burn; - } - else if (inStrLower == "msstore") - { - result = InstallerTypeEnum::MSStore; - } - - return result; - } - - ManifestInstaller::UpdateBehaviorEnum ManifestInstaller::ConvertToUpdateBehaviorEnum(const std::string& in) - { - UpdateBehaviorEnum result = UpdateBehaviorEnum::Unknown; - - if (Utility::CaseInsensitiveEquals(in, "install")) - { - result = UpdateBehaviorEnum::Install; - } - else if (Utility::CaseInsensitiveEquals(in, "uninstallprevious")) - { - result = UpdateBehaviorEnum::UninstallPrevious; - } - - return result; - } - - ManifestInstaller::ScopeEnum ManifestInstaller::ConvertToScopeEnum(const std::string& in) - { - ScopeEnum result = ScopeEnum::Unknown; - - if (Utility::CaseInsensitiveEquals(in, "user")) - { - result = ScopeEnum::User; - } - else if (Utility::CaseInsensitiveEquals(in, "machine")) - { - result = ScopeEnum::Machine; - } - - return result; - } - - std::string_view ManifestInstaller::InstallerTypeToString(ManifestInstaller::InstallerTypeEnum installerType) - { - switch (installerType) - { - case InstallerTypeEnum::Exe: - return "Exe"sv; - case InstallerTypeEnum::Inno: - return "Inno"sv; - case InstallerTypeEnum::Msi: - return "Msi"sv; - case InstallerTypeEnum::Msix: - return "Msix"sv; - case InstallerTypeEnum::Nullsoft: - return "Nullsoft"sv; - case InstallerTypeEnum::Wix: - return "Wix"sv; - case InstallerTypeEnum::Zip: - return "Zip"sv; - case InstallerTypeEnum::Burn: - return "Burn"sv; - case InstallerTypeEnum::MSStore: - return "MSStore"sv; - } - - return "Unknown"sv; - } - - std::string_view ManifestInstaller::ScopeToString(ScopeEnum scope) - { - switch (scope) - { - case ScopeEnum::User: - return "User"sv; - case ScopeEnum::Machine: - return "Machine"sv; - } - - return "Unknown"sv; - } - - bool ManifestInstaller::DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType) - { - return (installerType == InstallerTypeEnum::Msix || installerType == InstallerTypeEnum::MSStore); - } - - bool ManifestInstaller::DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType) - { - return ( - installerType == InstallerTypeEnum::Exe || - installerType == InstallerTypeEnum::Inno || - installerType == InstallerTypeEnum::Msi || - installerType == InstallerTypeEnum::Nullsoft || - installerType == InstallerTypeEnum::Wix || - installerType == InstallerTypeEnum::Burn - ); - } - - bool ManifestInstaller::IsInstallerTypeCompatible(InstallerTypeEnum type1, InstallerTypeEnum type2) - { - // Unknown type cannot be compatible with any other - if (type1 == InstallerTypeEnum::Unknown || type2 == InstallerTypeEnum::Unknown) - { - return false; - } - - // Not unknown, so must be compatible - if (type1 == type2) - { - return true; - } - - CompatibilitySet set1 = GetCompatibilitySet(type1); - CompatibilitySet set2 = GetCompatibilitySet(type2); - - // If either is none, they can't be compatible - if (set1 == CompatibilitySet::None || set2 == CompatibilitySet::None) - { - return false; - } - - return set1 == set2; - } -} diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp new file mode 100644 index 0000000000..9a3e905f51 --- /dev/null +++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/Yaml.h" +#include "winget/ManifestTypes.h" +#include "winget/ManifestSchemaValidation.h" +#include "winget/ManifestValidation.h" + +#include + +namespace AppInstaller::Manifest +{ + Json::Value YamlNodeToJson(const YAML::Node& rootNode) + { + Json::Value result; + + if (rootNode.IsNull()) + { + result = Json::Value::nullSingleton(); + } + else if (rootNode.IsMap()) + { + for (auto const& keyValuePair : rootNode.Mapping()) + { + // We only support string type as key in our manifest + result[keyValuePair.first.as()] = YamlNodeToJson(keyValuePair.second); + } + } + else if (rootNode.IsSequence()) + { + for (auto const& value : rootNode.Sequence()) + { + result.append(YamlNodeToJson(value)); + } + } + else if (rootNode.IsScalar()) + { + result = YamlScalarNodeToJson(rootNode); + } + else + { + THROW_HR(E_UNEXPECTED); + } + + return result; + } + + Json::Value YamlScalarNodeToJson(const YAML::Node& scalarNode) + { + return Json::Value(scalarNode.as()); + } + + std::vector ValidateAgainstSchema(const std::vector manifestList, const ManifestVer& manifestVersion, PCWSTR resourceModuleName) + { + std::vector errors; + std::map schemaList; + valijson::Validator schemaValidator; + + for (const auto& entry : manifestList) + { + if (schemaList.find(entry.ManifestType) == schemaList.end()) + { + valijson::Schema newSchema; + valijson::SchemaParser schemaParser; + Json::Value schemaJson = LoadSchemaDoc(manifestVersion, entry.ManifestType, resourceModuleName); + valijson::adapters::JsonCppAdapter jsonSchemaAdapter(schemaJson); + schemaParser.populateSchema(jsonSchemaAdapter, newSchema); + schemaList.emplace(entry.ManifestType, std::move(newSchema)); + } + + const auto& schema = schemaList.find(entry.ManifestType)->second; + + Json::Value manifestJson = YamlNodeToJson(entry.Root); + valijson::adapters::JsonCppAdapter manifestJsonAdapter(manifestJson); + valijson::ValidationResults results; + + if (!schemaValidator.validate(schema, manifestJsonAdapter, &results)) + { + valijson::ValidationResults::Error error; + std::stringstream ss; + + ss << "Schema validation failed." << std::endl; + while (results.popError(error)) + { + std::string context; + for (auto itr = error.context.begin(); itr != error.context.end(); itr++) + { + context += *itr; + } + + ss << "Error:" << std::endl + << " context: " << context << std::endl + << " description: " << error.description << std::endl; + } + + errors.emplace_back(ValidationError::MessageWithFile(ss.str(), entry.FileName)); + } + } + } + + std::string LoadResourceAsString(PCWSTR resourceModuleName, PCWSTR resourceName, PCWSTR resourceType) + { + HMODULE resourceModule = GetModuleHandle(resourceModuleName); + THROW_LAST_ERROR_IF_NULL(resourceModule); + + HRSRC resourceInfoHandle = FindResource(resourceModule, resourceName, resourceType); + THROW_LAST_ERROR_IF_NULL(resourceInfoHandle); + + HGLOBAL resourceMemoryHandle = LoadResource(resourceModule, resourceInfoHandle); + THROW_LAST_ERROR_IF_NULL(resourceMemoryHandle); + + ULONG resourceSize = 0; + char* resourceContent = NULL; + resourceSize = SizeofResource(resourceModule, resourceInfoHandle); + THROW_LAST_ERROR_IF(resourceSize == 0); + + resourceContent = reinterpret_cast(LockResource(resourceMemoryHandle)); + THROW_HR_IF_NULL(E_UNEXPECTED, resourceContent); + + std::string resourceStr; + resourceStr.assign(resourceContent, resourceSize); + + return resourceStr; + } + + Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType, PCWSTR resourceModuleName) + { + std::string schemaStr; + + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1 }) + { + switch (manifestType) + { + case AppInstaller::Manifest::ManifestTypeEnum::Singleton: + schemaStr = LoadResourceAsString(resourceModuleName, MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_SINGLETON), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + break; + case AppInstaller::Manifest::ManifestTypeEnum::Version: + schemaStr = LoadResourceAsString(resourceModuleName, MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_VERSION), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + break; + case AppInstaller::Manifest::ManifestTypeEnum::Installer: + schemaStr = LoadResourceAsString(resourceModuleName, MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_INSTALLER), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + break; + case AppInstaller::Manifest::ManifestTypeEnum::DefaultLocale: + schemaStr = LoadResourceAsString(resourceModuleName, MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + break; + case AppInstaller::Manifest::ManifestTypeEnum::Locale: + schemaStr = LoadResourceAsString(resourceModuleName, MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_LOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + default: + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + else + { + schemaStr = LoadResourceAsString(resourceModuleName, MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_PREVIEW), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + } + + Json::Value schemaJson; + int schemaLength = static_cast(schemaStr.length()); + Json::CharReaderBuilder charReaderBuilder; + const std::unique_ptr jsonReader(charReaderBuilder.newCharReader()); + std::string errorMsg; + if (!jsonReader->parse(schemaStr.c_str(), schemaStr.c_str() + schemaLength, &schemaJson, &errorMsg)) { + THROW_HR_MSG(E_UNEXPECTED, "Jsoncpp parser failed to parse the schema doc. Reason: %s", errorMsg.c_str()); + } + + return schemaJson; + } +} \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp new file mode 100644 index 0000000000..35ac0329b6 --- /dev/null +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/Manifest.h" +#include "winget/ManifestValidation.h" +#include "winget/Yaml.h" + +namespace AppInstaller::Manifest +{ + using ValidationErrors = std::vector; + + struct FieldProcessInfo + { + FieldProcessInfo(std::string name, std::function func) : + Name(std::move(name)), ProcessFunc(func) {} + + std::string Name; + std::function ProcessFunc; + }; + + std::vector GetRootFieldProcessInfo(const ManifestVer& manifestVersion, Manifest** ppManifest) + { + std::vector result = + { + { "ManifestVersion", [](const YAML::Node&)->ValidationErrors { /* ManifestVersion already processed. Field listed here for duplicate and PascalCase check */ return {}; } }, + { "Installers", [=](const YAML::Node& value)->ValidationErrors { *m_p_installersNode = value; }, true }, + { "Localization", [=](const YAML::Node& value)->ValidationErrors { *m_p_localizationsNode = value; } }, + }; + + if (manifestVersion.Major() == 0) + { + return + { + { "Id", [=](const YAML::Node& value)->ValidationErrors { auto s = value.as(); (*ppManifest)->Id = Utility::Trim(s); return {}; } }, + { "Version", [=](const YAML::Node& value)->ValidationErrors { auto s = value.as(); (*ppManifest)->Version = Utility::Trim(s); return {}; } }, + { "Name", [=](const YAML::Node& value)->ValidationErrors { auto s = value.as(); (*ppManifest)->DefaultLocalization.Add(Utility::Trim(s)); return {}; } }, + { "Publisher", [=](const YAML::Node& value)->ValidationErrors { (*ppManifest)->DefaultLocalization.Add(value.as()); return {}; } }, + { "Author", [=](const YAML::Node& value)->ValidationErrors { (*ppManifest)->DefaultLocalization.Add(value.as()); return {}; } }, + { "License", [=](const YAML::Node& value)->ValidationErrors { (*ppManifest)->DefaultLocalization.Add(value.as()); return {}; } }, + { "LicenseUrl", [=](const YAML::Node& value)->ValidationErrors { (*ppManifest)->DefaultLocalization.Add(value.as()); return {}; } }, + { "AppMoniker", [=](const YAML::Node& value)->ValidationErrors { auto s = value.as(); (*ppManifest)->DefaultLocalization.Add(Utility::Trim(s)); return {}; } }, + { "Channel", [=](const YAML::Node& value)->ValidationErrors { auto s = value.as(); (*ppManifest)->DefaultInstallerInfo.Channel = Utility::Trim(s); return {}; } }, + { "MinOSVersion", [=](const YAML::Node& value)->ValidationErrors { (*ppManifest)->DefaultInstallerInfo.MinOSVersion = value.as(); } }, + { "Tags", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->Tags = SplitMultiValueField(value.as()); } }, + { "Commands", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->Commands = SplitMultiValueField(value.as()); } }, + { "Protocols", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->Protocols = SplitMultiValueField(value.as()); } }, + { "FileExtensions", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->FileExtensions = SplitMultiValueField(value.as()); } }, + { "UpdateBehavior", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->UpdateBehavior = ManifestInstaller::ConvertToUpdateBehaviorEnum(value.as()); } }, + + { "Homepage", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->Homepage = value.as(); } }, + + { "InstallerType", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->InstallerType = ManifestInstaller::ConvertToInstallerTypeEnum(value.as()); } }, + { "PackageFamilyName", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->PackageFamilyName = value.as(); }, false, "[-.A-Za-z0-9]+_[A-Za-z0-9]{13}" }, + { "ProductCode", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->ProductCode = value.as(); } }, + { "Description", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->Description = value.as(); } }, + { "Switches", [=](const YAML::Node& value)->ValidationErrors { *m_p_switchesNode = value; } }, + + }; + } + else + { + // Starting v1, we should be only adding new fields for each minor version increase + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1 }) + { + + } + + } + } + + std::vector GetInstallerFieldProcessInfo(const ManifestVer& manifestVersion) + { + return {}; + } + + std::vector GetLocalizationFieldProcessInfo(const ManifestVer& manifestVersion) + { + return {}; + } + + std::vector GetDependenciesFieldProcessInfo(const ManifestVer& manifestVersion) + { + return {}; + } + + std::vector GetPackageDependenciesFieldProcessInfo(const ManifestVer& manifestVersion) + { + return {}; + } + + std::vector PopulateManifest(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion) + { + + + } +} \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp index 47925ea534..58bb3dff84 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp @@ -4,20 +4,12 @@ #include "AppInstallerSHA256.h" #include "winget/Yaml.h" #include "winget/ManifestYamlParser.h" +#include "winget/ManifestSchemaValidation.h" namespace AppInstaller::Manifest { namespace { - // The maximum supported major version known about by this code. - constexpr uint64_t s_MaxSupportedMajorVersion = 0; - - // The default manifest version assigned to manifests without a ManifestVersion field. - constexpr std::string_view s_DefaultManifestVersion = "0.1.0"sv; - - // The manifest extension for the MS Store - constexpr std::string_view s_MSStoreExtension = "msstore"sv; - std::vector SplitMultiValueField(const std::string& input) { if (input.empty()) @@ -125,16 +117,67 @@ namespace AppInstaller::Manifest } } - Manifest YamlParser::CreateFromPath(const std::filesystem::path& inputFile, bool fullValidation, bool throwOnWarning) + Manifest YamlParser::CreateFromPath(const std::filesystem::path& inputPath, bool fullValidation, bool throwOnWarning) + { + std::vector docList; + + try + { + if (std::filesystem::is_directory(inputPath)) + { + for (const auto& file : std::filesystem::directory_iterator(inputPath)) + { + THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_DIRECTORY_NOT_SUPPORTED), std::filesystem::is_directory(file.path()), "Subdirectory not supported in manifest path"); + + YamlManifestInfo doc; + doc.Root = YAML::Load(file.path()); + doc.FileName = file.path().filename().u8string(); + docList.emplace_back(std::move(doc)); + } + } + else + { + YamlManifestInfo doc; + doc.Root = YAML::Load(inputPath); + doc.FileName = inputPath.filename().u8string(); + docList.emplace_back(std::move(doc)); + } + } + catch (const std::exception& e) + { + THROW_EXCEPTION_MSG(ManifestException(), e.what()); + } + + return CreateInternal(docList, fullValidation, throwOnWarning); + } + + Manifest YamlParser::Create(const std::string& input, bool fullValidation, bool throwOnWarning) + { + std::vector docList; + + try + { + YamlManifestInfo doc; + doc.Root = YAML::Load(input); + docList.emplace_back(std::move(doc)); + } + catch (const std::exception& e) + { + THROW_EXCEPTION_MSG(ManifestException(), e.what()); + } + + return CreateInternal(docList, fullValidation, throwOnWarning); + } + + Manifest YamlParser::CreateInternal(const std::vector& input, bool fullValidation, bool throwOnWarning) { Manifest manifest; std::vector errors; try { - YAML::Node rootNode = YAML::Load(inputFile); YamlParser parser; - errors = parser.ParseManifest(rootNode, manifest, fullValidation); + errors = parser.ParseManifest(input, manifest, fullValidation); } catch (const ManifestException&) { @@ -159,67 +202,306 @@ namespace AppInstaller::Manifest return manifest; } - Manifest YamlParser::Create(const std::string& input, bool fullValidation, bool throwOnWarning) + ManifestVer YamlParser::ValidateInput(std::vector& input, bool fullValidation, bool isPartialManifest) { - Manifest manifest; std::vector errors; - try + std::string manifestVersionStr; + ManifestVer manifestVersion; + ManifestVer ManifestVersionV1{ s_ManifestVersionV1 }; + bool isMultifileManifest = input.size() > 1; + + // Use the first manifest doc to determine ManifestVersion + auto& firstYamlManifest = input[0]; + if (!firstYamlManifest.Root.IsMap()) { - YAML::Node rootNode = YAML::Load(input); - YamlParser parser; - errors = parser.ParseManifest(rootNode, manifest, fullValidation); + THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "The manifest does not contain a valid root. File: %S", firstYamlManifest.FileName.c_str()); } - catch (const ManifestException&) + + if (firstYamlManifest.Root["ManifestVersion"sv]) { - // Prevent ManifestException from being wrapped in another ManifestException - throw; + manifestVersionStr = firstYamlManifest.Root["ManifestVersion"sv].as(); } - catch (const std::exception& e) + else { - THROW_EXCEPTION_MSG(ManifestException(), e.what()); + manifestVersionStr = s_DefaultManifestVersion; + } + manifestVersion = ManifestVer{ manifestVersionStr }; + + // Check max supported version + if (manifestVersion.Major() > s_MaxSupportedMajorVersion) + { + THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION), "Unsupported ManifestVersion: %S", manifestVersion.ToString().c_str()); + } + + // multi file manifest is only supported starting ManifestVersion 1.0.0 + if (isMultifileManifest && manifestVersion < ManifestVersionV1) + { + THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "Preview manifest does not support multi file manifest format."); + } + + if (isMultifileManifest) + { + // Populates the PackageIdentifier and PackageVersion from first doc for later consistency check + std::string packageId = firstYamlManifest.Root["PackageIdentifier"].as(); + std::string packageVersion = firstYamlManifest.Root["PackageVersion"].as(); + + bool isVersionManifestFound = false; + bool isInstallerManifestFound = false; + bool isDefaultLocaleManifestFound = false; + std::string defaultLocaleFromVersionManifest; + std::string defaultLocaleFromDefaultLocaleManifest; + + for (auto& entry : input) + { + if (!entry.Root.IsMap()) + { + THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "The manifest does not contain a valid root. File: %S", entry.FileName.c_str()); + } + + std::string localPackageId = entry.Root["PackageIdentifier"].as(); + if (localPackageId != packageId) + { + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::InconsistentMultiFileManifestFieldValue, "PackageIdentifier", localPackageId, entry.FileName)); + } + + std::string localPackageVersion = entry.Root["PackageVersion"].as(); + if (localPackageVersion != packageVersion) + { + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::InconsistentMultiFileManifestFieldValue, "PackageVersion", localPackageVersion, entry.FileName)); + } + + std::string localManifestVersion = entry.Root["ManifestVersion"].as(); + if (localManifestVersion != manifestVersionStr) + { + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::InconsistentMultiFileManifestFieldValue, "ManifestVersion", localManifestVersion, entry.FileName)); + } + + std::string manifestTypeStr = entry.Root["ManifestType"sv].as(); + ManifestTypeEnum manifestType = ConvertToManifestTypeEnum(manifestTypeStr); + entry.ManifestType = manifestType; + + switch (manifestType) + { + case ManifestTypeEnum::Version: + if (isVersionManifestFound) + { + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + } + else + { + isVersionManifestFound = true; + defaultLocaleFromVersionManifest = entry.Root["DefaultLocale"sv].as(); + } + break; + case ManifestTypeEnum::Installer: + if (isInstallerManifestFound) + { + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + } + else + { + isInstallerManifestFound = true; + } + break; + case ManifestTypeEnum::DefaultLocale: + if (isDefaultLocaleManifestFound) + { + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + } + else + { + isDefaultLocaleManifestFound = true; + defaultLocaleFromDefaultLocaleManifest = entry.Root["PackageLocale"sv].as(); + } + break; + case ManifestTypeEnum::Locale: + // Nothing to validate + break; + default: + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::UnsupportedMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + } + } + + if (isVersionManifestFound && isDefaultLocaleManifestFound && defaultLocaleFromDefaultLocaleManifest != defaultLocaleFromVersionManifest) + { + errors.emplace_back(ManifestError::InconsistentMultiFileManifestDefaultLocale); + } + + if (!isPartialManifest && !(isVersionManifestFound && isInstallerManifestFound && isDefaultLocaleManifestFound)) + { + errors.emplace_back(ManifestError::IncompleteMultiFileManifest); + } + } + else + { + if (manifestVersion >= ManifestVersionV1) + { + std::string manifestTypeStr = firstYamlManifest.Root["ManifestType"sv].as(); + ManifestTypeEnum manifestType = ConvertToManifestTypeEnum(manifestTypeStr); + firstYamlManifest.ManifestType = manifestType; + + if (fullValidation && manifestType == ManifestTypeEnum::Merged) + { + errors.emplace_back(ValidationError::MessageFieldValueWithFile(ManifestError::FieldValueNotSupported, "ManifestType", manifestTypeStr, firstYamlManifest.FileName)); + } + + if (!isPartialManifest && manifestType != ManifestTypeEnum::Merged && manifestType != ManifestTypeEnum::Singleton) + { + errors.emplace_back(ValidationError::MessageWithFile(ManifestError::IncompleteMultiFileManifest, firstYamlManifest.FileName)); + } + } + else + { + firstYamlManifest.ManifestType = ManifestTypeEnum::Preview; + } } if (!errors.empty()) { ManifestException ex{ std::move(errors) }; + THROW_EXCEPTION(ex); + } - if (throwOnWarning || !ex.IsWarningOnly()) + return manifestVersion; + } + + const YAML::Node& FindUniqueRequiredDocFromMultiFileManifest(const std::vector& input, ManifestTypeEnum manifestType) + { + // We'll do case insensitive search first and validate correct case later. + auto iter = std::find_if(input.begin(), input.end(), + [](auto const& s) { - THROW_EXCEPTION(ex); + return s.ManifestType == manifestType; + }); + + THROW_HR_IF(E_UNEXPECTED, iter == input.end()); + + return iter->Root; + } + + void MergeOneManifestToMultiFileManifest(const YAML::Node& input, YAML::Node& destination) + { + THROW_HR_IF(E_UNEXPECTED, !input.IsMap()); + THROW_HR_IF(E_UNEXPECTED, !destination.IsMap()); + + const std::vector FieldsToIgnore = { "PackageIdentifier", "PackageVersion", "ManifestType", "ManifestVersion" }; + + for (auto const& keyValuePair : input.Mapping()) + { + // We only support string type as key in our manifest + if (std::find(FieldsToIgnore.begin(), FieldsToIgnore.end(), keyValuePair.first.as()) == FieldsToIgnore.end()) + { + YAML::Node key = keyValuePair.first; + YAML::Node value = keyValuePair.second; + destination.AddMappingNode(std::move(key), std::move(value)); } } - - return manifest; } - std::vector YamlParser::ParseManifest(const YAML::Node& rootNode, Manifest& manifest, bool fullValidation) + YAML::Node MergeMultiFileManifest(const std::vector& input) { - // Detects empty files with a better error. - if (!rootNode.IsMap()) + // Starts with installer manifest + YAML::Node result = FindUniqueRequiredDocFromMultiFileManifest(input, ManifestTypeEnum::Installer); + + // Copy default locale manifest content into manifest root + YAML::Node defaultLocaleManifest = FindUniqueRequiredDocFromMultiFileManifest(input, ManifestTypeEnum::DefaultLocale); + MergeOneManifestToMultiFileManifest(defaultLocaleManifest, result); + + // Copy additional locale manifests + YAML::Node localizations{ YAML::Node::Type::Sequence, "", YAML::Mark() }; + for (const auto& entry : input) { - THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "The manifest does not contain a valid root."); + if (entry.ManifestType == ManifestTypeEnum::Locale) + { + YAML::Node localization{ YAML::Node::Type::Mapping, "", YAML::Mark() }; + MergeOneManifestToMultiFileManifest(entry.Root, localization); + localizations.AddSequenceNode(std::move(localization)); + } } - // Detect manifest version first to determine expected fields - // Use index to access ManifestVersion directly. If there're duplicates or other general errors, it'll be detected in later - // processing of iterating the whole manifest. - if (rootNode["ManifestVersion"sv]) + if (localizations.size() > 0) { - auto manifestVersionValue = rootNode["ManifestVersion"sv].as(); - manifest.ManifestVersion = ManifestVer(manifestVersionValue); + YAML::Node key{ YAML::Node::Type::Scalar, "", YAML::Mark() }; + key.SetScalar("Localization"); + result.AddMappingNode(std::move(key), std::move(localizations)); + } + + result["ManifestType"sv].SetScalar("merged"); + + return result; + } + + void EmitYamlNode(const YAML::Node& input, YAML::Emitter& emitter) + { + if (input.IsMap()) + { + emitter << YAML::BeginMap; + for (auto const& keyValuePair : input.Mapping()) + { + emitter << YAML::Key; + EmitYamlNode(keyValuePair.first, emitter); + emitter << YAML::Value; + EmitYamlNode(keyValuePair.second, emitter); + } + emitter << YAML::EndMap; + } + else if (input.IsSequence()) + { + emitter << YAML::BeginSeq; + for (auto const& value : input.Sequence()) + { + EmitYamlNode(value, emitter); + } + emitter << YAML::EndSeq; + } + else if (input.IsScalar()) + { + emitter << input.as(); + } + else if (input.IsNull()) + { + emitter << ""; } else { - manifest.ManifestVersion = ManifestVer(s_DefaultManifestVersion); + THROW_HR(E_UNEXPECTED); } + } - // Check manifest version is supported - if (manifest.ManifestVersion.Major() > s_MaxSupportedMajorVersion) + void OutputYamlDoc(const YAML::Node& input, const std::filesystem::path& out) + { + THROW_HR_IF(E_UNEXPECTED, !input.IsMap()); + + YAML::Emitter emitter; + EmitYamlNode(input, emitter); + + std::filesystem::create_directories(out.parent_path()); + std::ofstream outFileStream(out); + emitter.Get(outFileStream); + outFileStream.close(); + } + + std::vector YamlParser::ParseManifest(const std::vector& input, Manifest& manifest, bool fullValidation, bool isPartialManifest) + { + manifest.ManifestVersion = ValidateInput(input, fullValidation, isPartialManifest); + + auto resultErrors = ValidateAgainstSchema(input, manifest.ManifestVersion, NULL); + + if (isPartialManifest) { - THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION), "Unsupported ManifestVersion: %S", manifest.ManifestVersion.ToString().c_str()); + return resultErrors; } + const YAML::Node& manifestDoc = (input.size() > 1) ? MergeMultiFileManifest(input) : input[0].Root; + PrepareManifestFieldInfos(manifest.ManifestVersion); // Populate root fields @@ -315,6 +597,11 @@ namespace AppInstaller::Manifest std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); } + if (true) + { + OutputYamlDoc(manifestDoc, "merged.yaml"); + } + return resultErrors; } @@ -331,7 +618,7 @@ namespace AppInstaller::Manifest return errors; } - // Keeps track of already processed fields. Used to check duplicate fields or missing required fields. + // Keeps track of already processed fields. Used to check duplicate fields. std::set processedFields; for (auto const& keyValuePair : rootNode.Mapping()) @@ -362,29 +649,6 @@ namespace AppInstaller::Manifest errors.emplace_back(ManifestError::FieldDuplicate, fieldInfo.Name, "", keyValuePair.first.Mark().line, keyValuePair.first.Mark().column); } - // Validate non empty value is provided for required fields - if (fieldInfo.Required) - { - if (!valueNode.IsDefined() || valueNode.IsNull() || // Should be defined and not null - (valueNode.IsScalar() && valueNode.as().empty()) || // Scalar type should have content - ((valueNode.IsMap() || valueNode.IsSequence()) && valueNode.size() == 0)) // Map or sequence type should have size greater than 0 - { - errors.emplace_back(ManifestError::RequiredFieldEmpty, fieldInfo.Name, "", valueNode.Mark().line, valueNode.Mark().column); - } - } - - // Validate value against regex if applicable - if (fullValidation && !fieldInfo.RegEx.empty()) - { - std::string value = valueNode.as(); - std::regex pattern{ fieldInfo.RegEx }; - if (!std::regex_match(value, pattern)) - { - errors.emplace_back(ManifestError::InvalidFieldValue, fieldInfo.Name, value, valueNode.Mark().line, valueNode.Mark().column); - continue; - } - } - if (!valueNode.IsNull()) { fieldInfo.ProcessFunc(valueNode); @@ -400,15 +664,6 @@ namespace AppInstaller::Manifest } } - // Make sure required fields are provided - for (auto const& fieldInfo : fieldInfos) - { - if (fieldInfo.Required && processedFields.find(fieldInfo.Name) == processedFields.end()) - { - errors.emplace_back(ManifestError::RequiredFieldMissing, fieldInfo.Name); - } - } - return errors; } diff --git a/src/AppInstallerCommonCore/Public/winget/Manifest.h b/src/AppInstallerCommonCore/Public/winget/Manifest.h index c9a1486750..8733216a73 100644 --- a/src/AppInstallerCommonCore/Public/winget/Manifest.h +++ b/src/AppInstallerCommonCore/Public/winget/Manifest.h @@ -2,7 +2,6 @@ // Licensed under the MIT License. #pragma once #include -#include #include #include @@ -10,28 +9,6 @@ namespace AppInstaller::Manifest { - // ManifestVer is inherited from Utility::Version and is a more restricted version. - // ManifestVer is used to specify the version of app manifest itself. - // ManifestVer is a 3 part version in the format of [0-65535].[0-65535].[0-65535] - // and optionally a following tag in the format of -[SomeString] for experimental purpose. - struct ManifestVer : public Utility::Version - { - ManifestVer() = default; - - ManifestVer(std::string_view version); - - uint64_t Major() const { return m_parts.size() > 0 ? m_parts[0].Integer : 0; } - uint64_t Minor() const { return m_parts.size() > 1 ? m_parts[1].Integer : 0; } - uint64_t Patch() const { return m_parts.size() > 2 ? m_parts[2].Integer : 0; } - - bool HasExtension() const; - - bool HasExtension(std::string_view extension) const; - - private: - std::vector m_extensions; - }; - // Representation of the parsed manifest file. struct Manifest { @@ -40,60 +17,19 @@ namespace AppInstaller::Manifest // Required string_t Id; - // Required - string_t Name; - // Required string_t Version; - // Required - string_t Publisher; - - string_t AppMoniker; - - string_t Channel; - - string_t Author; - - string_t License; - - string_t MinOSVersion; - - // Comma separated values - std::vector Tags; - - // Comma separated values - std::vector Commands; - - // Comma separated values - std::vector Protocols; - - // Comma separated values - std::vector FileExtensions; - - ManifestInstaller::InstallerTypeEnum InstallerType = ManifestInstaller::InstallerTypeEnum::Unknown; - - // Default is Install if not specified - ManifestInstaller::UpdateBehaviorEnum UpdateBehavior = ManifestInstaller::UpdateBehaviorEnum::Install; - - // Package family name for MSIX packaged installers. - string_t PackageFamilyName; - - // Product code for ARP (Add/Remove Programs) installers. - string_t ProductCode; - - string_t Description; - - string_t Homepage; - - string_t LicenseUrl; - ManifestVer ManifestVersion; - std::map Switches; + ManifestInstaller DefaultInstallerInfo; std::vector Installers; - std::vector Localization; + ManifestLocalization DefaultLocalization; + + std::vector Localizations; + + ManifestLocalization CurrentLocalization; }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h index eff350dd34..7827a35d87 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -3,6 +3,7 @@ #pragma once #include #include +#include #include #include @@ -19,47 +20,9 @@ namespace AppInstaller::Manifest { using string_t = Utility::NormalizedString; - enum class InstallerTypeEnum - { - Unknown, - Inno, - Wix, - Msi, - Nullsoft, - Zip, - Msix, - Exe, - Burn, - MSStore, - }; - - enum class UpdateBehaviorEnum - { - Unknown, - Install, - UninstallPrevious, - }; - - enum class InstallerSwitchType - { - Custom, - Silent, - SilentWithProgress, - Interactive, - Language, - Log, - InstallLocation, - Update - }; - - enum class ScopeEnum - { - Unknown, - User, - Machine, - }; - - // Required. Values: x86, x64, arm, arm64, all. + + + // Required AppInstaller::Utility::Architecture Arch; // Required @@ -72,47 +35,47 @@ namespace AppInstaller::Manifest // validate appx/msix signature and perform streaming install. std::vector SignatureSha256; - // Empty means default - string_t Language; - - // Name TBD - ScopeEnum Scope; - // Store Product Id string_t ProductId; - // Package family name for MSIX packaged installers. - string_t PackageFamilyName; + string_t Channel; - // Product code for ARP (Add/Remove Programs) installers. - string_t ProductCode; + string_t Locale; + + std::vector Platform; + + string_t MinOSVersion; // If present, has more precedence than root - InstallerTypeEnum InstallerType; + InstallerTypeEnum InstallerType = InstallerTypeEnum::Unknown; + + ScopeEnum Scope; - // Default is Install if not specified - UpdateBehaviorEnum UpdateBehavior; + std::vector InstallModes; // If present, has more precedence than root std::map Switches; - static InstallerTypeEnum ConvertToInstallerTypeEnum(const std::string& in); + std::vector InstallerSuccessCodes; - static UpdateBehaviorEnum ConvertToUpdateBehaviorEnum(const std::string& in); + UpdateBehaviorEnum UpdateBehavior = UpdateBehaviorEnum::Install; - static ScopeEnum ConvertToScopeEnum(const std::string& in); + std::vector Commands; - static std::string_view InstallerTypeToString(InstallerTypeEnum installerType); + std::vector Protocols; - static std::string_view ScopeToString(ScopeEnum scope); + std::vector FileExtensions; + + // Package family name for MSIX packaged installers. + string_t PackageFamilyName; + + // Product code for ARP (Add/Remove Programs) installers. + string_t ProductCode; - // Gets a value indicating whether the given installer type uses the PackageFamilyName system reference. - static bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType); + std::vector Capabilities; - // Gets a value indicating whether the given installer type uses the ProductCode system reference. - static bool DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType); + std::vector RestrictedCapabilities; - // Checks whether 2 installer types are compatible. E.g. inno and exe are update compatible - static bool IsInstallerTypeCompatible(InstallerTypeEnum type1, InstallerTypeEnum type2); + std::vector Dependencies; }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h index e430d68058..b7128d3079 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h @@ -3,20 +3,171 @@ #pragma once #include +#include + namespace AppInstaller::Manifest { - class ManifestLocalization + using string_t = Utility::NormalizedString; + + enum class Localization : size_t + { + Publisher, + PublisherUrl, + PublisherSupportUrl, + PrivacyUrl, + Author, + PackageName, + PackageUrl, + License, + LicenseUrl, + Copyright, + CopyrightUrl, + ShortDescription, + Description, + Moniker, + Tags, + Max + }; + + namespace details + { + template + struct LocalizationMapping + { + // value_t type specifies the type of this data + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = std::vector; + }; + + // Used to deduce the DataVariant type; making a variant that includes std::monostate and all DataMapping types. + template + inline auto Deduce(std::index_sequence) { return std::variant(I)>::value_t...>{}; } + + // Holds data of any type listed in a DataMapping. + using LocalizationVariant = decltype(Deduce(std::make_index_sequence(Localization::Max)>())); + + // Gets the index into the variant for the given Data. + constexpr inline size_t LocalizationIndex(Localization l) { return static_cast(l) + 1; } + } + + struct ManifestLocalization { - public: - using string_t = Utility::NormalizedString; + string_t Locale; - // Required - string_t Language; + // Adds a value to the Localization data, or overwrites an existing entry. + // This must be used to create the initial entry, but Get can be used to modify. + template + void Add(typename details::LocalizationMapping::value_t&& v) + { + m_data[D].emplace(std::forward::value_t>(v)); + } + template + void Add(const typename details::LocalizationMapping::value_t& v) + { + m_data[D].emplace(v); + } - string_t Description; + // Return a value indicating whether the given data type is stored in the context. + bool Contains(Localization l) { return (m_data.find(l) != m_data.end()); } - string_t Homepage; + // Gets context data; which can be modified in place. + template + typename details::LocalizationMapping::value_t& Get() + { + auto itr = m_data.find(L); + THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), itr == m_data.end(), "Get(%d)", L); + return std::get(itr->second); + } - string_t LicenseUrl; + private: + std::map m_data; }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h new file mode 100644 index 0000000000..f8a99272f3 --- /dev/null +++ b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "ManifestTypes.h" + +namespace AppInstaller::Manifest +{ + struct YamlManifestInfo + { + YAML::Node Root; + std::string FileName; + ManifestTypeEnum ManifestType; + }; + + std::vector ValidateAgainstSchema(const std::vector manifestList, const ManifestVer& manifestVersion, PCWSTR resourceModuleName); +} \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestTypes.h b/src/AppInstallerCommonCore/Public/winget/ManifestTypes.h new file mode 100644 index 0000000000..7b4f739608 --- /dev/null +++ b/src/AppInstallerCommonCore/Public/winget/ManifestTypes.h @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace AppInstaller::Manifest +{ + using string_t = Utility::NormalizedString; + + // The maximum supported major version known about by this code. + constexpr uint64_t s_MaxSupportedMajorVersion = 1; + + // The default manifest version assigned to manifests without a ManifestVersion field. + constexpr std::string_view s_DefaultManifestVersion = "0.1.0"sv; + + // V1 manifest version for GA + constexpr std::string_view s_ManifestVersionV1 = "1.0.0"sv; + + // The manifest extension for the MS Store + constexpr std::string_view s_MSStoreExtension = "msstore"sv; + + // ManifestVer is inherited from Utility::Version and is a more restricted version. + // ManifestVer is used to specify the version of app manifest itself. + // ManifestVer is a 3 part version in the format of [0-65535].[0-65535].[0-65535] + // and optionally a following tag in the format of -[SomeString] for experimental purpose. + struct ManifestVer : public Utility::Version + { + ManifestVer() = default; + + ManifestVer(std::string_view version); + + uint64_t Major() const { return m_parts.size() > 0 ? m_parts[0].Integer : 0; } + uint64_t Minor() const { return m_parts.size() > 1 ? m_parts[1].Integer : 0; } + uint64_t Patch() const { return m_parts.size() > 2 ? m_parts[2].Integer : 0; } + + bool HasExtension() const; + + bool HasExtension(std::string_view extension) const; + + private: + std::vector m_extensions; + }; + + enum class InstallerTypeEnum + { + Unknown, + Inno, + Wix, + Msi, + Nullsoft, + Zip, + Msix, + Exe, + Burn, + MSStore, + }; + + enum class UpdateBehaviorEnum + { + Unknown, + Install, + UninstallPrevious, + }; + + enum class InstallerSwitchType + { + Custom, + Silent, + SilentWithProgress, + Interactive, + Language, + Log, + InstallLocation, + Update + }; + + enum class ScopeEnum + { + Unknown, + User, + Machine, + }; + + enum class PlatformEnum + { + Unknown, + Universal, + Desktop, + }; + + enum class ManifestTypeEnum + { + Singleton, + Version, + Installer, + DefaultLocale, + Locale, + Merged, + Preview + }; + + struct PackageDependency + { + string_t Id; + ManifestVer MinVersion; + }; + + struct Dependency + { + std::vector WindowsFeatures; + std::vector WindowsLibraries; + std::vector PackageDependencies; + std::vector ExternalDependencies; + }; + + + InstallerTypeEnum ConvertToInstallerTypeEnum(const std::string& in); + + UpdateBehaviorEnum ConvertToUpdateBehaviorEnum(const std::string& in); + + ScopeEnum ConvertToScopeEnum(const std::string& in); + + ManifestTypeEnum ConvertToManifestTypeEnum(const std::string& in); + + std::string_view InstallerTypeToString(InstallerTypeEnum installerType); + + std::string_view ScopeToString(ScopeEnum scope); + + // Gets a value indicating whether the given installer type uses the PackageFamilyName system reference. + bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType); + + // Gets a value indicating whether the given installer type uses the ProductCode system reference. + bool DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType); + + // Checks whether 2 installer types are compatible. E.g. inno and exe are update compatible + bool IsInstallerTypeCompatible(InstallerTypeEnum type1, InstallerTypeEnum type2); +} \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h index 9022718288..e321ebfad2 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h @@ -31,6 +31,11 @@ namespace AppInstaller::Manifest const char* const DuplicateInstallerEntry = "Duplicate installer entry found."; const char* const InstallerTypeDoesNotSupportPackageFamilyName = "The specified installer type does not support PackageFamilyName."; const char* const InstallerTypeDoesNotSupportProductCode = "The specified installer type does not support ProductCode."; + const char* const IncompleteMultiFileManifest = "The multi file manifest is incomplete."; + const char* const InconsistentMultiFileManifestFieldValue = "The multi file manifest has inconsistent field values."; + const char* const DuplicateMultiFileManifestType = "The multi file manifest should contain only one file with the particular ManifestType."; + const char* const UnsupportedMultiFileManifestType = "The multi file manifest should not contain file with the particular ManifestType."; + const char* const InconsistentMultiFileManifestDefaultLocale = "DefaultLocale value in version manifest does not match PackageLocale value in defaultLocale manifest."; } struct ValidationError @@ -48,6 +53,7 @@ namespace AppInstaller::Manifest size_t Line = 0; size_t Column = 0; Level ErrorLevel = Level::Error; + std::string FileName; ValidationError(std::string message) : Message(std::move(message)) {} @@ -69,6 +75,20 @@ namespace AppInstaller::Manifest ValidationError(std::string message, std::string field, std::string value, size_t line, size_t column, Level level) : Message(std::move(message)), Field(std::move(field)), Value(std::move(value)), Line(line), Column(column), ErrorLevel(level) {} + + static ValidationError MessageWithFile(std::string message, std::string file) + { + ValidationError error{ message }; + error.FileName = file; + return error; + } + + static ValidationError MessageFieldValueWithFile(std::string message, std::string field, std::string value, std::string file) + { + ValidationError error{ message, field, value }; + error.FileName = file; + return error; + } }; struct ManifestException : public wil::ResultException diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h index 60a7861673..08379bd0ab 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once #include +#include #include #include @@ -13,30 +14,32 @@ namespace AppInstaller::Manifest // fullValidation: Bool to set if manifest creation should perform extra validation that client does not need. // e.g. Channel should be null. Client code does not need this check to work properly. // throwOnWarning: Bool to indicate if an exception should be thrown with only warnings detected in the manifest. - static Manifest CreateFromPath(const std::filesystem::path& inputFile, bool fullValidation = false, bool throwOnWarning = false); + static Manifest CreateFromPath(const std::filesystem::path& inputPath, bool fullValidation = false, bool throwOnWarning = false); static Manifest Create(const std::string& input, bool fullValidation = false, bool throwOnWarning = false); private: + + + + static Manifest CreateInternal(const std::vector& input, bool fullValidation, bool throwOnWarning); // These pointers are referenced in the processing functions in manifest field info table. YAML::Node* m_p_installersNode = nullptr; YAML::Node* m_p_switchesNode = nullptr; YAML::Node* m_p_localizationsNode = nullptr; AppInstaller::Manifest::Manifest* m_p_manifest = nullptr; AppInstaller::Manifest::ManifestInstaller* m_p_installer = nullptr; - std::map* m_p_switches = nullptr; + std::map* m_p_switches = nullptr; AppInstaller::Manifest::ManifestLocalization* m_p_localization = nullptr; - // This struct contains individual app manifest field info + // This struct contains individual manifest field population info struct ManifestFieldInfo { - ManifestFieldInfo(std::string name, std::function func, bool required = false, std::string regex = {}) : - Name(std::move(name)), ProcessFunc(func), Required(required), RegEx(std::move(regex)) {} + ManifestFieldInfo(std::string name, std::function(const YAML::Node&)> func) : + Name(std::move(name)), ProcessFunc(func) {} std::string Name; - std::function ProcessFunc; - bool Required = false; - std::string RegEx = {}; + std::function(const YAML::Node&)> ProcessFunc; }; std::vector RootFieldInfos; @@ -44,7 +47,8 @@ namespace AppInstaller::Manifest std::vector SwitchesFieldInfos; std::vector LocalizationFieldInfos; - std::vector ParseManifest(const YAML::Node& rootNode, Manifest& manifest, bool fullValidation); + std::vector ParseManifest(const std::vector& input, Manifest& manifest, bool fullValidation, bool isPartialManifest = false); + ManifestVer ValidateInput(const std::vector& input, bool fullValidation, bool isPartialManifest); static std::map GetDefaultKnownSwitches( ManifestInstaller::InstallerTypeEnum installerType); diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h new file mode 100644 index 0000000000..4adb9b94cd --- /dev/null +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +namespace AppInstaller::Manifest +{ + struct ManifestYamlPopulator + { + std::vector PopulateManifest(const YAML::Node& rootNode, Manifest& manifest); + + private: + + struct FieldProcessInfo + { + FieldProcessInfo(std::string name, std::function(const YAML::Node&)> func) : + Name(std::move(name)), ProcessFunc(func) {} + + std::string Name; + std::function(const YAML::Node&)> ProcessFunc; + }; + + // Installers node need to be processed after other fields in package root for default installer values + YAML::Node* m_p_installersNode; + + std::vector RootFieldInfos; + std::vector InstallerFieldInfos; + std::vector SwitchesFieldInfos; + std::vector DependenciesFieldInfos; + std::vector PackageDependenciesFieldInfos; + std::vector LocalizationFieldInfos; + + AppInstaller::Manifest::Manifest* m_p_manifest = nullptr; + AppInstaller::Manifest::ManifestInstaller* m_p_installer = nullptr; + std::map* m_p_switches = nullptr; + AppInstaller::Manifest::Dependency* m_p_dependency = nullptr; + AppInstaller::Manifest::PackageDependency* m_p_packageDependency = nullptr; + AppInstaller::Manifest::Localization* m_p_localization = nullptr; + + void GetRootFieldProcessInfo(const ManifestVer& manifestVersion); + void GetInstallerFieldProcessInfo(const ManifestVer& manifestVersion); + void GetSwitchesFieldProcessInfo(const ManifestVer& manifestVersion); + void GetDependenciesFieldProcessInfo(const ManifestVer& manifestVersion); + void GetPackageDependenciesFieldProcessInfo(const ManifestVer& manifestVersion); + void GetLocalizationFieldProcessInfo(const ManifestVer& manifestVersion); + }; +} \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/Yaml.h b/src/AppInstallerCommonCore/Public/winget/Yaml.h index 9a15687ca5..a983406381 100644 --- a/src/AppInstallerCommonCore/Public/winget/Yaml.h +++ b/src/AppInstallerCommonCore/Public/winget/Yaml.h @@ -195,6 +195,9 @@ namespace AppInstaller::YAML // Gets the result of the emitter; can only be retrieved once. std::string str(); + // Gets the result of the emitter to out stream; can only be retrieved once. + void Get(std::ostream& out); + private: // Appends the given node to the current container if applicable. void AppendNode(int id); diff --git a/src/AppInstallerCommonCore/Yaml.cpp b/src/AppInstallerCommonCore/Yaml.cpp index d6105b0405..f4e5b9bbcf 100644 --- a/src/AppInstallerCommonCore/Yaml.cpp +++ b/src/AppInstallerCommonCore/Yaml.cpp @@ -418,6 +418,14 @@ namespace AppInstaller::YAML return stream.str(); } + void Emitter::Get(std::ostream& out) + { + Wrapper::Emitter emitter(out); + + emitter.Dump(*m_document); + emitter.Flush(); + } + void Emitter::AppendNode(int id) { if (!m_containers.empty()) diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h index a5dad48fc7..a8eb1e9d75 100644 --- a/src/AppInstallerCommonCore/pch.h +++ b/src/AppInstallerCommonCore/pch.h @@ -16,6 +16,13 @@ #define YAML_DECLARE_STATIC #include +#include + +#include +#include +#include +#include + #include #include #include diff --git a/src/ManifestSchema/ManifestSchema.vcxitems.filters b/src/ManifestSchema/ManifestSchema.vcxitems.filters index e9b69ffd28..9f37ebc1f6 100644 --- a/src/ManifestSchema/ManifestSchema.vcxitems.filters +++ b/src/ManifestSchema/ManifestSchema.vcxitems.filters @@ -25,4 +25,11 @@ schema + + + + + + + \ No newline at end of file diff --git a/src/Valijson/Valijson.vcxitems b/src/Valijson/Valijson.vcxitems index 9012409189..2da44c84c7 100644 --- a/src/Valijson/Valijson.vcxitems +++ b/src/Valijson/Valijson.vcxitems @@ -7,7 +7,7 @@ - %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)valijson\include diff --git a/src/Valijson/Valijson.vcxitems.filters b/src/Valijson/Valijson.vcxitems.filters index e094258b06..185d926459 100644 --- a/src/Valijson/Valijson.vcxitems.filters +++ b/src/Valijson/Valijson.vcxitems.filters @@ -111,5 +111,12 @@ utils + + + + + + + \ No newline at end of file From 1d73ba0b357ca69f0b020ca3fb1c99257541fad2 Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Sun, 31 Jan 2021 02:08:14 -0800 Subject: [PATCH 04/18] second check --- src/AppInstallerCLI.sln | 1 + .../Workflows/InstallFlow.cpp | 34 +- .../Workflows/ManifestComparator.cpp | 10 +- .../ShellExecuteInstallerHandler.cpp | 38 +- .../Workflows/UninstallFlow.cpp | 32 +- .../PredefinedInstalledSource.cpp | 2 +- src/AppInstallerCLITests/YamlManifest.cpp | 4 +- .../AppInstallerCommonCore.vcxproj | 26 +- .../AppInstallerCommonCore.vcxproj.filters | 6 +- .../AppInstallerStrings.cpp | 7 + .../Manifest/ManifestCommon.cpp | 53 +- .../Manifest/ManifestSchemaValidation.cpp | 169 ++-- .../Manifest/ManifestValidation.cpp | 10 +- .../Manifest/ManifestYamlPopulator.cpp | 535 ++++++++++- .../Manifest/YamlParser.cpp | 892 +++++++----------- .../Public/AppInstallerStrings.h | 1 + .../Public/winget/Manifest.h | 4 +- .../{ManifestTypes.h => ManifestCommon.h} | 7 +- .../Public/winget/ManifestInstaller.h | 9 +- .../Public/winget/ManifestLocalization.h | 11 +- .../Public/winget/ManifestSchemaValidation.h | 26 +- .../Public/winget/ManifestYamlParser.h | 90 +- .../Public/winget/ManifestYamlPopulator.h | 38 +- .../Public/winget/Yaml.h | 3 +- src/AppInstallerCommonCore/Yaml.cpp | 7 +- .../Microsoft/ARPHelper.cpp | 16 +- .../Microsoft/ARPHelper.h | 4 +- .../PredefinedInstalledSourceFactory.cpp | 6 +- 28 files changed, 1154 insertions(+), 887 deletions(-) rename src/AppInstallerCommonCore/Public/winget/{ManifestTypes.h => ManifestCommon.h} (90%) diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index e668e9e3d1..5e657e57ee 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -76,6 +76,7 @@ Global binver\binver.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4 binver\binver.vcxitems*{6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}*SharedItemsImports = 9 ManifestSchema\ManifestSchema.vcxitems*{7d05f64d-ce5a-42aa-a2c1-e91458f061cf}*SharedItemsImports = 9 + catch2\catch2.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 binver\binver.vcxitems*{fb313532-38b0-4676-9303-ab200aa13576}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 2091529c83..e013b2eb7a 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -45,7 +45,7 @@ namespace AppInstaller::CLI::Workflow { auto installerType = context.Get().value().InstallerType; - if (installerType == ManifestInstaller::InstallerTypeEnum::MSStore) + if (installerType == InstallerTypeEnum::MSStore) { context.Reporter.Info() << Resource::String::InstallationDisclaimerMSStore << std::endl; } @@ -63,15 +63,15 @@ namespace AppInstaller::CLI::Workflow switch (installer.InstallerType) { - case ManifestInstaller::InstallerTypeEnum::Exe: - case ManifestInstaller::InstallerTypeEnum::Burn: - case ManifestInstaller::InstallerTypeEnum::Inno: - case ManifestInstaller::InstallerTypeEnum::Msi: - case ManifestInstaller::InstallerTypeEnum::Nullsoft: - case ManifestInstaller::InstallerTypeEnum::Wix: + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Wix: context << DownloadInstallerFile << VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable; break; - case ManifestInstaller::InstallerTypeEnum::Msix: + case InstallerTypeEnum::Msix: if (installer.SignatureSha256.empty()) { context << DownloadInstallerFile << VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable; @@ -82,7 +82,7 @@ namespace AppInstaller::CLI::Workflow context << GetMsixSignatureHash << VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable; } break; - case ManifestInstaller::InstallerTypeEnum::MSStore: + case InstallerTypeEnum::MSStore: // Nothing to do here break; default: @@ -273,12 +273,12 @@ namespace AppInstaller::CLI::Workflow switch (installer.InstallerType) { - case ManifestInstaller::InstallerTypeEnum::Exe: - case ManifestInstaller::InstallerTypeEnum::Burn: - case ManifestInstaller::InstallerTypeEnum::Inno: - case ManifestInstaller::InstallerTypeEnum::Msi: - case ManifestInstaller::InstallerTypeEnum::Nullsoft: - case ManifestInstaller::InstallerTypeEnum::Wix: + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Wix: if (isUpdate && installer.UpdateBehavior == ManifestInstaller::UpdateBehaviorEnum::UninstallPrevious) { context << @@ -288,10 +288,10 @@ namespace AppInstaller::CLI::Workflow } context << ShellExecuteInstall; break; - case ManifestInstaller::InstallerTypeEnum::Msix: + case InstallerTypeEnum::Msix: context << MsixInstall; break; - case ManifestInstaller::InstallerTypeEnum::MSStore: + case InstallerTypeEnum::MSStore: context << EnsureFeatureEnabled(Settings::ExperimentalFeature::Feature::ExperimentalMSStore) << EnsureStorePolicySatisfied << diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp index be4e2e5d83..e68624e2b1 100644 --- a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp +++ b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp @@ -12,14 +12,14 @@ namespace AppInstaller::CLI::Workflow namespace { // Determine if the installer is applicable. - bool IsInstallerApplicable(const Manifest::ManifestInstaller& installer, Manifest::ManifestInstaller::InstallerTypeEnum installedType) + bool IsInstallerApplicable(const Manifest::ManifestInstaller& installer, Manifest::InstallerTypeEnum installedType) { if (Utility::IsApplicableArchitecture(installer.Arch) == Utility::InapplicableArchitecture) { return false; } - if (installedType != Manifest::ManifestInstaller::InstallerTypeEnum::Unknown && + if (installedType != Manifest::InstallerTypeEnum::Unknown && !Manifest::ManifestInstaller::IsInstallerTypeCompatible(installer.InstallerType, installedType)) { return false; @@ -33,7 +33,7 @@ namespace AppInstaller::CLI::Workflow bool IsInstallerBetterMatch( const Manifest::ManifestInstaller& installer1, const Manifest::ManifestInstaller& installer2, - Manifest::ManifestInstaller::InstallerTypeEnum installedType) + Manifest::InstallerTypeEnum installedType) { auto arch1 = Utility::IsApplicableArchitecture(installer1.Arch); auto arch2 = Utility::IsApplicableArchitecture(installer2.Arch); @@ -46,7 +46,7 @@ namespace AppInstaller::CLI::Workflow } // If there's installation metadata, pick the preferred one or compatible one - if (installedType != Manifest::ManifestInstaller::InstallerTypeEnum::Unknown) + if (installedType != Manifest::InstallerTypeEnum::Unknown) { if (installer1.InstallerType == installedType && installer2.InstallerType != installedType) { @@ -96,7 +96,7 @@ namespace AppInstaller::CLI::Workflow AICLI_LOG(CLI, Info, << "Starting installer selection."); // Get the currently installed package's type (if present) - Manifest::ManifestInstaller::InstallerTypeEnum installedType = Manifest::ManifestInstaller::InstallerTypeEnum::Unknown; + Manifest::InstallerTypeEnum installedType = Manifest::InstallerTypeEnum::Unknown; auto installerTypeItr = m_installationMetadata.find(Repository::PackageVersionMetadata::InstalledType); if (installerTypeItr != m_installationMetadata.end()) { diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp index f547c915d6..633ff52c03 100644 --- a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp +++ b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp @@ -69,19 +69,19 @@ namespace AppInstaller::CLI::Workflow // Construct install experience arg. // SilentWithProgress is default, so look for it first. - auto experienceArgsItr = installerSwitches.find(ManifestInstaller::InstallerSwitchType::SilentWithProgress); + auto experienceArgsItr = installerSwitches.find(InstallerSwitchType::SilentWithProgress); if (context.Args.Contains(Execution::Args::Type::Interactive)) { // If interactive requested, always use Interactive (or nothing). If the installer supports // interactive it is usually the default, and thus it is cumbersome to put a blank entry in // the manifest. - experienceArgsItr = installerSwitches.find(ManifestInstaller::InstallerSwitchType::Interactive); + experienceArgsItr = installerSwitches.find(InstallerSwitchType::Interactive); } // If no SilentWithProgress exists, or Silent requested, try to find Silent. else if (experienceArgsItr == installerSwitches.end() || context.Args.Contains(Execution::Args::Type::Silent)) { - auto silentItr = installerSwitches.find(ManifestInstaller::InstallerSwitchType::Silent); + auto silentItr = installerSwitches.find(InstallerSwitchType::Silent); // If Silent requested, but doesn't exist, then continue using SilentWithProgress. if (silentItr != installerSwitches.end()) { @@ -95,35 +95,35 @@ namespace AppInstaller::CLI::Workflow } // Construct language arg if necessary. - if (context.Args.Contains(Execution::Args::Type::Language) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Language) != installerSwitches.end()) + if (context.Args.Contains(Execution::Args::Type::Language) && installerSwitches.find(InstallerSwitchType::Language) != installerSwitches.end()) { - installerArgs += ' ' + installerSwitches.at(ManifestInstaller::InstallerSwitchType::Language); + installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Language); } // Construct log path arg. - if (installerSwitches.find(ManifestInstaller::InstallerSwitchType::Log) != installerSwitches.end()) + if (installerSwitches.find(InstallerSwitchType::Log) != installerSwitches.end()) { - installerArgs += ' ' + installerSwitches.at(ManifestInstaller::InstallerSwitchType::Log); + installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Log); } // Construct custom arg. - if (installerSwitches.find(ManifestInstaller::InstallerSwitchType::Custom) != installerSwitches.end()) + if (installerSwitches.find(InstallerSwitchType::Custom) != installerSwitches.end()) { - installerArgs += ' ' + installerSwitches.at(ManifestInstaller::InstallerSwitchType::Custom); + installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Custom); } // Construct update arg if applicable - if (isUpdate && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Update) != installerSwitches.end()) + if (isUpdate && installerSwitches.find(InstallerSwitchType::Update) != installerSwitches.end()) { - installerArgs += ' ' + installerSwitches.at(ManifestInstaller::InstallerSwitchType::Update); + installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Update); } // Construct install location arg if necessary. if (!isUpdate && context.Args.Contains(Execution::Args::Type::InstallLocation) && - installerSwitches.find(ManifestInstaller::InstallerSwitchType::InstallLocation) != installerSwitches.end()) + installerSwitches.find(InstallerSwitchType::InstallLocation) != installerSwitches.end()) { - installerArgs += ' ' + installerSwitches.at(ManifestInstaller::InstallerSwitchType::InstallLocation); + installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::InstallLocation); } return installerArgs; @@ -249,14 +249,14 @@ namespace AppInstaller::CLI::Workflow switch(context.Get()->InstallerType) { - case ManifestInstaller::InstallerTypeEnum::Burn: - case ManifestInstaller::InstallerTypeEnum::Exe: - case ManifestInstaller::InstallerTypeEnum::Inno: - case ManifestInstaller::InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Nullsoft: renamedDownloadedInstaller += L".exe"; break; - case ManifestInstaller::InstallerTypeEnum::Msi: - case ManifestInstaller::InstallerTypeEnum::Wix: + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Wix: renamedDownloadedInstaller += L".msi"; break; } diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp index ea5ac7f70f..f998f586ef 100644 --- a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp @@ -19,10 +19,10 @@ namespace AppInstaller::CLI::Workflow const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType]; switch (ManifestInstaller::ConvertToInstallerTypeEnum(installedTypeString)) { - case ManifestInstaller::InstallerTypeEnum::Exe: - case ManifestInstaller::InstallerTypeEnum::Burn: - case ManifestInstaller::InstallerTypeEnum::Inno: - case ManifestInstaller::InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Nullsoft: { IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata(); @@ -46,8 +46,8 @@ namespace AppInstaller::CLI::Workflow context.Add(uninstallCommandItr->second); break; } - case ManifestInstaller::InstallerTypeEnum::Msi: - case ManifestInstaller::InstallerTypeEnum::Wix: + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Wix: { // Uninstall strings for MSI don't include UI level (/q) needed to avoid interactivity, // so we handle them differently. @@ -61,8 +61,8 @@ namespace AppInstaller::CLI::Workflow context.Add(std::move(productCodes)); break; } - case ManifestInstaller::InstallerTypeEnum::Msix: - case ManifestInstaller::InstallerTypeEnum::MSStore: + case InstallerTypeEnum::Msix: + case InstallerTypeEnum::MSStore: { auto packageFamilyNames = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); if (packageFamilyNames.empty()) @@ -84,18 +84,18 @@ namespace AppInstaller::CLI::Workflow const std::string installedTypeString = context.Get()->GetMetadata()[PackageVersionMetadata::InstalledType]; switch (ManifestInstaller::ConvertToInstallerTypeEnum(installedTypeString)) { - case ManifestInstaller::InstallerTypeEnum::Exe: - case ManifestInstaller::InstallerTypeEnum::Burn: - case ManifestInstaller::InstallerTypeEnum::Inno: - case ManifestInstaller::InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Nullsoft: context << Workflow::ShellExecuteUninstallImpl; break; - case ManifestInstaller::InstallerTypeEnum::Msi: - case ManifestInstaller::InstallerTypeEnum::Wix: + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Wix: context << Workflow::ShellExecuteMsiExecUninstall; break; - case ManifestInstaller::InstallerTypeEnum::Msix: - case ManifestInstaller::InstallerTypeEnum::MSStore: + case InstallerTypeEnum::Msix: + case InstallerTypeEnum::MSStore: context << Workflow::MsixUninstall; break; default: diff --git a/src/AppInstallerCLITests/PredefinedInstalledSource.cpp b/src/AppInstallerCLITests/PredefinedInstalledSource.cpp index 7eb35cfdd6..a5cf63c12e 100644 --- a/src/AppInstallerCLITests/PredefinedInstalledSource.cpp +++ b/src/AppInstallerCLITests/PredefinedInstalledSource.cpp @@ -151,7 +151,7 @@ TEST_CASE("ARPHelper_GetARPForArchitecture", "[arphelper][list]") ARPHelper helper; - auto nativeMachineKey = helper.GetARPKey(ManifestInstaller::ScopeEnum::Machine, systemArch); + auto nativeMachineKey = helper.GetARPKey(ScopeEnum::Machine, systemArch); REQUIRE(nativeMachineKey); } diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index aa340c01b3..1165811fa7 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -72,7 +72,7 @@ TEST_CASE("ReadGoodManifestAndVerifyContents", "[ManifestValidation]") REQUIRE(installer1.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); REQUIRE(installer1.Language == "en-US"); REQUIRE(installer1.InstallerType == ManifestInstaller::InstallerTypeEnum::Zip); - REQUIRE(installer1.Scope == ManifestInstaller::ScopeEnum::User); + REQUIRE(installer1.Scope == ScopeEnum::User); REQUIRE(installer1.PackageFamilyName == ""); REQUIRE(installer1.ProductCode == ""); REQUIRE(installer1.UpdateBehavior == ManifestInstaller::UpdateBehaviorEnum::Install); @@ -93,7 +93,7 @@ TEST_CASE("ReadGoodManifestAndVerifyContents", "[ManifestValidation]") REQUIRE(installer2.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF0000")); REQUIRE(installer2.Language == "en-US"); REQUIRE(installer2.InstallerType == ManifestInstaller::InstallerTypeEnum::Zip); - REQUIRE(installer2.Scope == ManifestInstaller::ScopeEnum::User); + REQUIRE(installer2.Scope == ScopeEnum::User); REQUIRE(installer2.PackageFamilyName == ""); REQUIRE(installer2.ProductCode == ""); REQUIRE(installer2.UpdateBehavior == ManifestInstaller::UpdateBehaviorEnum::UninstallPrevious); diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj index c731adf8d0..374c72d514 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -163,9 +163,9 @@ Disabled _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) true true true @@ -180,7 +180,7 @@ WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) true @@ -193,10 +193,10 @@ true true NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) true true true @@ -218,10 +218,10 @@ true true NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD;WINGET_DISABLE_FOR_FUZZING - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories) true true true @@ -271,7 +271,7 @@ - + diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters index 6ed5490ea4..320c29b6a4 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -138,9 +138,6 @@ Public\winget - - Public\winget - Public\winget @@ -150,6 +147,9 @@ Public\winget + + Public\winget + diff --git a/src/AppInstallerCommonCore/AppInstallerStrings.cpp b/src/AppInstallerCommonCore/AppInstallerStrings.cpp index 9a57111b4c..987f7d5ce2 100644 --- a/src/AppInstallerCommonCore/AppInstallerStrings.cpp +++ b/src/AppInstallerCommonCore/AppInstallerStrings.cpp @@ -396,6 +396,13 @@ namespace AppInstaller::Utility return str; } + std::string Trim(std::string&& str) + { + std::string result = std::move(str); + Utility::Trim(result); + return result; + } + std::string ReadEntireStream(std::istream& stream) { std::streampos currentPos = stream.tellg(); diff --git a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp index 13ee3ee500..8a5d5875b6 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "winget/ManifestTypes.h" +#include "winget/ManifestCommon.h" #include "winget/ManifestValidation.h" namespace AppInstaller::Manifest @@ -194,6 +194,22 @@ namespace AppInstaller::Manifest return result; } + PlatformEnum ConvertToPlatformEnum(const std::string& in) + { + PlatformEnum result = PlatformEnum::Unknown; + + if (Utility::CaseInsensitiveEquals(in, "windows.desktop")) + { + result = PlatformEnum::Desktop; + } + else if (Utility::CaseInsensitiveEquals(in, "windows.universal")) + { + result = PlatformEnum::Universal; + } + + return result; + } + ManifestTypeEnum ConvertToManifestTypeEnum(const std::string& in) { if (in == "singleton") @@ -308,4 +324,39 @@ namespace AppInstaller::Manifest return set1 == set2; } + + std::map GetDefaultKnownSwitches(InstallerTypeEnum installerType) + { + switch (installerType) + { + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Wix: + case InstallerTypeEnum::Msi: + return + { + {InstallerSwitchType::Silent, ManifestInstaller::string_t("/quiet")}, + {InstallerSwitchType::SilentWithProgress, ManifestInstaller::string_t("/passive")}, + {InstallerSwitchType::Log, ManifestInstaller::string_t("/log \"" + std::string(ARG_TOKEN_LOGPATH) + "\"")}, + {InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("TARGETDIR=\"" + std::string(ARG_TOKEN_INSTALLPATH) + "\"")}, + {InstallerSwitchType::Update, ManifestInstaller::string_t("REINSTALL=ALL REINSTALLMODE=vamus")} + }; + case InstallerTypeEnum::Nullsoft: + return + { + {InstallerSwitchType::Silent, ManifestInstaller::string_t("/S")}, + {InstallerSwitchType::SilentWithProgress, ManifestInstaller::string_t("/S")}, + {InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("/D=" + std::string(ARG_TOKEN_INSTALLPATH))} + }; + case InstallerTypeEnum::Inno: + return + { + {InstallerSwitchType::Silent, ManifestInstaller::string_t("/VERYSILENT")}, + {InstallerSwitchType::SilentWithProgress, ManifestInstaller::string_t("/SILENT")}, + {InstallerSwitchType::Log, ManifestInstaller::string_t("/LOG=\"" + std::string(ARG_TOKEN_LOGPATH) + "\"")}, + {InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("/DIR=\"" + std::string(ARG_TOKEN_INSTALLPATH) + "\"")} + }; + default: + return {}; + } + } } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp index 9a3e905f51..64f1d3e29a 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -2,99 +2,86 @@ // Licensed under the MIT License. #include "pch.h" #include "winget/Yaml.h" -#include "winget/ManifestTypes.h" +#include "winget/ManifestCommon.h" #include "winget/ManifestSchemaValidation.h" #include "winget/ManifestValidation.h" +#include "winget/ManifestYamlParser.h" #include -namespace AppInstaller::Manifest +namespace AppInstaller::Manifest::YamlParser { - Json::Value YamlNodeToJson(const YAML::Node& rootNode) + namespace { - Json::Value result; + enum class YamlScalarType + { + String, + Int + }; - if (rootNode.IsNull()) + using namespace std::string_view_literals; + const std::map ManifestFieldTypes= { - result = Json::Value::nullSingleton(); - } - else if (rootNode.IsMap()) + { "InstallerSuccessCodes"sv, YamlScalarType::Int } + }; + + YamlScalarType GetManifestScalarValueType(const std::string& key) { - for (auto const& keyValuePair : rootNode.Mapping()) + auto iter = ManifestFieldTypes.find(key); + if (iter != ManifestFieldTypes.end()) { - // We only support string type as key in our manifest - result[keyValuePair.first.as()] = YamlNodeToJson(keyValuePair.second); + return iter->second; } + + return YamlScalarType::String; } - else if (rootNode.IsSequence()) + + Json::Value YamlScalarNodeToJson(const YAML::Node& scalarNode, YamlScalarType scalarType) { - for (auto const& value : rootNode.Sequence()) + if (scalarType == YamlScalarType::Int) { - result.append(YamlNodeToJson(value)); + return Json::Value(scalarNode.as()); + } + else + { + return Json::Value(scalarNode.as()); } } - else if (rootNode.IsScalar()) - { - result = YamlScalarNodeToJson(rootNode); - } - else - { - THROW_HR(E_UNEXPECTED); - } - - return result; - } - - Json::Value YamlScalarNodeToJson(const YAML::Node& scalarNode) - { - return Json::Value(scalarNode.as()); - } - std::vector ValidateAgainstSchema(const std::vector manifestList, const ManifestVer& manifestVersion, PCWSTR resourceModuleName) - { - std::vector errors; - std::map schemaList; - valijson::Validator schemaValidator; - - for (const auto& entry : manifestList) + Json::Value ManifestYamlNodeToJson(const YAML::Node& rootNode, YamlScalarType scalarType = YamlScalarType::String) { - if (schemaList.find(entry.ManifestType) == schemaList.end()) + Json::Value result; + + if (rootNode.IsNull()) { - valijson::Schema newSchema; - valijson::SchemaParser schemaParser; - Json::Value schemaJson = LoadSchemaDoc(manifestVersion, entry.ManifestType, resourceModuleName); - valijson::adapters::JsonCppAdapter jsonSchemaAdapter(schemaJson); - schemaParser.populateSchema(jsonSchemaAdapter, newSchema); - schemaList.emplace(entry.ManifestType, std::move(newSchema)); + result = Json::Value::nullSingleton(); } - - const auto& schema = schemaList.find(entry.ManifestType)->second; - - Json::Value manifestJson = YamlNodeToJson(entry.Root); - valijson::adapters::JsonCppAdapter manifestJsonAdapter(manifestJson); - valijson::ValidationResults results; - - if (!schemaValidator.validate(schema, manifestJsonAdapter, &results)) + else if (rootNode.IsMap()) { - valijson::ValidationResults::Error error; - std::stringstream ss; - - ss << "Schema validation failed." << std::endl; - while (results.popError(error)) + for (auto const& keyValuePair : rootNode.Mapping()) { - std::string context; - for (auto itr = error.context.begin(); itr != error.context.end(); itr++) - { - context += *itr; - } - - ss << "Error:" << std::endl - << " context: " << context << std::endl - << " description: " << error.description << std::endl; + // We only support string type as key in our manifest + auto key = keyValuePair.first.as(); + result[keyValuePair.first.as()] = ManifestYamlNodeToJson(keyValuePair.second, GetManifestScalarValueType(key)); } - - errors.emplace_back(ValidationError::MessageWithFile(ss.str(), entry.FileName)); } + else if (rootNode.IsSequence()) + { + for (auto const& value : rootNode.Sequence()) + { + result.append(ManifestYamlNodeToJson(value, scalarType)); + } + } + else if (rootNode.IsScalar()) + { + result = YamlScalarNodeToJson(rootNode, scalarType); + } + else + { + THROW_HR(E_UNEXPECTED); + } + + return result; } } @@ -165,4 +152,50 @@ namespace AppInstaller::Manifest return schemaJson; } + + std::vector ValidateAgainstSchema(const std::vector& manifestList, const ManifestVer& manifestVersion, PCWSTR resourceModuleName) + { + std::vector errors; + std::map schemaList; + valijson::Validator schemaValidator; + + for (const auto& entry : manifestList) + { + if (schemaList.find(entry.ManifestType) == schemaList.end()) + { + valijson::Schema newSchema; + valijson::SchemaParser schemaParser; + Json::Value schemaJson = LoadSchemaDoc(manifestVersion, entry.ManifestType, resourceModuleName); + valijson::adapters::JsonCppAdapter jsonSchemaAdapter(schemaJson); + schemaParser.populateSchema(jsonSchemaAdapter, newSchema); + schemaList.emplace(entry.ManifestType, std::move(newSchema)); + } + + const auto& schema = schemaList.find(entry.ManifestType)->second; + + Json::Value manifestJson = ManifestYamlNodeToJson(entry.Root); + valijson::adapters::JsonCppAdapter manifestJsonAdapter(manifestJson); + valijson::ValidationResults results; + + if (!schemaValidator.validate(schema, manifestJsonAdapter, &results)) + { + valijson::ValidationResults::Error error; + std::stringstream ss; + + ss << "Schema validation failed." << std::endl; + while (results.popError(error)) + { + std::string context; + for (auto itr = error.context.begin(); itr != error.context.end(); itr++) + { + context += *itr; + } + + ss << "Error context: " << context << " Description: " << error.description << std::endl; + } + + errors.emplace_back(ValidationError::MessageWithFile(ss.str(), entry.FileName)); + } + } + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp index 9cb9e6100c..108ba56546 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp @@ -75,7 +75,7 @@ namespace AppInstaller::Manifest resultErrors.emplace_back(ManifestError::InvalidFieldValue, "Arch"); } - if (installer.InstallerType == ManifestInstaller::InstallerTypeEnum::Unknown) + if (installer.InstallerType == InstallerTypeEnum::Unknown) { resultErrors.emplace_back(ManifestError::InvalidFieldValue, "InstallerType"); } @@ -96,7 +96,7 @@ namespace AppInstaller::Manifest resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportProductCode, "InstallerType", ManifestInstaller::InstallerTypeToString(installer.InstallerType)); } - if (installer.InstallerType == ManifestInstaller::InstallerTypeEnum::MSStore) + if (installer.InstallerType == InstallerTypeEnum::MSStore) { // MSStore type is not supported in community repo resultErrors.emplace_back( @@ -126,9 +126,9 @@ namespace AppInstaller::Manifest } } - if (installer.InstallerType == ManifestInstaller::InstallerTypeEnum::Exe && - (installer.Switches.find(ManifestInstaller::InstallerSwitchType::SilentWithProgress) == installer.Switches.end() || - installer.Switches.find(ManifestInstaller::InstallerSwitchType::Silent) == installer.Switches.end())) + if (installer.InstallerType == InstallerTypeEnum::Exe && + (installer.Switches.find(InstallerSwitchType::SilentWithProgress) == installer.Switches.end() || + installer.Switches.find(InstallerSwitchType::Silent) == installer.Switches.end())) { resultErrors.emplace_back(ManifestError::ExeInstallerMissingSilentSwitches, ValidationError::Level::Warning); } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index 35ac0329b6..e6534c35ac 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -1,96 +1,531 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "winget/Manifest.h" -#include "winget/ManifestValidation.h" -#include "winget/Yaml.h" +#include "AppInstallerSHA256.h" +#include "winget/ManifestYamlPopulator.h" namespace AppInstaller::Manifest { using ValidationErrors = std::vector; - struct FieldProcessInfo + namespace { - FieldProcessInfo(std::string name, std::function func) : - Name(std::move(name)), ProcessFunc(func) {} + std::vector SplitMultiValueField(const std::string& input) + { + if (input.empty()) + { + return {}; + } + + std::vector result; + size_t currentPos = 0; + while (currentPos < input.size()) + { + size_t splitPos = input.find(',', currentPos); + if (splitPos == std::string::npos) + { + splitPos = input.size(); + } + + std::string splitVal = input.substr(currentPos, splitPos - currentPos); + Utility::Trim(splitVal); + if (!splitVal.empty()) + { + result.emplace_back(std::move(splitVal)); + } + currentPos = splitPos + 1; + } + + return result; + } + + std::vector ProcessStringSequenceNode(const YAML::Node& node, bool trim = true) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + std::vector result; + + for (auto const& entry : node.Sequence()) + { + std::string value = entry.as(); + if (trim) + { + Utility::Trim(value); + } + + result.emplace_back(std::move(value)); + } + + return result; + } + + std::vector ProcessIntSequenceNode(const YAML::Node& node) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + std::vector result; + + for (auto const& entry : node.Sequence()) + { + result.emplace_back(entry.as()); + } + + return result; + } + + std::vector ProcessPlatformSequenceNode(const YAML::Node& node) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + std::vector result; + + for (auto const& entry : node.Sequence()) + { + result.emplace_back(ConvertToPlatformEnum(entry.as())); + } + + return result; + } + + std::vector ProcessScopeSequenceNode(const YAML::Node& node) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + std::vector result; + + for (auto const& entry : node.Sequence()) + { + result.emplace_back(ConvertToScopeEnum(entry.as())); + } + + return result; + } + } + + std::vector ManifestYamlPopulator::GetRootFieldProcessInfo(const ManifestVer& manifestVersion) + { + // Common fields across versions + std::vector result = + { + { "ManifestVersion", [](const YAML::Node&)->ValidationErrors { /* ManifestVersion already populated. Field listed here for duplicate and PascalCase check */ return {}; } }, + { "Installers", [this](const YAML::Node& value)->ValidationErrors { *m_p_installersNode = value; return {}; } }, + { "Localization", [this](const YAML::Node& value)->ValidationErrors { return ProcessLocalizationNode(value, m_p_manifest->Localizations); } }, + }; + + // Additional version specific fields + if (manifestVersion.Major() == 0) + { + std::vector previewRootFields + { + { "Id", [this](const YAML::Node& value)->ValidationErrors { m_p_manifest->Id = Utility::Trim(value.as()); return {}; } }, + { "Version", [this](const YAML::Node& value)->ValidationErrors { m_p_manifest->Version = Utility::Trim(value.as()); return {}; } }, + { "AppMoniker", [this](const YAML::Node& value)->ValidationErrors { m_p_manifest->Moniker = Utility::Trim(value.as()); return {}; } }, + }; + + std::move(previewRootFields.begin(), previewRootFields.end(), std::inserter(result, result.end())); + } + else if (manifestVersion.Major() == 1) + { + // Starting v1, we should be only adding new fields for each minor version increase + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1 }) + { + std::vector v1RootFields + { + { "PackageIdentifier", [this](const YAML::Node& value)->ValidationErrors { m_p_manifest->Id = Utility::Trim(value.as()); return {}; } }, + { "PackageVersion", [this](const YAML::Node& value)->ValidationErrors { m_p_manifest->Version = Utility::Trim(value.as()); return {}; } }, + { "Moniker", [this](const YAML::Node& value)->ValidationErrors { m_p_manifest->Moniker = Utility::Trim(value.as()); return {}; } }, + }; + + std::move(v1RootFields.begin(), v1RootFields.end(), std::inserter(result, result.end())); + } + } + + // Root fields mapped as Installer and Localization values + auto rootInstallerFields = GetInstallerFieldProcessInfo(manifestVersion, true); + std::move(rootInstallerFields.begin(), rootInstallerFields.end(), std::inserter(result, result.end())); - std::string Name; - std::function ProcessFunc; - }; + auto rootLocalizationFields = GetInstallerFieldProcessInfo(manifestVersion, true); + std::move(rootLocalizationFields.begin(), rootLocalizationFields.end(), std::inserter(result, result.end())); - std::vector GetRootFieldProcessInfo(const ManifestVer& manifestVersion, Manifest** ppManifest) + return result; + } + + std::vector ManifestYamlPopulator::GetInstallerFieldProcessInfo(const ManifestVer& manifestVersion, bool forRootFields) { + // Common fields across versions std::vector result = { - { "ManifestVersion", [](const YAML::Node&)->ValidationErrors { /* ManifestVersion already processed. Field listed here for duplicate and PascalCase check */ return {}; } }, - { "Installers", [=](const YAML::Node& value)->ValidationErrors { *m_p_installersNode = value; }, true }, - { "Localization", [=](const YAML::Node& value)->ValidationErrors { *m_p_localizationsNode = value; } }, + { "InstallerType", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->InstallerType = ConvertToInstallerTypeEnum(value.as()); return {}; } }, + { "PackageFamilyName", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->PackageFamilyName = value.as(); return {}; } }, + { "ProductCode", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->ProductCode = value.as(); return {}; } }, }; + // Additional version specific fields if (manifestVersion.Major() == 0) { - return - { - { "Id", [=](const YAML::Node& value)->ValidationErrors { auto s = value.as(); (*ppManifest)->Id = Utility::Trim(s); return {}; } }, - { "Version", [=](const YAML::Node& value)->ValidationErrors { auto s = value.as(); (*ppManifest)->Version = Utility::Trim(s); return {}; } }, - { "Name", [=](const YAML::Node& value)->ValidationErrors { auto s = value.as(); (*ppManifest)->DefaultLocalization.Add(Utility::Trim(s)); return {}; } }, - { "Publisher", [=](const YAML::Node& value)->ValidationErrors { (*ppManifest)->DefaultLocalization.Add(value.as()); return {}; } }, - { "Author", [=](const YAML::Node& value)->ValidationErrors { (*ppManifest)->DefaultLocalization.Add(value.as()); return {}; } }, - { "License", [=](const YAML::Node& value)->ValidationErrors { (*ppManifest)->DefaultLocalization.Add(value.as()); return {}; } }, - { "LicenseUrl", [=](const YAML::Node& value)->ValidationErrors { (*ppManifest)->DefaultLocalization.Add(value.as()); return {}; } }, - { "AppMoniker", [=](const YAML::Node& value)->ValidationErrors { auto s = value.as(); (*ppManifest)->DefaultLocalization.Add(Utility::Trim(s)); return {}; } }, - { "Channel", [=](const YAML::Node& value)->ValidationErrors { auto s = value.as(); (*ppManifest)->DefaultInstallerInfo.Channel = Utility::Trim(s); return {}; } }, - { "MinOSVersion", [=](const YAML::Node& value)->ValidationErrors { (*ppManifest)->DefaultInstallerInfo.MinOSVersion = value.as(); } }, - { "Tags", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->Tags = SplitMultiValueField(value.as()); } }, - { "Commands", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->Commands = SplitMultiValueField(value.as()); } }, - { "Protocols", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->Protocols = SplitMultiValueField(value.as()); } }, - { "FileExtensions", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->FileExtensions = SplitMultiValueField(value.as()); } }, - { "UpdateBehavior", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->UpdateBehavior = ManifestInstaller::ConvertToUpdateBehaviorEnum(value.as()); } }, - - { "Homepage", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->Homepage = value.as(); } }, - - { "InstallerType", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->InstallerType = ManifestInstaller::ConvertToInstallerTypeEnum(value.as()); } }, - { "PackageFamilyName", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->PackageFamilyName = value.as(); }, false, "[-.A-Za-z0-9]+_[A-Za-z0-9]{13}" }, - { "ProductCode", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->ProductCode = value.as(); } }, - { "Description", [=](const YAML::Node& value)->ValidationErrors { m_p_manifest->Description = value.as(); } }, - { "Switches", [=](const YAML::Node& value)->ValidationErrors { *m_p_switchesNode = value; } }, - + // Root level and Localization node level + std::vector previewCommonFields = + { + { "UpdateBehavior", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->UpdateBehavior = ConvertToUpdateBehaviorEnum(value.as()); return {}; } }, + { "Switches", [this](const YAML::Node& value)->ValidationErrors { m_p_switches = &(m_p_installer->Switches); return ValidateAndProcessFields(value, SwitchesFieldInfos); } }, }; + + std::move(previewCommonFields.begin(), previewCommonFields.end(), std::inserter(result, result.end())); + + if (!forRootFields) + { + // Installer node only + std::vector installerOnlyFields = + { + { "Arch", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Arch = Utility::ConvertToArchitectureEnum(value.as()); return {}; } }, + { "Url", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Url = value.as(); return {}; } }, + { "Sha256", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Sha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, + { "SignatureSha256", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->SignatureSha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, + { "Language", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Locale = value.as(); return {}; } }, + { "Scope", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Scope = ConvertToScopeEnum(value.as()); return {}; } }, + }; + + if (manifestVersion.HasExtension(s_MSStoreExtension)) + { + installerOnlyFields.emplace_back("ProductId", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->ProductId = value.as(); return {}; }); + } + + std::move(installerOnlyFields.begin(), installerOnlyFields.end(), std::inserter(result, result.end())); + } + else + { + // Root node only + std::vector rootOnlyFields = + { + { "Channel", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Channel = Utility::Trim(value.as()); return {}; } }, + { "MinOSVersion", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->MinOSVersion = value.as(); return {}; } }, + { "Commands", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Commands = SplitMultiValueField(value.as()); return {}; } }, + { "Protocols", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Protocols = SplitMultiValueField(value.as()); return {}; } }, + { "FileExtensions", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->FileExtensions = SplitMultiValueField(value.as()); return {}; } }, + }; + + std::move(rootOnlyFields.begin(), rootOnlyFields.end(), std::inserter(result, result.end())); + } + } + else if (manifestVersion.Major() == 1) + { + // Starting v1, we should be only adding new fields for each minor version increase + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1 }) + { + // Root level and Localization node level + std::vector v1CommonFields = + { + { "Channel", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Channel = Utility::Trim(value.as()); return {}; } }, + { "InstallerLocale", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Locale = value.as(); return {}; } }, + { "Platform", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Platform = ProcessPlatformSequenceNode(value); return {}; } }, + { "MinimumOSVersion", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->MinOSVersion = value.as(); return {}; } }, + { "Scope", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Scope = ConvertToScopeEnum(value.as()); return {}; } }, + { "InstallModes", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->InstallModes = ProcessScopeSequenceNode(value); return {}; } }, + { "InstallerSwitches", [this](const YAML::Node& value)->ValidationErrors { m_p_switches = &(m_p_installer->Switches); return ValidateAndProcessFields(value, SwitchesFieldInfos); } }, + { "InstallerSuccessCodes", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->InstallerSuccessCodes = ProcessIntSequenceNode(value); return {}; } }, + { "UpgradeBehavior", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->UpdateBehavior = ConvertToUpdateBehaviorEnum(value.as()); return {}; } }, + { "Commands", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Commands = ProcessStringSequenceNode(value); return {}; } }, + { "Protocols", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Protocols = ProcessStringSequenceNode(value); return {}; } }, + { "FileExtensions", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->FileExtensions = ProcessStringSequenceNode(value); return {}; } }, + { "Dependencies", [this](const YAML::Node& value)->ValidationErrors { m_p_dependency = &(m_p_installer->Dependencies); return ValidateAndProcessFields(value, DependenciesFieldInfos); } }, + { "Capabilities", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Capabilities = ProcessStringSequenceNode(value); return {}; } }, + { "RestrictedCapabilities", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->RestrictedCapabilities = ProcessStringSequenceNode(value); return {}; } }, + }; + + std::move(v1CommonFields.begin(), v1CommonFields.end(), std::inserter(result, result.end())); + + if (!forRootFields) + { + // Installer level only fields + std::vector v1InstallerFields = + { + { "Architecture", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Arch = Utility::ConvertToArchitectureEnum(value.as()); return {}; } }, + { "InstallerUrl", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Url = value.as(); return {}; } }, + { "InstallerSha256", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Sha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, + { "SignatureSha256", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->SignatureSha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, + }; + + std::move(v1InstallerFields.begin(), v1InstallerFields.end(), std::inserter(result, result.end())); + } + } } - else + + return result; + } + + std::vector ManifestYamlPopulator::GetSwitchesFieldProcessInfo(const ManifestVer& manifestVersion) + { + // Common fields across versions + std::vector result = + { + { "Custom", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::Custom] = value.as(); return{}; } }, + { "Silent", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::Silent] = value.as(); return{}; } }, + { "SilentWithProgress", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::SilentWithProgress] = value.as(); return{}; } }, + { "Interactive", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::Interactive] = value.as(); return{}; } }, + { "Log", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::Log] = value.as(); return{}; } }, + { "InstallLocation", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::InstallLocation] = value.as(); return{}; } }, + { "Update", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::Update] = value.as(); return{}; } }, + }; + + // Additional version specific fields + if (manifestVersion.Major() == 0) + { + // Language only exists in preview manifests. Though we don't use it in our code yet, keep it here to be consistent with schema. + result.emplace_back("Language", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::Language] = value.as(); return{}; }); + } + else if (manifestVersion.Major() == 1) + { + // No new fields in v1 yet + } + + return result; + } + + std::vector ManifestYamlPopulator::GetLocalizationFieldProcessInfo(const ManifestVer& manifestVersion, bool forRootFields) + { + // Common fields across versions + std::vector result = + { + { "Description", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "LicenseUrl", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + }; + + // Additional version specific fields + if (manifestVersion.Major() == 0) + { + // Root level and Localization node level + result.emplace_back("Homepage", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; }); + + if (!forRootFields) + { + // Localization node only + result.emplace_back("Language", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Locale = value.as(); return {}; }); + } + else + { + // Root node only + std::vector rootOnlyFields = + { + { "Name", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(Utility::Trim(value.as())); return {}; } }, + { "Publisher", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "Author", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "License", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "Tags", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(SplitMultiValueField(value.as())); return {}; } }, + }; + + std::move(rootOnlyFields.begin(), rootOnlyFields.end(), std::inserter(result, result.end())); + } + } + else if (manifestVersion.Major() == 1) { // Starting v1, we should be only adding new fields for each minor version increase if (manifestVersion >= ManifestVer{ s_ManifestVersionV1 }) { + // Root level and Localization node level + std::vector v1CommonFields = + { + { "PackageLocale", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Locale = value.as(); return {}; } }, + { "Publisher", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "PublisherUrl", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "PublisherSupportUrl", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "PrivacyUrl", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "Author", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "PackageName", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(Utility::Trim(value.as())); return {}; } }, + { "PackageUrl", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "License", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "Copyright", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "CopyrightUrl", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "ShortDescription", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(value.as()); return {}; } }, + { "Tags", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add(ProcessStringSequenceNode(value)); return {}; } }, + }; + std::move(v1CommonFields.begin(), v1CommonFields.end(), std::inserter(result, result.end())); } + } + return result; + } + + std::vector ManifestYamlPopulator::GetDependenciesFieldProcessInfo(const ManifestVer& manifestVersion) + { + std::vector result = {}; + + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1 }) + { + result = + { + { "WindowsFeatures", [this](const YAML::Node& value)->ValidationErrors { m_p_dependency->WindowsFeatures = ProcessStringSequenceNode(value); return {}; } }, + { "WindowsLibraries", [this](const YAML::Node& value)->ValidationErrors { m_p_dependency->WindowsLibraries = ProcessStringSequenceNode(value); return {}; } }, + { "PackageDependencies", [this](const YAML::Node& value)->ValidationErrors { return ProcessPackageDependenciesNode(value, m_p_dependency->PackageDependencies); } }, + { "ExternalDependencies", [this](const YAML::Node& value)->ValidationErrors { m_p_dependency->ExternalDependencies = ProcessStringSequenceNode(value); return {}; } }, + }; } + + return result; } - std::vector GetInstallerFieldProcessInfo(const ManifestVer& manifestVersion) + std::vector ManifestYamlPopulator::GetPackageDependenciesFieldProcessInfo(const ManifestVer& manifestVersion) { - return {}; + std::vector result = {}; + + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1 }) + { + result = + { + { "PackageIdentifier", [this](const YAML::Node& value)->ValidationErrors { m_p_packageDependency->Id = Utility::Trim(value.as()); return {}; } }, + { "MinimumVersion", [this](const YAML::Node& value)->ValidationErrors { m_p_packageDependency->MinVersion = Utility::Trim(value.as()); return {}; } }, + }; + } + + return result; } - std::vector GetLocalizationFieldProcessInfo(const ManifestVer& manifestVersion) + ValidationErrors ManifestYamlPopulator::ValidateAndProcessFields( + const YAML::Node& rootNode, + const std::vector& fieldInfos) { - return {}; + ValidationErrors resultErrors; + + if (!rootNode.IsMap() || rootNode.size() == 0) + { + resultErrors.emplace_back(ManifestError::InvalidRootNode, "", "", rootNode.Mark().line, rootNode.Mark().column); + return resultErrors; + } + + // Keeps track of already processed fields. Used to check duplicate fields. + std::set processedFields; + + for (auto const& keyValuePair : rootNode.Mapping()) + { + std::string key = keyValuePair.first.as(); + const YAML::Node& valueNode = keyValuePair.second; + + // We'll do case insensitive search first and validate correct case later. + auto fieldIter = std::find_if(fieldInfos.begin(), fieldInfos.end(), + [&](auto const& s) + { + return Utility::CaseInsensitiveEquals(s.Name, key); + }); + + if (fieldIter != fieldInfos.end()) + { + const FieldProcessInfo& fieldInfo = *fieldIter; + + // Make sure the found key is in Pascal Case + if (key != fieldInfo.Name) + { + resultErrors.emplace_back(ManifestError::FieldIsNotPascalCase, key, "", keyValuePair.first.Mark().line, keyValuePair.first.Mark().column); + } + + // Make sure it's not a duplicate key + if (!processedFields.insert(fieldInfo.Name).second) + { + resultErrors.emplace_back(ManifestError::FieldDuplicate, fieldInfo.Name, "", keyValuePair.first.Mark().line, keyValuePair.first.Mark().column); + } + + if (!valueNode.IsNull()) + { + auto errors = fieldInfo.ProcessFunc(valueNode); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + } + } + else + { + // For full validation, also reports unrecognized fields as warning + if (m_fullValidation) + { + resultErrors.emplace_back(ManifestError::FieldUnknown, key, "", keyValuePair.first.Mark().line, keyValuePair.first.Mark().column, ValidationError::Level::Warning); + } + } + } + + return resultErrors; } - std::vector GetDependenciesFieldProcessInfo(const ManifestVer& manifestVersion) + ValidationErrors ManifestYamlPopulator::ProcessLocalizationNode(const YAML::Node& rootNode, std::vector& localizations) { - return {}; + ValidationErrors resultErrors; + + for (auto const& entry : rootNode.Sequence()) + { + ManifestLocalization localization; + m_p_localization = &localization; + auto errors = ValidateAndProcessFields(entry, LocalizationFieldInfos); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + localizations.emplace_back(std::move(std::move(localization))); + } + + return resultErrors; } - std::vector GetPackageDependenciesFieldProcessInfo(const ManifestVer& manifestVersion) + ValidationErrors ManifestYamlPopulator::ProcessPackageDependenciesNode(const YAML::Node& rootNode, std::vector& packageDependencies) { - return {}; + ValidationErrors resultErrors; + + for (auto const& entry : rootNode.Sequence()) + { + PackageDependency packageDependency; + m_p_packageDependency = &packageDependency; + auto errors = ValidateAndProcessFields(entry, PackageDependenciesFieldInfos); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + packageDependencies.emplace_back(std::move(std::move(packageDependency))); + } + + return resultErrors; } - std::vector PopulateManifest(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion) + ValidationErrors ManifestYamlPopulator::PopulateManifestInternal(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, bool fullValidation) { - + ValidationErrors resultErrors; + m_fullValidation = fullValidation; + manifest.ManifestVersion = manifestVersion; + + // Prepare field infos + RootFieldInfos = GetRootFieldProcessInfo(manifestVersion); + InstallerFieldInfos = GetInstallerFieldProcessInfo(manifestVersion); + SwitchesFieldInfos = GetSwitchesFieldProcessInfo(manifestVersion); + DependenciesFieldInfos = GetDependenciesFieldProcessInfo(manifestVersion); + PackageDependenciesFieldInfos = GetPackageDependenciesFieldProcessInfo(manifestVersion); + LocalizationFieldInfos = GetLocalizationFieldProcessInfo(manifestVersion); + + YAML::Node installersNode; + m_p_installersNode = &installersNode; + m_p_manifest = &manifest; + m_p_installer = &(manifest.DefaultInstallerInfo); + m_p_localization = &(manifest.DefaultLocalization); + // Populate root + resultErrors = ValidateAndProcessFields(rootNode, RootFieldInfos); + + // Populate installers + for (auto const& entry : installersNode.Sequence()) + { + ManifestInstaller installer = manifest.DefaultInstallerInfo; + m_p_installer = &installer; + auto errors = ValidateAndProcessFields(entry, InstallerFieldInfos); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + manifest.Installers.emplace_back(std::move(installer)); + } + + // Populate installer default switches if not exists + for (auto& installer : manifest.Installers) + { + auto defaultSwitches = GetDefaultKnownSwitches(installer.InstallerType); + for (auto const& entry : defaultSwitches) + { + if (installer.Switches.find(entry.first) == installer.Switches.end()) + { + installer.Switches[entry.first] = entry.second; + } + } + } + + return resultErrors; + } + + ValidationErrors ManifestYamlPopulator::PopulateManifest(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, bool fullValidation) + { + ManifestYamlPopulator manifestPopulator; + return manifestPopulator.PopulateManifest(rootNode, manifest, manifestVersion, fullValidation); } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp index 58bb3dff84..ac03c52800 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp @@ -5,701 +5,439 @@ #include "winget/Yaml.h" #include "winget/ManifestYamlParser.h" #include "winget/ManifestSchemaValidation.h" +#include "winget/ManifestYamlPopulator.h" -namespace AppInstaller::Manifest +namespace AppInstaller::Manifest::YamlParser { namespace { - std::vector SplitMultiValueField(const std::string& input) + ManifestVer ValidateInput(std::vector& input, bool fullValidation, bool isPartialManifest) { - if (input.empty()) - { - return {}; - } + std::vector errors; - std::vector result; - size_t currentPos = 0; - while (currentPos < input.size()) - { - size_t splitPos = input.find(',', currentPos); - if (splitPos == std::string::npos) - { - splitPos = input.size(); - } + std::string manifestVersionStr; + ManifestVer manifestVersion; + ManifestVer ManifestVersionV1{ s_ManifestVersionV1 }; + bool isMultifileManifest = input.size() > 1; - std::string splitVal = input.substr(currentPos, splitPos - currentPos); - Utility::Trim(splitVal); - if (!splitVal.empty()) - { - result.emplace_back(std::move(splitVal)); - } - currentPos = splitPos + 1; + // Use the first manifest doc to determine ManifestVersion, there'll be checks for manifest version consistency later + auto& firstYamlManifest = input[0]; + if (!firstYamlManifest.Root.IsMap()) + { + THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "The manifest does not contain a valid root. File: %S", firstYamlManifest.FileName.c_str()); } - return result; - } - } - - void YamlParser::PrepareManifestFieldInfos(const ManifestVer& manifestVer) - { - // Initially supported fields - RootFieldInfos = - { - { "ManifestVersion", [](const YAML::Node&) { /* ManifestVersion already processed */ }, false, - // Regex here is to prevent leading 0s in the version, this also keeps consistent with other versions in the manifest - "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$" }, - { "Id", [this](const YAML::Node& value) { m_p_manifest->Id = value.as(); Utility::Trim(m_p_manifest->Id); }, true, "^[\\S]+\\.[\\S]+$" }, - { "Name", [this](const YAML::Node& value) { m_p_manifest->Name = value.as(); Utility::Trim(m_p_manifest->Name); }, true }, - { "Version", [this](const YAML::Node& value) { m_p_manifest->Version = value.as(); Utility::Trim(m_p_manifest->Version); }, true, - /* File name chars not allowed */ "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$" }, - { "Publisher", [this](const YAML::Node& value) { m_p_manifest->Publisher = value.as(); }, true }, - { "AppMoniker", [this](const YAML::Node& value) { m_p_manifest->AppMoniker = value.as(); Utility::Trim(m_p_manifest->AppMoniker); } }, - { "Channel", [this](const YAML::Node& value) { m_p_manifest->Channel = value.as(); Utility::Trim(m_p_manifest->Channel); } }, - { "Author", [this](const YAML::Node& value) { m_p_manifest->Author = value.as(); } }, - { "License", [this](const YAML::Node& value) { m_p_manifest->License = value.as(); } }, - { "MinOSVersion", [this](const YAML::Node& value) { m_p_manifest->MinOSVersion = value.as(); Utility::Trim(m_p_manifest->MinOSVersion); }, false, - "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$" }, - { "Tags", [this](const YAML::Node& value) { m_p_manifest->Tags = SplitMultiValueField(value.as()); } }, - { "Commands", [this](const YAML::Node& value) { m_p_manifest->Commands = SplitMultiValueField(value.as()); } }, - { "Protocols", [this](const YAML::Node& value) { m_p_manifest->Protocols = SplitMultiValueField(value.as()); } }, - { "FileExtensions", [this](const YAML::Node& value) { m_p_manifest->FileExtensions = SplitMultiValueField(value.as()); } }, - { "InstallerType", [this](const YAML::Node& value) { m_p_manifest->InstallerType = ManifestInstaller::ConvertToInstallerTypeEnum(value.as()); } }, - { "UpdateBehavior", [this](const YAML::Node& value) { m_p_manifest->UpdateBehavior = ManifestInstaller::ConvertToUpdateBehaviorEnum(value.as()); } }, - { "PackageFamilyName", [this](const YAML::Node& value) { m_p_manifest->PackageFamilyName = value.as(); }, false, "[-.A-Za-z0-9]+_[A-Za-z0-9]{13}" }, - { "ProductCode", [this](const YAML::Node& value) { m_p_manifest->ProductCode = value.as(); } }, - { "Description", [this](const YAML::Node& value) { m_p_manifest->Description = value.as(); } }, - { "Homepage", [this](const YAML::Node& value) { m_p_manifest->Homepage = value.as(); } }, - { "LicenseUrl", [this](const YAML::Node& value) { m_p_manifest->LicenseUrl = value.as(); } }, - { "Switches", [this](const YAML::Node& value) { *m_p_switchesNode = value; } }, - { "Installers", [this](const YAML::Node& value) { *m_p_installersNode = value; }, true }, - { "Localization", [this](const YAML::Node& value) { *m_p_localizationsNode = value; } }, - }; - - InstallerFieldInfos = - { - { "Arch", [this](const YAML::Node& value) { m_p_installer->Arch = Utility::ConvertToArchitectureEnum(value.as()); }, true }, - { "Url", [this](const YAML::Node& value) { m_p_installer->Url = value.as(); } }, - { "Sha256", [this](const YAML::Node& value) { m_p_installer->Sha256 = Utility::SHA256::ConvertToBytes(value.as()); }, false, "^[A-Fa-f0-9]{64}$" }, - { "SignatureSha256", [this](const YAML::Node& value) { m_p_installer->SignatureSha256 = Utility::SHA256::ConvertToBytes(value.as()); }, false, "^[A-Fa-f0-9]{64}$" }, - { "Language", [this](const YAML::Node& value) { m_p_installer->Language = value.as(); } }, - { "Scope", [this](const YAML::Node& value) { m_p_installer->Scope = ManifestInstaller::ConvertToScopeEnum(value.as()); } }, - { "InstallerType", [this](const YAML::Node& value) { m_p_installer->InstallerType = ManifestInstaller::ConvertToInstallerTypeEnum(value.as()); } }, - { "UpdateBehavior", [this](const YAML::Node& value) { m_p_installer->UpdateBehavior = ManifestInstaller::ConvertToUpdateBehaviorEnum(value.as()); } }, - { "PackageFamilyName", [this](const YAML::Node& value) { m_p_installer->PackageFamilyName = value.as(); }, false, "[-.A-Za-z0-9]+_[A-Za-z0-9]{13}" }, - { "ProductCode", [this](const YAML::Node& value) { m_p_installer->ProductCode = value.as(); } }, - { "Switches", [this](const YAML::Node& value) { *m_p_switchesNode = value; } }, - }; - - SwitchesFieldInfos = - { - { "Custom", [this](const YAML::Node& value) { (*m_p_switches)[ManifestInstaller::InstallerSwitchType::Custom] = value.as(); } }, - { "Silent", [this](const YAML::Node& value) { (*m_p_switches)[ManifestInstaller::InstallerSwitchType::Silent] = value.as(); } }, - { "SilentWithProgress", [this](const YAML::Node& value) { (*m_p_switches)[ManifestInstaller::InstallerSwitchType::SilentWithProgress] = value.as(); } }, - { "Interactive", [this](const YAML::Node& value) { (*m_p_switches)[ManifestInstaller::InstallerSwitchType::Interactive] = value.as(); } }, - { "Language", [this](const YAML::Node& value) { (*m_p_switches)[ManifestInstaller::InstallerSwitchType::Language] = value.as(); } }, - { "Log", [this](const YAML::Node& value) { (*m_p_switches)[ManifestInstaller::InstallerSwitchType::Log] = value.as(); } }, - { "InstallLocation", [this](const YAML::Node& value) { (*m_p_switches)[ManifestInstaller::InstallerSwitchType::InstallLocation] = value.as(); } }, - { "Update", [this](const YAML::Node& value) { (*m_p_switches)[ManifestInstaller::InstallerSwitchType::Update] = value.as(); } }, - }; - - LocalizationFieldInfos = - { - { "Language", [this](const YAML::Node& value) { m_p_localization->Language = value.as(); }, true }, - { "Description", [this](const YAML::Node& value) { m_p_localization->Description = value.as(); } }, - { "Homepage", [this](const YAML::Node& value) { m_p_localization->Homepage = value.as(); } }, - { "LicenseUrl", [this](const YAML::Node& value) { m_p_localization->LicenseUrl = value.as(); } }, - }; - - // Store extension - if (manifestVer.HasExtension(s_MSStoreExtension)) - { - InstallerFieldInfos.emplace_back("ProductId", [this](const YAML::Node& value) { m_p_installer->ProductId = value.as(); }); - } - } - - Manifest YamlParser::CreateFromPath(const std::filesystem::path& inputPath, bool fullValidation, bool throwOnWarning) - { - std::vector docList; - - try - { - if (std::filesystem::is_directory(inputPath)) + if (firstYamlManifest.Root["ManifestVersion"sv]) { - for (const auto& file : std::filesystem::directory_iterator(inputPath)) - { - THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_DIRECTORY_NOT_SUPPORTED), std::filesystem::is_directory(file.path()), "Subdirectory not supported in manifest path"); - - YamlManifestInfo doc; - doc.Root = YAML::Load(file.path()); - doc.FileName = file.path().filename().u8string(); - docList.emplace_back(std::move(doc)); - } + manifestVersionStr = firstYamlManifest.Root["ManifestVersion"sv].as(); } else { - YamlManifestInfo doc; - doc.Root = YAML::Load(inputPath); - doc.FileName = inputPath.filename().u8string(); - docList.emplace_back(std::move(doc)); + manifestVersionStr = s_DefaultManifestVersion; } - } - catch (const std::exception& e) - { - THROW_EXCEPTION_MSG(ManifestException(), e.what()); - } - - return CreateInternal(docList, fullValidation, throwOnWarning); - } - - Manifest YamlParser::Create(const std::string& input, bool fullValidation, bool throwOnWarning) - { - std::vector docList; - - try - { - YamlManifestInfo doc; - doc.Root = YAML::Load(input); - docList.emplace_back(std::move(doc)); - } - catch (const std::exception& e) - { - THROW_EXCEPTION_MSG(ManifestException(), e.what()); - } - - return CreateInternal(docList, fullValidation, throwOnWarning); - } - - Manifest YamlParser::CreateInternal(const std::vector& input, bool fullValidation, bool throwOnWarning) - { - Manifest manifest; - std::vector errors; - - try - { - YamlParser parser; - errors = parser.ParseManifest(input, manifest, fullValidation); - } - catch (const ManifestException&) - { - // Prevent ManifestException from being wrapped in another ManifestException - throw; - } - catch (const std::exception& e) - { - THROW_EXCEPTION_MSG(ManifestException(), e.what()); - } - - if (!errors.empty()) - { - ManifestException ex{ std::move(errors) }; + manifestVersion = ManifestVer{ manifestVersionStr }; - if (throwOnWarning || !ex.IsWarningOnly()) + // Check max supported version + if (manifestVersion.Major() > s_MaxSupportedMajorVersion) { - THROW_EXCEPTION(ex); + THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION), "Unsupported ManifestVersion: %S", manifestVersion.ToString().c_str()); } - } - - return manifest; - } - - ManifestVer YamlParser::ValidateInput(std::vector& input, bool fullValidation, bool isPartialManifest) - { - std::vector errors; - std::string manifestVersionStr; - ManifestVer manifestVersion; - ManifestVer ManifestVersionV1{ s_ManifestVersionV1 }; - bool isMultifileManifest = input.size() > 1; - - // Use the first manifest doc to determine ManifestVersion - auto& firstYamlManifest = input[0]; - if (!firstYamlManifest.Root.IsMap()) - { - THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "The manifest does not contain a valid root. File: %S", firstYamlManifest.FileName.c_str()); - } - - if (firstYamlManifest.Root["ManifestVersion"sv]) - { - manifestVersionStr = firstYamlManifest.Root["ManifestVersion"sv].as(); - } - else - { - manifestVersionStr = s_DefaultManifestVersion; - } - manifestVersion = ManifestVer{ manifestVersionStr }; - - // Check max supported version - if (manifestVersion.Major() > s_MaxSupportedMajorVersion) - { - THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION), "Unsupported ManifestVersion: %S", manifestVersion.ToString().c_str()); - } - - // multi file manifest is only supported starting ManifestVersion 1.0.0 - if (isMultifileManifest && manifestVersion < ManifestVersionV1) - { - THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "Preview manifest does not support multi file manifest format."); - } - - if (isMultifileManifest) - { - // Populates the PackageIdentifier and PackageVersion from first doc for later consistency check - std::string packageId = firstYamlManifest.Root["PackageIdentifier"].as(); - std::string packageVersion = firstYamlManifest.Root["PackageVersion"].as(); - - bool isVersionManifestFound = false; - bool isInstallerManifestFound = false; - bool isDefaultLocaleManifestFound = false; - std::string defaultLocaleFromVersionManifest; - std::string defaultLocaleFromDefaultLocaleManifest; - - for (auto& entry : input) + // multi file manifest is only supported starting ManifestVersion 1.0.0 + if (isMultifileManifest && manifestVersion < ManifestVersionV1) { - if (!entry.Root.IsMap()) - { - THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "The manifest does not contain a valid root. File: %S", entry.FileName.c_str()); - } - - std::string localPackageId = entry.Root["PackageIdentifier"].as(); - if (localPackageId != packageId) - { - errors.emplace_back(ValidationError::MessageFieldValueWithFile( - ManifestError::InconsistentMultiFileManifestFieldValue, "PackageIdentifier", localPackageId, entry.FileName)); - } - - std::string localPackageVersion = entry.Root["PackageVersion"].as(); - if (localPackageVersion != packageVersion) - { - errors.emplace_back(ValidationError::MessageFieldValueWithFile( - ManifestError::InconsistentMultiFileManifestFieldValue, "PackageVersion", localPackageVersion, entry.FileName)); - } + THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "Preview manifest does not support multi file manifest format."); + } - std::string localManifestVersion = entry.Root["ManifestVersion"].as(); - if (localManifestVersion != manifestVersionStr) - { - errors.emplace_back(ValidationError::MessageFieldValueWithFile( - ManifestError::InconsistentMultiFileManifestFieldValue, "ManifestVersion", localManifestVersion, entry.FileName)); - } + if (isMultifileManifest) + { + // Populates the PackageIdentifier and PackageVersion from first doc for later consistency check + std::string packageId = firstYamlManifest.Root["PackageIdentifier"].as(); + std::string packageVersion = firstYamlManifest.Root["PackageVersion"].as(); - std::string manifestTypeStr = entry.Root["ManifestType"sv].as(); - ManifestTypeEnum manifestType = ConvertToManifestTypeEnum(manifestTypeStr); - entry.ManifestType = manifestType; + bool isVersionManifestFound = false; + bool isInstallerManifestFound = false; + bool isDefaultLocaleManifestFound = false; + std::string defaultLocaleFromVersionManifest; + std::string defaultLocaleFromDefaultLocaleManifest; - switch (manifestType) + for (auto& entry : input) { - case ManifestTypeEnum::Version: - if (isVersionManifestFound) + if (!entry.Root.IsMap()) { - errors.emplace_back(ValidationError::MessageFieldValueWithFile( - ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "The manifest does not contain a valid root. File: %S", entry.FileName.c_str()); } - else - { - isVersionManifestFound = true; - defaultLocaleFromVersionManifest = entry.Root["DefaultLocale"sv].as(); - } - break; - case ManifestTypeEnum::Installer: - if (isInstallerManifestFound) + + std::string localPackageId = entry.Root["PackageIdentifier"].as(); + if (localPackageId != packageId) { errors.emplace_back(ValidationError::MessageFieldValueWithFile( - ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + ManifestError::InconsistentMultiFileManifestFieldValue, "PackageIdentifier", localPackageId, entry.FileName)); } - else + + std::string localPackageVersion = entry.Root["PackageVersion"].as(); + if (localPackageVersion != packageVersion) { - isInstallerManifestFound = true; + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::InconsistentMultiFileManifestFieldValue, "PackageVersion", localPackageVersion, entry.FileName)); } - break; - case ManifestTypeEnum::DefaultLocale: - if (isDefaultLocaleManifestFound) + + std::string localManifestVersion = entry.Root["ManifestVersion"].as(); + if (localManifestVersion != manifestVersionStr) { errors.emplace_back(ValidationError::MessageFieldValueWithFile( - ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + ManifestError::InconsistentMultiFileManifestFieldValue, "ManifestVersion", localManifestVersion, entry.FileName)); } - else + + std::string manifestTypeStr = entry.Root["ManifestType"sv].as(); + ManifestTypeEnum manifestType = ConvertToManifestTypeEnum(manifestTypeStr); + entry.ManifestType = manifestType; + + switch (manifestType) { - isDefaultLocaleManifestFound = true; - defaultLocaleFromDefaultLocaleManifest = entry.Root["PackageLocale"sv].as(); + case ManifestTypeEnum::Version: + if (isVersionManifestFound) + { + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + } + else + { + isVersionManifestFound = true; + defaultLocaleFromVersionManifest = entry.Root["DefaultLocale"sv].as(); + } + break; + case ManifestTypeEnum::Installer: + if (isInstallerManifestFound) + { + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + } + else + { + isInstallerManifestFound = true; + } + break; + case ManifestTypeEnum::DefaultLocale: + if (isDefaultLocaleManifestFound) + { + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + } + else + { + isDefaultLocaleManifestFound = true; + defaultLocaleFromDefaultLocaleManifest = entry.Root["PackageLocale"sv].as(); + } + break; + case ManifestTypeEnum::Locale: + // Nothing to validate + break; + default: + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::UnsupportedMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); } - break; - case ManifestTypeEnum::Locale: - // Nothing to validate - break; - default: - errors.emplace_back(ValidationError::MessageFieldValueWithFile( - ManifestError::UnsupportedMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); } - } - - if (isVersionManifestFound && isDefaultLocaleManifestFound && defaultLocaleFromDefaultLocaleManifest != defaultLocaleFromVersionManifest) - { - errors.emplace_back(ManifestError::InconsistentMultiFileManifestDefaultLocale); - } - if (!isPartialManifest && !(isVersionManifestFound && isInstallerManifestFound && isDefaultLocaleManifestFound)) - { - errors.emplace_back(ManifestError::IncompleteMultiFileManifest); - } - } - else - { - if (manifestVersion >= ManifestVersionV1) - { - std::string manifestTypeStr = firstYamlManifest.Root["ManifestType"sv].as(); - ManifestTypeEnum manifestType = ConvertToManifestTypeEnum(manifestTypeStr); - firstYamlManifest.ManifestType = manifestType; - - if (fullValidation && manifestType == ManifestTypeEnum::Merged) + if (isVersionManifestFound && isDefaultLocaleManifestFound && defaultLocaleFromDefaultLocaleManifest != defaultLocaleFromVersionManifest) { - errors.emplace_back(ValidationError::MessageFieldValueWithFile(ManifestError::FieldValueNotSupported, "ManifestType", manifestTypeStr, firstYamlManifest.FileName)); + errors.emplace_back(ManifestError::InconsistentMultiFileManifestDefaultLocale); } - if (!isPartialManifest && manifestType != ManifestTypeEnum::Merged && manifestType != ManifestTypeEnum::Singleton) + if (!isPartialManifest && !(isVersionManifestFound && isInstallerManifestFound && isDefaultLocaleManifestFound)) { - errors.emplace_back(ValidationError::MessageWithFile(ManifestError::IncompleteMultiFileManifest, firstYamlManifest.FileName)); + errors.emplace_back(ManifestError::IncompleteMultiFileManifest); } } else { - firstYamlManifest.ManifestType = ManifestTypeEnum::Preview; - } - } + if (manifestVersion >= ManifestVersionV1) + { + std::string manifestTypeStr = firstYamlManifest.Root["ManifestType"sv].as(); + ManifestTypeEnum manifestType = ConvertToManifestTypeEnum(manifestTypeStr); + firstYamlManifest.ManifestType = manifestType; - if (!errors.empty()) - { - ManifestException ex{ std::move(errors) }; - THROW_EXCEPTION(ex); - } + if (fullValidation && manifestType == ManifestTypeEnum::Merged) + { + errors.emplace_back(ValidationError::MessageFieldValueWithFile(ManifestError::FieldValueNotSupported, "ManifestType", manifestTypeStr, firstYamlManifest.FileName)); + } - return manifestVersion; - } + if (!isPartialManifest && manifestType != ManifestTypeEnum::Merged && manifestType != ManifestTypeEnum::Singleton) + { + errors.emplace_back(ValidationError::MessageWithFile(ManifestError::IncompleteMultiFileManifest, firstYamlManifest.FileName)); + } + } + else + { + firstYamlManifest.ManifestType = ManifestTypeEnum::Preview; + } + } - const YAML::Node& FindUniqueRequiredDocFromMultiFileManifest(const std::vector& input, ManifestTypeEnum manifestType) - { - // We'll do case insensitive search first and validate correct case later. - auto iter = std::find_if(input.begin(), input.end(), - [](auto const& s) + if (!errors.empty()) { - return s.ManifestType == manifestType; - }); + ManifestException ex{ std::move(errors) }; + THROW_EXCEPTION(ex); + } - THROW_HR_IF(E_UNEXPECTED, iter == input.end()); + return manifestVersion; + } - return iter->Root; - } + const YAML::Node& FindUniqueRequiredDocFromMultiFileManifest(const std::vector& input, ManifestTypeEnum manifestType) + { + // We'll do case insensitive search first and validate correct case later. + auto iter = std::find_if(input.begin(), input.end(), + [](auto const& s) + { + return s.ManifestType == manifestType; + }); - void MergeOneManifestToMultiFileManifest(const YAML::Node& input, YAML::Node& destination) - { - THROW_HR_IF(E_UNEXPECTED, !input.IsMap()); - THROW_HR_IF(E_UNEXPECTED, !destination.IsMap()); + THROW_HR_IF(E_UNEXPECTED, iter == input.end()); - const std::vector FieldsToIgnore = { "PackageIdentifier", "PackageVersion", "ManifestType", "ManifestVersion" }; + return iter->Root; + } - for (auto const& keyValuePair : input.Mapping()) + void MergeOneManifestToMultiFileManifest(const YAML::Node& input, YAML::Node& destination) { - // We only support string type as key in our manifest - if (std::find(FieldsToIgnore.begin(), FieldsToIgnore.end(), keyValuePair.first.as()) == FieldsToIgnore.end()) + THROW_HR_IF(E_UNEXPECTED, !input.IsMap()); + THROW_HR_IF(E_UNEXPECTED, !destination.IsMap()); + + const std::vector FieldsToIgnore = { "PackageIdentifier", "PackageVersion", "ManifestType", "ManifestVersion" }; + + for (auto const& keyValuePair : input.Mapping()) { - YAML::Node key = keyValuePair.first; - YAML::Node value = keyValuePair.second; - destination.AddMappingNode(std::move(key), std::move(value)); + // We only support string type as key in our manifest + if (std::find(FieldsToIgnore.begin(), FieldsToIgnore.end(), keyValuePair.first.as()) == FieldsToIgnore.end()) + { + YAML::Node key = keyValuePair.first; + YAML::Node value = keyValuePair.second; + destination.AddMappingNode(std::move(key), std::move(value)); + } } } - } - YAML::Node MergeMultiFileManifest(const std::vector& input) - { - // Starts with installer manifest - YAML::Node result = FindUniqueRequiredDocFromMultiFileManifest(input, ManifestTypeEnum::Installer); + YAML::Node MergeMultiFileManifest(const std::vector& input) + { + // Starts with installer manifest + YAML::Node result = FindUniqueRequiredDocFromMultiFileManifest(input, ManifestTypeEnum::Installer); - // Copy default locale manifest content into manifest root - YAML::Node defaultLocaleManifest = FindUniqueRequiredDocFromMultiFileManifest(input, ManifestTypeEnum::DefaultLocale); - MergeOneManifestToMultiFileManifest(defaultLocaleManifest, result); + // Copy default locale manifest content into manifest root + YAML::Node defaultLocaleManifest = FindUniqueRequiredDocFromMultiFileManifest(input, ManifestTypeEnum::DefaultLocale); + MergeOneManifestToMultiFileManifest(defaultLocaleManifest, result); - // Copy additional locale manifests - YAML::Node localizations{ YAML::Node::Type::Sequence, "", YAML::Mark() }; - for (const auto& entry : input) - { - if (entry.ManifestType == ManifestTypeEnum::Locale) + // Copy additional locale manifests + YAML::Node localizations{ YAML::Node::Type::Sequence, "", YAML::Mark() }; + for (const auto& entry : input) { - YAML::Node localization{ YAML::Node::Type::Mapping, "", YAML::Mark() }; - MergeOneManifestToMultiFileManifest(entry.Root, localization); - localizations.AddSequenceNode(std::move(localization)); + if (entry.ManifestType == ManifestTypeEnum::Locale) + { + YAML::Node localization{ YAML::Node::Type::Mapping, "", YAML::Mark() }; + MergeOneManifestToMultiFileManifest(entry.Root, localization); + localizations.AddSequenceNode(std::move(localization)); + } } - } - if (localizations.size() > 0) - { - YAML::Node key{ YAML::Node::Type::Scalar, "", YAML::Mark() }; - key.SetScalar("Localization"); - result.AddMappingNode(std::move(key), std::move(localizations)); - } + if (localizations.size() > 0) + { + YAML::Node key{ YAML::Node::Type::Scalar, "", YAML::Mark() }; + key.SetScalar("Localization"); + result.AddMappingNode(std::move(key), std::move(localizations)); + } - result["ManifestType"sv].SetScalar("merged"); + result["ManifestType"sv].SetScalar("merged"); - return result; - } + return result; + } - void EmitYamlNode(const YAML::Node& input, YAML::Emitter& emitter) - { - if (input.IsMap()) + void EmitYamlNode(const YAML::Node& input, YAML::Emitter& emitter) { - emitter << YAML::BeginMap; - for (auto const& keyValuePair : input.Mapping()) + if (input.IsMap()) { - emitter << YAML::Key; - EmitYamlNode(keyValuePair.first, emitter); - emitter << YAML::Value; - EmitYamlNode(keyValuePair.second, emitter); + emitter << YAML::BeginMap; + for (auto const& keyValuePair : input.Mapping()) + { + emitter << YAML::Key; + EmitYamlNode(keyValuePair.first, emitter); + emitter << YAML::Value; + EmitYamlNode(keyValuePair.second, emitter); + } + emitter << YAML::EndMap; } - emitter << YAML::EndMap; - } - else if (input.IsSequence()) - { - emitter << YAML::BeginSeq; - for (auto const& value : input.Sequence()) + else if (input.IsSequence()) { - EmitYamlNode(value, emitter); + emitter << YAML::BeginSeq; + for (auto const& value : input.Sequence()) + { + EmitYamlNode(value, emitter); + } + emitter << YAML::EndSeq; + } + else if (input.IsScalar()) + { + emitter << input.as(); + } + else if (input.IsNull()) + { + emitter << ""; + } + else + { + THROW_HR(E_UNEXPECTED); } - emitter << YAML::EndSeq; - } - else if (input.IsScalar()) - { - emitter << input.as(); - } - else if (input.IsNull()) - { - emitter << ""; - } - else - { - THROW_HR(E_UNEXPECTED); } - } - - void OutputYamlDoc(const YAML::Node& input, const std::filesystem::path& out) - { - THROW_HR_IF(E_UNEXPECTED, !input.IsMap()); - YAML::Emitter emitter; - EmitYamlNode(input, emitter); - - std::filesystem::create_directories(out.parent_path()); - std::ofstream outFileStream(out); - emitter.Get(outFileStream); - outFileStream.close(); - } - - std::vector YamlParser::ParseManifest(const std::vector& input, Manifest& manifest, bool fullValidation, bool isPartialManifest) - { - manifest.ManifestVersion = ValidateInput(input, fullValidation, isPartialManifest); + void OutputYamlDoc(const YAML::Node& input, const std::filesystem::path& out) + { + THROW_HR_IF(E_UNEXPECTED, !input.IsMap()); - auto resultErrors = ValidateAgainstSchema(input, manifest.ManifestVersion, NULL); + YAML::Emitter emitter; + EmitYamlNode(input, emitter); - if (isPartialManifest) - { - return resultErrors; + std::filesystem::create_directories(out.parent_path()); + std::ofstream outFileStream(out); + emitter.Emit(outFileStream); + outFileStream.close(); } - const YAML::Node& manifestDoc = (input.size() > 1) ? MergeMultiFileManifest(input) : input[0].Root; + std::vector ParseManifest( + std::vector& input, + Manifest& manifest, + bool fullValidation, + bool isPartialManifest, + PCWSTR resourceDll, + const std::filesystem::path& mergedManifestPath) + { + THROW_HR_IF_MSG(E_INVALIDARG, isPartialManifest && !mergedManifestPath.empty(), "Manifest cannot be merged from partial manifest"); + THROW_HR_IF_MSG(E_INVALIDARG, input.size() == 1 && !mergedManifestPath.empty(), "Manifest cannot be merged from a single manifest"); - PrepareManifestFieldInfos(manifest.ManifestVersion); + auto manifestVersion = ValidateInput(input, fullValidation, isPartialManifest); - // Populate root fields - YAML::Node switchesNode; - YAML::Node installersNode; - YAML::Node localizationsNode; - m_p_switchesNode = &switchesNode; - m_p_installersNode = &installersNode; - m_p_localizationsNode = &localizationsNode; - m_p_manifest = &manifest; - auto resultErrors = ValidateAndProcessFields(rootNode, RootFieldInfos, fullValidation); + auto resultErrors = ValidateAgainstSchema(input, manifestVersion, resourceDll); - // Populate root switches - if (!switchesNode.IsNull()) - { - m_p_switches = &manifest.Switches; - auto errors = ValidateAndProcessFields(switchesNode, SwitchesFieldInfos, fullValidation); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - } - - // Populate installers - for (std::size_t i = 0; i < installersNode.size(); i++) - { - YAML::Node installerNode = installersNode[i]; - ManifestInstaller installer; - YAML::Node installerSwitchesNode; - - // Populate defaults - installer.InstallerType = manifest.InstallerType; - installer.UpdateBehavior = manifest.UpdateBehavior; - installer.Scope = ManifestInstaller::ScopeEnum::User; - - m_p_installer = &installer; - m_p_switchesNode = &installerSwitchesNode; - auto errors = ValidateAndProcessFields(installerNode, InstallerFieldInfos, fullValidation); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - - // Copy in system reference strings from the root if not set in the installer and appropriate - if (installer.PackageFamilyName.empty() && ManifestInstaller::DoesInstallerTypeUsePackageFamilyName(installer.InstallerType)) + // For partial manifest, only schema validations are performed + if (isPartialManifest) { - installer.PackageFamilyName = manifest.PackageFamilyName; + return resultErrors; } - if (installer.ProductCode.empty() && ManifestInstaller::DoesInstallerTypeUseProductCode(installer.InstallerType)) - { - installer.ProductCode = manifest.ProductCode; - } + const YAML::Node& manifestDoc = (input.size() > 1) ? MergeMultiFileManifest(input) : input[0].Root; - // Populate default known switches - installer.Switches = GetDefaultKnownSwitches(installer.InstallerType); + ManifestYamlPopulator::PopulateManifest(manifestDoc, manifest, manifestVersion, fullValidation); - // Override with switches from manifest root if applicable - for (auto const& keyValuePair : manifest.Switches) + // Extra semantic validations after basic validation and field population + if (fullValidation) { - installer.Switches[keyValuePair.first] = keyValuePair.second; + auto errors = ValidateManifest(manifest); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); } - // Override with switches from installer declaration if applicable - if (!installerSwitchesNode.IsNull()) + if (!mergedManifestPath.empty()) { - m_p_switches = &installer.Switches; - auto switchesErrors = ValidateAndProcessFields(installerSwitchesNode, SwitchesFieldInfos, fullValidation); - std::move(switchesErrors.begin(), switchesErrors.end(), std::inserter(resultErrors, resultErrors.end())); + OutputYamlDoc(manifestDoc, mergedManifestPath); } - manifest.Installers.emplace_back(std::move(installer)); + return resultErrors; } + } - // Populate localization fields - if (!localizationsNode.IsNull()) + Manifest CreateFromPath( + const std::filesystem::path& inputPath, + bool fullValidation, + bool throwOnWarning, + bool isPartialManifest, + PCWSTR resourceDll, + const std::filesystem::path& mergedManifestPath) + { + std::vector docList; + + try { - for (std::size_t i = 0; i < localizationsNode.size(); i++) + if (std::filesystem::is_directory(inputPath)) { - YAML::Node localizationNode = localizationsNode[i]; - ManifestLocalization localization; - - // Populates default values from root first - localization.Description = manifest.Description; - localization.Homepage = manifest.Homepage; - localization.LicenseUrl = manifest.LicenseUrl; + for (const auto& file : std::filesystem::directory_iterator(inputPath)) + { + THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_DIRECTORY_NOT_SUPPORTED), std::filesystem::is_directory(file.path()), "Subdirectory not supported in manifest path"); - m_p_localization = &localization; - auto errors = ValidateAndProcessFields(localizationNode, LocalizationFieldInfos, fullValidation); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - manifest.Localization.emplace_back(std::move(localization)); + YamlManifestInfo doc; + doc.Root = YAML::Load(file.path()); + doc.FileName = file.path().filename().u8string(); + docList.emplace_back(std::move(doc)); + } + } + else + { + YamlManifestInfo doc; + doc.Root = YAML::Load(inputPath); + doc.FileName = inputPath.filename().u8string(); + docList.emplace_back(std::move(doc)); } } - - // Extra semantic validations after basic validation and field population - if (fullValidation) + catch (const std::exception& e) { - auto errors = ValidateManifest(manifest); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + THROW_EXCEPTION_MSG(ManifestException(), e.what()); } - if (true) + return CreateImpl(docList, fullValidation, throwOnWarning, isPartialManifest, resourceDll, mergedManifestPath); + } + + Manifest Create( + const std::string& input, + bool fullValidation, + bool throwOnWarning, + bool isPartialManifest, + PCWSTR resourceDll, + const std::filesystem::path& mergedManifestPath) + { + std::vector docList; + + try { - OutputYamlDoc(manifestDoc, "merged.yaml"); + YamlManifestInfo doc; + doc.Root = YAML::Load(input); + docList.emplace_back(std::move(doc)); + } + catch (const std::exception& e) + { + THROW_EXCEPTION_MSG(ManifestException(), e.what()); } - return resultErrors; + return CreateImpl(docList, fullValidation, throwOnWarning, isPartialManifest, resourceDll, mergedManifestPath); } - std::vector YamlParser::ValidateAndProcessFields( - const YAML::Node& rootNode, - const std::vector& fieldInfos, - bool fullValidation) + Manifest CreateImpl( + std::vector& input, + bool fullValidation, + bool throwOnWarning, + bool isPartialManifest, + PCWSTR resourceDll, + const std::filesystem::path& mergedManifestPath) { + Manifest manifest; std::vector errors; - if (rootNode.size() == 0 || !rootNode.IsMap()) + try { - errors.emplace_back(ManifestError::InvalidRootNode, "", "", rootNode.Mark().line, rootNode.Mark().column); - return errors; + errors = ParseManifest(input, manifest, fullValidation, isPartialManifest, resourceDll, mergedManifestPath); } - - // Keeps track of already processed fields. Used to check duplicate fields. - std::set processedFields; - - for (auto const& keyValuePair : rootNode.Mapping()) + catch (const ManifestException&) { - std::string key = keyValuePair.first.as(); - const YAML::Node& valueNode = keyValuePair.second; - - // We'll do case insensitive search first and validate correct case later. - auto fieldIter = std::find_if(fieldInfos.begin(), fieldInfos.end(), - [&](auto const& s) - { - return Utility::CaseInsensitiveEquals(s.Name, key); - }); - - if (fieldIter != fieldInfos.end()) - { - const ManifestFieldInfo& fieldInfo = *fieldIter; - - // Make sure the found key is in Pascal Case - if (key != fieldInfo.Name) - { - errors.emplace_back(ManifestError::FieldIsNotPascalCase, key, "", keyValuePair.first.Mark().line, keyValuePair.first.Mark().column); - } + // Prevent ManifestException from being wrapped in another ManifestException + throw; + } + catch (const std::exception& e) + { + THROW_EXCEPTION_MSG(ManifestException(), e.what()); + } - // Make sure it's not a duplicate key - if (!processedFields.insert(fieldInfo.Name).second) - { - errors.emplace_back(ManifestError::FieldDuplicate, fieldInfo.Name, "", keyValuePair.first.Mark().line, keyValuePair.first.Mark().column); - } + if (!errors.empty()) + { + ManifestException ex{ std::move(errors) }; - if (!valueNode.IsNull()) - { - fieldInfo.ProcessFunc(valueNode); - } - } - else + if (throwOnWarning || !ex.IsWarningOnly()) { - // For full validation, also reports unrecognized fields as warning - if (fullValidation) - { - errors.emplace_back(ManifestError::FieldUnknown, key, "", keyValuePair.first.Mark().line, keyValuePair.first.Mark().column, ValidationError::Level::Warning); - } + THROW_EXCEPTION(ex); } } - return errors; - } - - std::map YamlParser::GetDefaultKnownSwitches( - ManifestInstaller::InstallerTypeEnum installerType) - { - switch (installerType) - { - case ManifestInstaller::InstallerTypeEnum::Burn: - case ManifestInstaller::InstallerTypeEnum::Wix: - case ManifestInstaller::InstallerTypeEnum::Msi: - return - { - {ManifestInstaller::InstallerSwitchType::Silent, ManifestInstaller::string_t("/quiet")}, - {ManifestInstaller::InstallerSwitchType::SilentWithProgress, ManifestInstaller::string_t("/passive")}, - {ManifestInstaller::InstallerSwitchType::Log, ManifestInstaller::string_t("/log \"" + std::string(ARG_TOKEN_LOGPATH) + "\"")}, - {ManifestInstaller::InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("TARGETDIR=\"" + std::string(ARG_TOKEN_INSTALLPATH) + "\"")}, - {ManifestInstaller::InstallerSwitchType::Update, ManifestInstaller::string_t("REINSTALL=ALL REINSTALLMODE=vamus")} - }; - case ManifestInstaller::InstallerTypeEnum::Nullsoft: - return - { - {ManifestInstaller::InstallerSwitchType::Silent, ManifestInstaller::string_t("/S")}, - {ManifestInstaller::InstallerSwitchType::SilentWithProgress, ManifestInstaller::string_t("/S")}, - {ManifestInstaller::InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("/D=" + std::string(ARG_TOKEN_INSTALLPATH))} - }; - case ManifestInstaller::InstallerTypeEnum::Inno: - return - { - {ManifestInstaller::InstallerSwitchType::Silent, ManifestInstaller::string_t("/VERYSILENT")}, - {ManifestInstaller::InstallerSwitchType::SilentWithProgress, ManifestInstaller::string_t("/SILENT")}, - {ManifestInstaller::InstallerSwitchType::Log, ManifestInstaller::string_t("/LOG=\"" + std::string(ARG_TOKEN_LOGPATH) + "\"")}, - {ManifestInstaller::InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("/DIR=\"" + std::string(ARG_TOKEN_INSTALLPATH) + "\"")} - }; - default: - return {}; - } + return manifest; } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerStrings.h b/src/AppInstallerCommonCore/Public/AppInstallerStrings.h index 4772e8c8f0..aef1ab20b9 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerStrings.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerStrings.h @@ -121,6 +121,7 @@ namespace AppInstaller::Utility // Removes whitespace from the beginning and end of the string. std::string& Trim(std::string& str); + std::string Trim(std::string&& str); // Reads the entire stream into a string. std::string ReadEntireStream(std::istream& stream); diff --git a/src/AppInstallerCommonCore/Public/winget/Manifest.h b/src/AppInstallerCommonCore/Public/winget/Manifest.h index 8733216a73..c9d28b60b1 100644 --- a/src/AppInstallerCommonCore/Public/winget/Manifest.h +++ b/src/AppInstallerCommonCore/Public/winget/Manifest.h @@ -14,12 +14,12 @@ namespace AppInstaller::Manifest { using string_t = Utility::NormalizedString; - // Required string_t Id; - // Required string_t Version; + string_t Moniker; + ManifestVer ManifestVersion; ManifestInstaller DefaultInstallerInfo; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestTypes.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h similarity index 90% rename from src/AppInstallerCommonCore/Public/winget/ManifestTypes.h rename to src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 7b4f739608..80358080dc 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestTypes.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -7,6 +7,7 @@ namespace AppInstaller::Manifest { using string_t = Utility::NormalizedString; + using namespace std::string_view_literals; // The maximum supported major version known about by this code. constexpr uint64_t s_MaxSupportedMajorVersion = 1; @@ -103,7 +104,7 @@ namespace AppInstaller::Manifest struct PackageDependency { string_t Id; - ManifestVer MinVersion; + string_t MinVersion; }; struct Dependency @@ -121,6 +122,8 @@ namespace AppInstaller::Manifest ScopeEnum ConvertToScopeEnum(const std::string& in); + PlatformEnum ConvertToPlatformEnum(const std::string& in); + ManifestTypeEnum ConvertToManifestTypeEnum(const std::string& in); std::string_view InstallerTypeToString(InstallerTypeEnum installerType); @@ -135,4 +138,6 @@ namespace AppInstaller::Manifest // Checks whether 2 installer types are compatible. E.g. inno and exe are update compatible bool IsInstallerTypeCompatible(InstallerTypeEnum type1, InstallerTypeEnum type2); + + std::map GetDefaultKnownSwitches(InstallerTypeEnum installerType); } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h index 7827a35d87..58a6d5e7d4 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -3,7 +3,7 @@ #pragma once #include #include -#include +#include #include #include @@ -20,15 +20,10 @@ namespace AppInstaller::Manifest { using string_t = Utility::NormalizedString; - - - // Required AppInstaller::Utility::Architecture Arch; - // Required string_t Url; - // Required std::vector Sha256; // Optional. Only used by appx/msix type. If provided, Appinstaller will @@ -76,6 +71,6 @@ namespace AppInstaller::Manifest std::vector RestrictedCapabilities; - std::vector Dependencies; + Dependency Dependencies; }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h index b7128d3079..7efb12396a 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h @@ -24,7 +24,6 @@ namespace AppInstaller::Manifest CopyrightUrl, ShortDescription, Description, - Moniker, Tags, Max }; @@ -115,12 +114,6 @@ namespace AppInstaller::Manifest using value_t = string_t; }; - template <> - struct LocalizationMapping - { - using value_t = string_t; - }; - template <> struct LocalizationMapping { @@ -147,12 +140,12 @@ namespace AppInstaller::Manifest template void Add(typename details::LocalizationMapping::value_t&& v) { - m_data[D].emplace(std::forward::value_t>(v)); + m_data[L].emplace(std::forward::value_t>(v)); } template void Add(const typename details::LocalizationMapping::value_t& v) { - m_data[D].emplace(v); + m_data[L].emplace(v); } // Return a value indicating whether the given data type is stored in the context. diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h index f8a99272f3..7d7e0d1a72 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h @@ -1,15 +1,21 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#include "ManifestTypes.h" +#pragma once +#include "ManifestCommon.h" -namespace AppInstaller::Manifest +#include + +namespace AppInstaller::Manifest::YamlParser { - struct YamlManifestInfo - { - YAML::Node Root; - std::string FileName; - ManifestTypeEnum ManifestType; - }; - - std::vector ValidateAgainstSchema(const std::vector manifestList, const ManifestVer& manifestVersion, PCWSTR resourceModuleName); + // Forward declarations + struct YamlManifestInfo; + + std::string LoadResourceAsString(PCWSTR resourceModuleName, PCWSTR resourceName, PCWSTR resourceType); + + Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType, PCWSTR resourceModuleName); + + std::vector ValidateAgainstSchema( + const std::vector manifestList, + const ManifestVer& manifestVersion, + PCWSTR resourceModuleName); } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h index 08379bd0ab..04febb553b 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h @@ -2,66 +2,46 @@ // Licensed under the MIT License. #pragma once #include -#include #include #include -namespace AppInstaller::Manifest +namespace AppInstaller::Manifest::YamlParser { - struct YamlParser + struct YamlManifestInfo { - // fullValidation: Bool to set if manifest creation should perform extra validation that client does not need. - // e.g. Channel should be null. Client code does not need this check to work properly. - // throwOnWarning: Bool to indicate if an exception should be thrown with only warnings detected in the manifest. - static Manifest CreateFromPath(const std::filesystem::path& inputPath, bool fullValidation = false, bool throwOnWarning = false); - - static Manifest Create(const std::string& input, bool fullValidation = false, bool throwOnWarning = false); - - private: - - - - static Manifest CreateInternal(const std::vector& input, bool fullValidation, bool throwOnWarning); - // These pointers are referenced in the processing functions in manifest field info table. - YAML::Node* m_p_installersNode = nullptr; - YAML::Node* m_p_switchesNode = nullptr; - YAML::Node* m_p_localizationsNode = nullptr; - AppInstaller::Manifest::Manifest* m_p_manifest = nullptr; - AppInstaller::Manifest::ManifestInstaller* m_p_installer = nullptr; - std::map* m_p_switches = nullptr; - AppInstaller::Manifest::ManifestLocalization* m_p_localization = nullptr; - - // This struct contains individual manifest field population info - struct ManifestFieldInfo - { - ManifestFieldInfo(std::string name, std::function(const YAML::Node&)> func) : - Name(std::move(name)), ProcessFunc(func) {} - - std::string Name; - std::function(const YAML::Node&)> ProcessFunc; - }; - - std::vector RootFieldInfos; - std::vector InstallerFieldInfos; - std::vector SwitchesFieldInfos; - std::vector LocalizationFieldInfos; - - std::vector ParseManifest(const std::vector& input, Manifest& manifest, bool fullValidation, bool isPartialManifest = false); - ManifestVer ValidateInput(const std::vector& input, bool fullValidation, bool isPartialManifest); - - static std::map GetDefaultKnownSwitches( - ManifestInstaller::InstallerTypeEnum installerType); - - // This method takes YAML root node and list of manifest field info. - // Yaml-cpp does not support case insensitive search and it allows duplicate keys. If duplicate keys exist, - // the value is undefined. So in this method, we will iterate through the node map and process each individual - // pair ourselves. This also helps with generating aggregated error rather than throwing on first failure. - static std::vector ValidateAndProcessFields( - const YAML::Node& rootNode, - const std::vector& fieldInfos, - bool fullValidation); - - void PrepareManifestFieldInfos(const ManifestVer& manifestVer); + YAML::Node Root; + std::string FileName; + ManifestTypeEnum ManifestType; }; + + // fullValidation: Bool to set if manifest creation should perform extra validation that client does not need. + // e.g. Channel should be null. Client code does not need this check to work properly. + // throwOnWarning: Bool to indicate if an exception should be thrown with only warnings detected in the manifest. + Manifest CreateFromPath( + const std::filesystem::path& inputPath, + bool fullValidation = false, + bool throwOnWarning = false, + bool isPartialManifest = false, + PCWSTR resourceDll = nullptr, + const std::filesystem::path& mergedManifestPath = {}); + + Manifest Create( + const std::string& input, + bool fullValidation = false, + bool throwOnWarning = false, + bool isPartialManifest = false, + PCWSTR resourceDll = nullptr, + const std::filesystem::path& mergedManifestPath = {}); + + Manifest CreateImpl( + std::vector& input, + bool fullValidation, + bool throwOnWarning, + bool isPartialManifest, + PCWSTR resourceDll, + const std::filesystem::path& mergedManifestPath); + + + } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h index 4adb9b94cd..1b7444f5d9 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h @@ -9,7 +9,7 @@ namespace AppInstaller::Manifest { struct ManifestYamlPopulator { - std::vector PopulateManifest(const YAML::Node& rootNode, Manifest& manifest); + static std::vector PopulateManifest(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, bool fullValidation); private: @@ -22,8 +22,7 @@ namespace AppInstaller::Manifest std::function(const YAML::Node&)> ProcessFunc; }; - // Installers node need to be processed after other fields in package root for default installer values - YAML::Node* m_p_installersNode; + bool m_fullValidation = false; std::vector RootFieldInfos; std::vector InstallerFieldInfos; @@ -32,18 +31,35 @@ namespace AppInstaller::Manifest std::vector PackageDependenciesFieldInfos; std::vector LocalizationFieldInfos; + // These pointers are referenced in the processing functions in manifest field process info table. AppInstaller::Manifest::Manifest* m_p_manifest = nullptr; AppInstaller::Manifest::ManifestInstaller* m_p_installer = nullptr; std::map* m_p_switches = nullptr; AppInstaller::Manifest::Dependency* m_p_dependency = nullptr; AppInstaller::Manifest::PackageDependency* m_p_packageDependency = nullptr; - AppInstaller::Manifest::Localization* m_p_localization = nullptr; - - void GetRootFieldProcessInfo(const ManifestVer& manifestVersion); - void GetInstallerFieldProcessInfo(const ManifestVer& manifestVersion); - void GetSwitchesFieldProcessInfo(const ManifestVer& manifestVersion); - void GetDependenciesFieldProcessInfo(const ManifestVer& manifestVersion); - void GetPackageDependenciesFieldProcessInfo(const ManifestVer& manifestVersion); - void GetLocalizationFieldProcessInfo(const ManifestVer& manifestVersion); + AppInstaller::Manifest::ManifestLocalization* m_p_localization = nullptr; + + // Cache of Installers node. This needs to be processed after other fields in package root for default installer values + YAML::Node* m_p_installersNode; + + std::vector GetRootFieldProcessInfo(const ManifestVer& manifestVersion); + std::vector GetInstallerFieldProcessInfo(const ManifestVer& manifestVersion, bool forRootFields = false); + std::vector GetSwitchesFieldProcessInfo(const ManifestVer& manifestVersion); + std::vector GetDependenciesFieldProcessInfo(const ManifestVer& manifestVersion); + std::vector GetPackageDependenciesFieldProcessInfo(const ManifestVer& manifestVersion); + std::vector GetLocalizationFieldProcessInfo(const ManifestVer& manifestVersion, bool forRootFields = false); + + // This method takes YAML root node and list of manifest field info. + // Yaml lib does not support case insensitive search and it allows duplicate keys. If duplicate keys exist, + // the value is undefined. So in this method, we will iterate through the node map and process each individual + // pair ourselves. This also helps with generating aggregated error rather than throwing on first failure. + std::vector ValidateAndProcessFields( + const YAML::Node& rootNode, + const std::vector& fieldInfos); + + std::vector ProcessLocalizationNode(const YAML::Node& rootNode, std::vector& localizations); + std::vector ProcessPackageDependenciesNode(const YAML::Node& rootNode, std::vector& packageDependencies); + + std::vector PopulateManifestInternal(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, bool fullValidation); }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/Yaml.h b/src/AppInstallerCommonCore/Public/winget/Yaml.h index a983406381..e7b847b0cb 100644 --- a/src/AppInstallerCommonCore/Public/winget/Yaml.h +++ b/src/AppInstallerCommonCore/Public/winget/Yaml.h @@ -140,6 +140,7 @@ namespace AppInstaller::YAML // The workers for the as function. std::string as_dispatch(std::string*) const; int64_t as_dispatch(int64_t*) const; + int as_dispatch(int*) const; bool as_dispatch(bool*) const; Type m_type; @@ -196,7 +197,7 @@ namespace AppInstaller::YAML std::string str(); // Gets the result of the emitter to out stream; can only be retrieved once. - void Get(std::ostream& out); + void Emit(std::ostream& out); private: // Appends the given node to the current container if applicable. diff --git a/src/AppInstallerCommonCore/Yaml.cpp b/src/AppInstallerCommonCore/Yaml.cpp index f4e5b9bbcf..64f6c4f38d 100644 --- a/src/AppInstallerCommonCore/Yaml.cpp +++ b/src/AppInstallerCommonCore/Yaml.cpp @@ -244,6 +244,11 @@ namespace AppInstaller::YAML return std::stoll(m_scalar); } + int Node::as_dispatch(int*) const + { + return std::stoi(m_scalar); + } + bool Node::as_dispatch(bool*) const { if (Utility::CaseInsensitiveEquals(m_scalar, "true")) @@ -418,7 +423,7 @@ namespace AppInstaller::YAML return stream.str(); } - void Emitter::Get(std::ostream& out) + void Emitter::Emit(std::ostream& out) { Wrapper::Emitter emitter(out); diff --git a/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp b/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp index 7eaf44b40c..f04335171e 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp @@ -5,16 +5,16 @@ namespace AppInstaller::Repository::Microsoft { - Registry::Key ARPHelper::GetARPKey(Manifest::ManifestInstaller::ScopeEnum scope, Utility::Architecture architecture) const + Registry::Key ARPHelper::GetARPKey(Manifest::ScopeEnum scope, Utility::Architecture architecture) const { HKEY rootKey = NULL; switch (scope) { - case Manifest::ManifestInstaller::ScopeEnum::User: + case Manifest::ScopeEnum::User: rootKey = HKEY_CURRENT_USER; break; - case Manifest::ManifestInstaller::ScopeEnum::Machine: + case Manifest::ScopeEnum::Machine: rootKey = HKEY_LOCAL_MACHINE; break; default: @@ -38,7 +38,7 @@ namespace AppInstaller::Repository::Microsoft switch (architecture) { case Utility::Architecture::X86: - if (scope == Manifest::ManifestInstaller::ScopeEnum::Machine) + if (scope == Manifest::ScopeEnum::Machine) { access |= KEY_WOW64_32KEY; isValid = true; @@ -62,7 +62,7 @@ namespace AppInstaller::Repository::Microsoft switch (architecture) { case Utility::Architecture::X86: - if (scope == Manifest::ManifestInstaller::ScopeEnum::Machine) + if (scope == Manifest::ScopeEnum::Machine) { #ifdef _ARM_ // Not accessible if this is an ARM process @@ -172,7 +172,7 @@ namespace AppInstaller::Repository::Microsoft } } - void ARPHelper::PopulateIndexFromARP(SQLiteIndex& index, Manifest::ManifestInstaller::ScopeEnum scope) const + void ARPHelper::PopulateIndexFromARP(SQLiteIndex& index, Manifest::ScopeEnum scope) const { for (auto architecture : Utility::GetApplicableArchitectures()) { @@ -297,11 +297,11 @@ namespace AppInstaller::Repository::Microsoft // Pick up WindowsInstaller to determine if this is an MSI install. // TODO: Could also determine Inno (and maybe other types) through detecting other keys here. - auto installedType = Manifest::ManifestInstaller::InstallerTypeEnum::Exe; + auto installedType = Manifest::InstallerTypeEnum::Exe; if (GetBoolValue(arpKey, WindowsInstaller)) { - installedType = Manifest::ManifestInstaller::InstallerTypeEnum::Msi; + installedType = Manifest::InstallerTypeEnum::Msi; } index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledType, Manifest::ManifestInstaller::InstallerTypeToString(installedType)); diff --git a/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.h b/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.h index 6074158902..f573146d72 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.h +++ b/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.h @@ -50,7 +50,7 @@ namespace AppInstaller::Repository::Microsoft // Gets the registry key associated with the given scope and architecture on this platform. // May return an empty key if there is no valid location (bad combination or not found). - Registry::Key GetARPKey(Manifest::ManifestInstaller::ScopeEnum scope, Utility::Architecture architecture) const; + Registry::Key GetARPKey(Manifest::ScopeEnum scope, Utility::Architecture architecture) const; // Returns true IFF the value exists and contains a non-zero DWORD. static bool GetBoolValue(const Registry::Key& arpKey, const std::wstring& name); @@ -67,7 +67,7 @@ namespace AppInstaller::Repository::Microsoft // Populates the index with the ARP entries from the given scope (machine/user). // Handles all of the architectures for the given scope. - void PopulateIndexFromARP(SQLiteIndex& index, Manifest::ManifestInstaller::ScopeEnum scope) const; + void PopulateIndexFromARP(SQLiteIndex& index, Manifest::ScopeEnum scope) const; // Populates the index with the ARP entries from the given key. // This entry point is primarily to allow unit tests to operate of arbitrary keys; diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp index 72c7588c18..4fb4ce9452 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp @@ -88,7 +88,7 @@ namespace AppInstaller::Repository::Microsoft auto manifestId = index.AddManifest(manifest, std::filesystem::path{ packageId.FamilyName().c_str() }); index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledType, - Manifest::ManifestInstaller::InstallerTypeToString(Manifest::ManifestInstaller::InstallerTypeEnum::Msix)); + Manifest::ManifestInstaller::InstallerTypeToString(Manifest::InstallerTypeEnum::Msix)); } } @@ -119,7 +119,7 @@ namespace AppInstaller::Repository::Microsoft arpHelper = ARPHelper(); } - arpHelper->PopulateIndexFromARP(index, Manifest::ManifestInstaller::ScopeEnum::Machine); + arpHelper->PopulateIndexFromARP(index, Manifest::ScopeEnum::Machine); } if (filter == PredefinedInstalledSourceFactory::Filter::None || filter == PredefinedInstalledSourceFactory::Filter::ARP_User) @@ -129,7 +129,7 @@ namespace AppInstaller::Repository::Microsoft arpHelper = ARPHelper(); } - arpHelper->PopulateIndexFromARP(index, Manifest::ManifestInstaller::ScopeEnum::User); + arpHelper->PopulateIndexFromARP(index, Manifest::ScopeEnum::User); } if (filter == PredefinedInstalledSourceFactory::Filter::None || filter == PredefinedInstalledSourceFactory::Filter::MSIX) From 571de43c385f6ab72e40f6d3e0c3139f5ebe8322 Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Sun, 31 Jan 2021 20:20:57 -0800 Subject: [PATCH 05/18] Working snapshot --- src/AppInstallerCLI.sln | 1 + src/AppInstallerCLI/AppInstallerCLI.vcxproj | 1 + .../Commands/InstallCommand.cpp | 1 - .../Commands/UpgradeCommand.cpp | 2 - .../Workflows/InstallFlow.cpp | 20 ++--- .../Workflows/InstallFlow.h | 6 -- .../Workflows/ManifestComparator.cpp | 80 +++++-------------- .../Workflows/ManifestComparator.h | 1 - .../ShellExecuteInstallerHandler.cpp | 3 +- .../Workflows/ShowFlow.cpp | 68 +++++++++++----- .../Workflows/UninstallFlow.cpp | 4 +- .../Workflows/UpdateFlow.cpp | 8 +- .../Workflows/WorkflowBase.cpp | 10 ++- src/AppInstallerCLICore/pch.h | 1 + .../AppInstallerCommonCore.vcxproj | 1 + .../AppInstallerCommonCore.vcxproj.filters | 3 + .../Manifest/Manifest.cpp | 50 ++++++++++++ .../Manifest/ManifestSchemaValidation.cpp | 8 +- .../Manifest/ManifestValidation.cpp | 28 ++----- .../Manifest/ManifestYamlPopulator.cpp | 2 +- .../Manifest/YamlParser.cpp | 5 +- .../Public/winget/Manifest.h | 6 ++ .../Public/winget/ManifestLocalization.h | 14 +++- .../Public/winget/ManifestSchemaValidation.h | 3 +- .../Public/winget/ManifestYamlParser.h | 1 + src/AppInstallerCommonCore/pch.h | 4 + .../Microsoft/ARPHelper.cpp | 15 ++-- .../PredefinedInstalledSourceFactory.cpp | 16 ++-- .../Microsoft/Schema/1_0/ChannelTable.h | 1 + .../Microsoft/Schema/1_0/Interface_1_0.cpp | 33 ++++---- .../Microsoft/Schema/1_0/NameTable.h | 1 + src/ManifestSchema/ManifestSchema.h | 2 +- 32 files changed, 222 insertions(+), 177 deletions(-) create mode 100644 src/AppInstallerCommonCore/Manifest/Manifest.cpp diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index 5e657e57ee..b53714424c 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -74,6 +74,7 @@ Global ManifestSchema\ManifestSchema.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 Valijson\Valijson.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 binver\binver.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4 + ManifestSchema\ManifestSchema.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4 binver\binver.vcxitems*{6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}*SharedItemsImports = 9 ManifestSchema\ManifestSchema.vcxitems*{7d05f64d-ce5a-42aa-a2c1-e91458f061cf}*SharedItemsImports = 9 catch2\catch2.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 diff --git a/src/AppInstallerCLI/AppInstallerCLI.vcxproj b/src/AppInstallerCLI/AppInstallerCLI.vcxproj index 43c670c710..bca2ba6d58 100644 --- a/src/AppInstallerCLI/AppInstallerCLI.vcxproj +++ b/src/AppInstallerCLI/AppInstallerCLI.vcxproj @@ -70,6 +70,7 @@ + diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp index 157f9676bc..fa65ae7553 100644 --- a/src/AppInstallerCLICore/Commands/InstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -99,7 +99,6 @@ namespace AppInstaller::CLI context << Workflow::ReportExecutionStage(ExecutionStage::Discovery) << Workflow::GetManifest << - Workflow::EnsureMinOSVersion << Workflow::SelectInstaller << Workflow::EnsureApplicableInstaller << Workflow::ShowInstallationDisclaimer << diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp index d2fcbd58bd..ddcdb84cde 100644 --- a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp @@ -155,7 +155,6 @@ namespace AppInstaller::CLI EnsureOneMatchFromSearchResult(true) << GetInstalledPackageVersion << EnsureUpdateVersionApplicable << - EnsureMinOSVersion << SelectInstaller << EnsureApplicableInstaller << ShowInstallationDisclaimer << @@ -181,7 +180,6 @@ namespace AppInstaller::CLI context << GetManifestFromPackage << EnsureUpdateVersionApplicable << - EnsureMinOSVersion << SelectInstaller << EnsureApplicableInstaller; } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index e013b2eb7a..9b3cf93807 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -18,18 +18,6 @@ namespace AppInstaller::CLI::Workflow using namespace AppInstaller::Manifest; using namespace AppInstaller::Repository; - void EnsureMinOSVersion(Execution::Context& context) - { - const auto& manifest = context.Get(); - - if (!manifest.MinOSVersion.empty() && - !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Version(manifest.MinOSVersion))) - { - context.Reporter.Error() << Resource::String::InstallationRequiresHigherWindows << ' ' << manifest.MinOSVersion << std::endl; - AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_OLD_WIN_VERSION)); - } - } - void EnsureApplicableInstaller(Execution::Context& context) { const auto& installer = context.Get(); @@ -195,7 +183,8 @@ namespace AppInstaller::CLI::Workflow bool overrideHashMismatch = context.Args.Contains(Execution::Args::Type::Force); const auto& manifest = context.Get(); - Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, manifest.Channel, hashPair.first, hashPair.second, overrideHashMismatch); + const auto& installer = context.Get().value(); + Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, installer.Channel, hashPair.first, hashPair.second, overrideHashMismatch); // If running as admin, do not allow the user to override the hash failure. if (Runtime::IsRunningAsAdmin()) @@ -279,7 +268,7 @@ namespace AppInstaller::CLI::Workflow case InstallerTypeEnum::Msi: case InstallerTypeEnum::Nullsoft: case InstallerTypeEnum::Wix: - if (isUpdate && installer.UpdateBehavior == ManifestInstaller::UpdateBehaviorEnum::UninstallPrevious) + if (isUpdate && installer.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious) { context << GetUninstallInfo << @@ -336,7 +325,8 @@ namespace AppInstaller::CLI::Workflow catch (const wil::ResultException& re) { const auto& manifest = context.Get(); - Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, "MSIX", re.GetErrorCode()); + const auto& installer = context.Get().value(); + Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, installer.Channel, "MSIX", re.GetErrorCode()); context.Reporter.Error() << GetUserPresentableMessage(re) << std::endl; AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h index 8e6351fecc..bc1ddab99d 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -11,12 +11,6 @@ namespace AppInstaller::CLI::Workflow static constexpr std::string_view ARG_TOKEN_LOGPATH = ""sv; static constexpr std::string_view ARG_TOKEN_INSTALLPATH = ""sv; - // Ensures that the current OS version is greater than or equal to the one in the manifest. - // Required Args: None - // Inputs: Manifest - // Outputs: None - void EnsureMinOSVersion(Execution::Context& context); - // Ensures that there is an applicable installer. // Required Args: None // Inputs: Installer diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp index e68624e2b1..4d8fd695aa 100644 --- a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp +++ b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp @@ -14,13 +14,20 @@ namespace AppInstaller::CLI::Workflow // Determine if the installer is applicable. bool IsInstallerApplicable(const Manifest::ManifestInstaller& installer, Manifest::InstallerTypeEnum installedType) { + // Check MinOSVersion + if (!installer.MinOSVersion.empty() && + !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version(installer.MinOSVersion))) + { + return false; + } + if (Utility::IsApplicableArchitecture(installer.Arch) == Utility::InapplicableArchitecture) { return false; } if (installedType != Manifest::InstallerTypeEnum::Unknown && - !Manifest::ManifestInstaller::IsInstallerTypeCompatible(installer.InstallerType, installedType)) + !Manifest::IsInstallerTypeCompatible(installer.InstallerType, installedType)) { return false; } @@ -35,6 +42,14 @@ namespace AppInstaller::CLI::Workflow const Manifest::ManifestInstaller& installer2, Manifest::InstallerTypeEnum installedType) { + bool isOSVersionSatisfied1 = installer1.MinOSVersion.empty() || Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version(installer1.MinOSVersion)); + bool isOSVersionSatisfied2 = installer2.MinOSVersion.empty() || Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version(installer2.MinOSVersion)); + + if (isOSVersionSatisfied1 && !isOSVersionSatisfied2) + { + return true; + } + auto arch1 = Utility::IsApplicableArchitecture(installer1.Arch); auto arch2 = Utility::IsApplicableArchitecture(installer2.Arch); @@ -52,8 +67,8 @@ namespace AppInstaller::CLI::Workflow { return true; } - if (Manifest::ManifestInstaller::IsInstallerTypeCompatible(installer1.InstallerType, installedType) && - !Manifest::ManifestInstaller::IsInstallerTypeCompatible(installer2.InstallerType, installedType)) + if (Manifest::IsInstallerTypeCompatible(installer1.InstallerType, installedType) && + !Manifest::IsInstallerTypeCompatible(installer2.InstallerType, installedType)) { return true; } @@ -67,28 +82,6 @@ namespace AppInstaller::CLI::Workflow return false; } - - // This is used in sorting the list of available localizations to get the best match. - struct LocalizationComparator - { - bool operator() ( - const Manifest::ManifestLocalization& loc1, - const Manifest::ManifestLocalization& loc2) - { - // Todo: Compare simple language for now. Need more work and spec. - std::string userPreferredLocale = std::locale("").name(); - - auto foundLoc1 = userPreferredLocale.find(loc1.Language); - auto foundLoc2 = userPreferredLocale.find(loc2.Language); - - if (foundLoc1 != std::string::npos && foundLoc2 == std::string::npos) - { - return true; - } - - return false; - } - }; } std::optional ManifestComparator::GetPreferredInstaller(const Manifest::Manifest& manifest) @@ -100,7 +93,7 @@ namespace AppInstaller::CLI::Workflow auto installerTypeItr = m_installationMetadata.find(Repository::PackageVersionMetadata::InstalledType); if (installerTypeItr != m_installationMetadata.end()) { - installedType = Manifest::ManifestInstaller::ConvertToInstallerTypeEnum(installerTypeItr->second); + installedType = Manifest::ConvertToInstallerTypeEnum(installerTypeItr->second); } const Manifest::ManifestInstaller* result = nullptr; @@ -128,39 +121,10 @@ namespace AppInstaller::CLI::Workflow Logging::Telemetry().LogSelectedInstaller( static_cast(result->Arch), result->Url, - Manifest::ManifestInstaller::InstallerTypeToString(result->InstallerType), - Manifest::ManifestInstaller::ScopeToString(result->Scope), - result->Language); + Manifest::InstallerTypeToString(result->InstallerType), + Manifest::ScopeToString(result->Scope), + result->Locale); return *result; } - - Manifest::ManifestLocalization ManifestComparator::GetPreferredLocalization(const Manifest::Manifest& manifest) - { - AICLI_LOG(CLI, Info, << "Starting localization selection."); - - ManifestLocalization selectedLocalization; - - // Sorting the list of available localizations according to rules defined in LocalizationComparator. - if (!manifest.Localization.empty()) - { - auto localization = manifest.Localization; - std::sort(localization.begin(), localization.end(), LocalizationComparator()); - - // TODO: needs to check language applicability here - - selectedLocalization = localization[0]; - } - else - { - // Populate default from package manifest - selectedLocalization.Description = manifest.Description; - selectedLocalization.Homepage = manifest.Homepage; - selectedLocalization.LicenseUrl = manifest.LicenseUrl; - } - - AICLI_LOG(CLI, Info, << "Completed localization selection. Selected localization language: " << selectedLocalization.Language); - - return selectedLocalization; - } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.h b/src/AppInstallerCLICore/Workflows/ManifestComparator.h index e4c896cf54..fa366f7380 100644 --- a/src/AppInstallerCLICore/Workflows/ManifestComparator.h +++ b/src/AppInstallerCLICore/Workflows/ManifestComparator.h @@ -15,7 +15,6 @@ namespace AppInstaller::CLI::Workflow m_installationMetadata(std::move(installationMetadata)) {} std::optional GetPreferredInstaller(const Manifest::Manifest& manifest); - Manifest::ManifestLocalization GetPreferredLocalization(const Manifest::Manifest& manifest); private: // TODO: Handle args to change how we select. diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp index 633ff52c03..c3e527628c 100644 --- a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp +++ b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp @@ -208,7 +208,8 @@ namespace AppInstaller::CLI::Workflow else if (installResult.value() != 0) { const auto& manifest = context.Get(); - Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, "ShellExecute", installResult.value()); + const auto& installer = context.Get(); + Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, installer->Channel, "ShellExecute", installResult.value()); context.Reporter.Error() << "Installer failed with exit code: " << installResult.value() << std::endl; // Show installer log path if exists diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp index 35976084af..0b4dc42bd5 100644 --- a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -15,44 +15,47 @@ namespace AppInstaller::CLI::Workflow const auto& manifest = context.Get(); const auto& installer = context.Get(); - ManifestComparator manifestComparator(context.Args); - auto selectedLocalization = manifestComparator.GetPreferredLocalization(manifest); - // TODO: Come up with a prettier format context.Reporter.Info() << "Version: " << manifest.Version << std::endl; - context.Reporter.Info() << "Publisher: " << manifest.Publisher << std::endl; - if (!manifest.Author.empty()) + context.Reporter.Info() << "Publisher: " << manifest.CurrentLocalization.Get() << std::endl; + auto author = manifest.CurrentLocalization.Get(); + if (!author.empty()) { - context.Reporter.Info() << "Author: " << manifest.Author << std::endl; + context.Reporter.Info() << "Author: " << author << std::endl; } - if (!manifest.AppMoniker.empty()) + if (!manifest.Moniker.empty()) { - context.Reporter.Info() << "AppMoniker: " << manifest.AppMoniker << std::endl; + context.Reporter.Info() << "Moniker: " << manifest.Moniker << std::endl; } - if (!selectedLocalization.Description.empty()) + auto description = manifest.CurrentLocalization.Get(); + if (description.empty()) { - context.Reporter.Info() << "Description: " << selectedLocalization.Description << std::endl; + // Fall back to short description + description = manifest.CurrentLocalization.Get(); } - if (!selectedLocalization.Homepage.empty()) + if (!description.empty()) { - context.Reporter.Info() << "Homepage: " << selectedLocalization.Homepage << std::endl; + context.Reporter.Info() << "Description: " << description << std::endl; } - if (!manifest.License.empty()) + auto homepage = manifest.CurrentLocalization.Get(); + if (!homepage.empty()) { - context.Reporter.Info() << "License: " << manifest.License << std::endl; + context.Reporter.Info() << "Homepage: " << homepage << std::endl; } - if (!selectedLocalization.LicenseUrl.empty()) + context.Reporter.Info() << "License: " << manifest.CurrentLocalization.Get() << std::endl; + auto licenseUrl = manifest.CurrentLocalization.Get(); + if (!licenseUrl.empty()) { - context.Reporter.Info() << "License Url: " << selectedLocalization.LicenseUrl << std::endl; + context.Reporter.Info() << "License Url: " << licenseUrl << std::endl; } context.Reporter.Info() << "Installer:" << std::endl; if (installer) { - context.Reporter.Info() << " Type: " << Manifest::ManifestInstaller::InstallerTypeToString(installer->InstallerType) << std::endl; - if (!installer->Language.empty()) + context.Reporter.Info() << " Type: " << Manifest::InstallerTypeToString(installer->InstallerType) << std::endl; + if (!installer->Locale.empty()) { - context.Reporter.Info() << " Language: " << installer->Language << std::endl; + context.Reporter.Info() << " Locale: " << installer->Locale << std::endl; } if (!installer->Url.empty()) { @@ -77,8 +80,33 @@ namespace AppInstaller::CLI::Workflow { const auto& manifest = context.Get(); + std::set channels; + for (const auto& installer : manifest.Installers) + { + if (!installer.Channel.empty()) + { + channels.insert(installer.Channel); + } + } + + std::string channelsStr; + bool firstEntry = true; + for (const auto& c : channels) + { + if (firstEntry) + { + firstEntry = false; + } + else + { + channelsStr += ", "; + } + + channelsStr += c; + } + Execution::TableOutput<2> table(context.Reporter, { Resource::String::ShowVersion, Resource::String::ShowChannel }); - table.OutputLine({ manifest.Version, manifest.Channel }); + table.OutputLine({ manifest.Version, channelsStr }); table.Complete(); } diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp index f998f586ef..b5fa852316 100644 --- a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp @@ -17,7 +17,7 @@ namespace AppInstaller::CLI::Workflow { auto installedPackageVersion = context.Get(); const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType]; - switch (ManifestInstaller::ConvertToInstallerTypeEnum(installedTypeString)) + switch (ConvertToInstallerTypeEnum(installedTypeString)) { case InstallerTypeEnum::Exe: case InstallerTypeEnum::Burn: @@ -82,7 +82,7 @@ namespace AppInstaller::CLI::Workflow void ExecuteUninstaller(Execution::Context& context) { const std::string installedTypeString = context.Get()->GetMetadata()[PackageVersionMetadata::InstalledType]; - switch (ManifestInstaller::ConvertToInstallerTypeEnum(installedTypeString)) + switch (ConvertToInstallerTypeEnum(installedTypeString)) { case InstallerTypeEnum::Exe: case InstallerTypeEnum::Burn: diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index 89951de070..5fe28b5245 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -37,13 +37,6 @@ namespace AppInstaller::CLI::Workflow auto packageVersion = package->GetAvailableVersion(key); auto manifest = packageVersion->GetManifest(); - // Check MinOSVersion - if (!manifest.MinOSVersion.empty() && - !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version(manifest.MinOSVersion))) - { - continue; - } - // Check applicable Installer auto installer = manifestComparator.GetPreferredInstaller(manifest); if (!installer.has_value()) @@ -52,6 +45,7 @@ namespace AppInstaller::CLI::Workflow } // Since we already did installer selection, just populate the context Data + manifest.ApplyLocale(); context.Add(std::move(manifest)); context.Add(std::move(packageVersion)); context.Add(std::move(installer)); diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 8018e1a044..48a98dcd8b 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -502,7 +502,8 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND); } - Logging::Telemetry().LogManifestFields(manifest->Id, manifest->Name, manifest->Version); + Logging::Telemetry().LogManifestFields(manifest->Id, manifest->DefaultLocalization.Get(), manifest->Version); + manifest->ApplyLocale(); context.Add(std::move(manifest.value())); context.Add(std::move(requestedVersion)); } @@ -533,7 +534,8 @@ namespace AppInstaller::CLI::Workflow [](Execution::Context& context) { Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::Manifest))); - Logging::Telemetry().LogManifestFields(manifest.Id, manifest.Name, manifest.Version); + Logging::Telemetry().LogManifestFields(manifest.Id, manifest.DefaultLocalization.Get(), manifest.Version); + manifest.ApplyLocale(); context.Add(std::move(manifest)); }; } @@ -547,7 +549,7 @@ namespace AppInstaller::CLI::Workflow void ReportManifestIdentity(Execution::Context& context) { const auto& manifest = context.Get(); - ReportIdentity(context, manifest.Name, manifest.Id); + ReportIdentity(context, manifest.CurrentLocalization.Get(), manifest.Id); } void GetManifest(Execution::Context& context) @@ -637,7 +639,7 @@ namespace AppInstaller::CLI::Workflow SearchRequest searchRequest; searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id)); // In case there're same Ids from different sources, filter the result using package name - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Name, MatchType::CaseInsensitive, manifest.Name)); + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Name, MatchType::CaseInsensitive, manifest.DefaultLocalization.Get())); context.Add(source->Search(searchRequest)); } diff --git a/src/AppInstallerCLICore/pch.h b/src/AppInstallerCLICore/pch.h index 73ed67aec1..ef55d0a8c8 100644 --- a/src/AppInstallerCLICore/pch.h +++ b/src/AppInstallerCLICore/pch.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj index 374c72d514..8c62a66f5e 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -309,6 +309,7 @@ true + diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters index 320c29b6a4..373c0e0774 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -248,6 +248,9 @@ Manifest + + Manifest + diff --git a/src/AppInstallerCommonCore/Manifest/Manifest.cpp b/src/AppInstallerCommonCore/Manifest/Manifest.cpp new file mode 100644 index 0000000000..de21be7f38 --- /dev/null +++ b/src/AppInstallerCommonCore/Manifest/Manifest.cpp @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/Manifest.h" + +namespace AppInstaller::Manifest +{ + void Manifest::ApplyLocale(const std::string&) + { + // TODO: need more work in locale processing + CurrentLocalization = DefaultLocalization; + } + + std::vector Manifest::GetAggregatedTags() const + { + std::vector resultTags = DefaultLocalization.Get(); + + for (const auto& locale : Localizations) + { + auto tags = locale.Get(); + for (const auto& tag : tags) + { + if (std::find(resultTags.begin(), resultTags.end(), tag) != resultTags.end()) + { + resultTags.emplace_back(tag); + } + } + } + + return resultTags; + } + + std::vector Manifest::GetAggregatedCommands() const + { + std::vector resultCommands; + + for (const auto& installer : Installers) + { + for (const auto& command : installer.Commands) + { + if (std::find(resultCommands.begin(), resultCommands.end(), command) != resultCommands.end()) + { + resultCommands.emplace_back(command); + } + } + } + + return resultCommands; + } +} \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp index 64f1d3e29a..06873335f5 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -4,7 +4,6 @@ #include "winget/Yaml.h" #include "winget/ManifestCommon.h" #include "winget/ManifestSchemaValidation.h" -#include "winget/ManifestValidation.h" #include "winget/ManifestYamlParser.h" #include @@ -163,12 +162,13 @@ namespace AppInstaller::Manifest::YamlParser { if (schemaList.find(entry.ManifestType) == schemaList.end()) { - valijson::Schema newSchema; + // Copy constructor of valijson::Schema was private + valijson::Schema& newSchema = schemaList.emplace( + std::piecewise_construct, std::make_tuple(entry.ManifestType), std::make_tuple()).first->second; valijson::SchemaParser schemaParser; Json::Value schemaJson = LoadSchemaDoc(manifestVersion, entry.ManifestType, resourceModuleName); valijson::adapters::JsonCppAdapter jsonSchemaAdapter(schemaJson); schemaParser.populateSchema(jsonSchemaAdapter, newSchema); - schemaList.emplace(entry.ManifestType, std::move(newSchema)); } const auto& schema = schemaList.find(entry.ManifestType)->second; @@ -197,5 +197,7 @@ namespace AppInstaller::Manifest::YamlParser errors.emplace_back(ValidationError::MessageWithFile(ss.str(), entry.FileName)); } } + + return errors; } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp index 108ba56546..eb0edccf15 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp @@ -9,12 +9,6 @@ namespace AppInstaller::Manifest { std::vector resultErrors; - // Channel is not supported currently - if (!manifest.Channel.empty()) - { - resultErrors.emplace_back(ManifestError::FieldNotSupported, "Channel", manifest.Channel); - } - try { // Version value should be successfully parsed @@ -25,12 +19,6 @@ namespace AppInstaller::Manifest resultErrors.emplace_back(ManifestError::InvalidFieldValue, "Version", manifest.Version); } - // License field is required - if (manifest.License.empty()) - { - resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "License"); - } - // Comparison function to check duplicate installer entry. {installerType, arch, language and scope} combination is the key. // Todo: use the comparator from ManifestComparator when that one is fully implemented. auto installerCmp = [](const ManifestInstaller& in1, const ManifestInstaller& in2) @@ -45,9 +33,9 @@ namespace AppInstaller::Manifest return in1.Arch < in2.Arch; } - if (in1.Language != in2.Language) + if (in1.Locale != in2.Locale) { - return in1.Language < in2.Language; + return in1.Locale < in2.Locale; } if (in1.Scope != in2.Scope) @@ -80,20 +68,20 @@ namespace AppInstaller::Manifest resultErrors.emplace_back(ManifestError::InvalidFieldValue, "InstallerType"); } - if (installer.UpdateBehavior == ManifestInstaller::UpdateBehaviorEnum::Unknown) + if (installer.UpdateBehavior == UpdateBehaviorEnum::Unknown) { resultErrors.emplace_back(ManifestError::InvalidFieldValue, "UpdateBehavior"); } // Validate system reference strings if they are set at the installer level - if (!installer.PackageFamilyName.empty() && !ManifestInstaller::DoesInstallerTypeUsePackageFamilyName(installer.InstallerType)) + if (!installer.PackageFamilyName.empty() && !DoesInstallerTypeUsePackageFamilyName(installer.InstallerType)) { - resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportPackageFamilyName, "InstallerType", ManifestInstaller::InstallerTypeToString(installer.InstallerType)); + resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportPackageFamilyName, "InstallerType", InstallerTypeToString(installer.InstallerType)); } - if (!installer.ProductCode.empty() && !ManifestInstaller::DoesInstallerTypeUseProductCode(installer.InstallerType)) + if (!installer.ProductCode.empty() && !DoesInstallerTypeUseProductCode(installer.InstallerType)) { - resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportProductCode, "InstallerType", ManifestInstaller::InstallerTypeToString(installer.InstallerType)); + resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportProductCode, "InstallerType", InstallerTypeToString(installer.InstallerType)); } if (installer.InstallerType == InstallerTypeEnum::MSStore) @@ -101,7 +89,7 @@ namespace AppInstaller::Manifest // MSStore type is not supported in community repo resultErrors.emplace_back( ManifestError::FieldValueNotSupported, "InstallerType", - ManifestInstaller::InstallerTypeToString(installer.InstallerType)); + InstallerTypeToString(installer.InstallerType)); if (installer.ProductId.empty()) { diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index e6534c35ac..6451a515df 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -526,6 +526,6 @@ namespace AppInstaller::Manifest ValidationErrors ManifestYamlPopulator::PopulateManifest(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, bool fullValidation) { ManifestYamlPopulator manifestPopulator; - return manifestPopulator.PopulateManifest(rootNode, manifest, manifestVersion, fullValidation); + return manifestPopulator.PopulateManifestInternal(rootNode, manifest, manifestVersion, fullValidation); } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp index ac03c52800..85110c6175 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp @@ -3,9 +3,9 @@ #include "pch.h" #include "AppInstallerSHA256.h" #include "winget/Yaml.h" -#include "winget/ManifestYamlParser.h" #include "winget/ManifestSchemaValidation.h" #include "winget/ManifestYamlPopulator.h" +#include "winget/ManifestYamlParser.h" namespace AppInstaller::Manifest::YamlParser { @@ -186,7 +186,7 @@ namespace AppInstaller::Manifest::YamlParser { // We'll do case insensitive search first and validate correct case later. auto iter = std::find_if(input.begin(), input.end(), - [](auto const& s) + [=](auto const& s) { return s.ManifestType == manifestType; }); @@ -306,6 +306,7 @@ namespace AppInstaller::Manifest::YamlParser PCWSTR resourceDll, const std::filesystem::path& mergedManifestPath) { + THROW_HR_IF_MSG(E_INVALIDARG, input.size() == 0, "No manifest file found"); THROW_HR_IF_MSG(E_INVALIDARG, isPartialManifest && !mergedManifestPath.empty(), "Manifest cannot be merged from partial manifest"); THROW_HR_IF_MSG(E_INVALIDARG, input.size() == 1 && !mergedManifestPath.empty(), "Manifest cannot be merged from a single manifest"); diff --git a/src/AppInstallerCommonCore/Public/winget/Manifest.h b/src/AppInstallerCommonCore/Public/winget/Manifest.h index c9d28b60b1..1a6c024143 100644 --- a/src/AppInstallerCommonCore/Public/winget/Manifest.h +++ b/src/AppInstallerCommonCore/Public/winget/Manifest.h @@ -31,5 +31,11 @@ namespace AppInstaller::Manifest std::vector Localizations; ManifestLocalization CurrentLocalization; + + // If locale is empty, user setting locale will be used + void ApplyLocale(const std::string& locale = {}); + + std::vector GetAggregatedTags() const; + std::vector GetAggregatedCommands() const; }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h index 7efb12396a..2a4de81473 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h @@ -151,13 +151,19 @@ namespace AppInstaller::Manifest // Return a value indicating whether the given data type is stored in the context. bool Contains(Localization l) { return (m_data.find(l) != m_data.end()); } - // Gets context data; which can be modified in place. + // Gets context data template - typename details::LocalizationMapping::value_t& Get() + typename details::LocalizationMapping::value_t Get() const { auto itr = m_data.find(L); - THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), itr == m_data.end(), "Get(%d)", L); - return std::get(itr->second); + if (itr == m_data.end()) + { + return {}; + } + else + { + return std::get(itr->second); + } } private: diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h index 7d7e0d1a72..51aeb61314 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once #include "ManifestCommon.h" +#include "ManifestValidation.h" #include @@ -15,7 +16,7 @@ namespace AppInstaller::Manifest::YamlParser Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType, PCWSTR resourceModuleName); std::vector ValidateAgainstSchema( - const std::vector manifestList, + const std::vector& manifestList, const ManifestVer& manifestVersion, PCWSTR resourceModuleName); } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h index 04febb553b..01caf74086 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h @@ -3,6 +3,7 @@ #pragma once #include #include +#include #include diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h index a8eb1e9d75..97d66c7f8f 100644 --- a/src/AppInstallerCommonCore/pch.h +++ b/src/AppInstallerCommonCore/pch.h @@ -18,10 +18,14 @@ #include +#include +#pragma warning( push ) +#pragma warning ( disable : 4458 4100 ) #include #include #include #include +#pragma warning( pop ) #include #include diff --git a/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp b/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp index f04335171e..a4f0d1397f 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp @@ -180,7 +180,7 @@ namespace AppInstaller::Repository::Microsoft if (arpRootKey) { - PopulateIndexFromKey(index, arpRootKey, Manifest::ManifestInstaller::ScopeToString(scope), Utility::ToString(architecture)); + PopulateIndexFromKey(index, arpRootKey, Manifest::ScopeToString(scope), Utility::ToString(architecture)); } } } @@ -194,7 +194,7 @@ namespace AppInstaller::Repository::Microsoft std::string productCode = arpEntry.Name(); Manifest::Manifest manifest; - manifest.Tags = { "ARP" }; + manifest.DefaultLocalization.Add({ "ARP" }); // Use the key name as the Id, as it is supposed to be unique. // TODO: We probably want something better here, like constructing the value as @@ -226,8 +226,9 @@ namespace AppInstaller::Repository::Microsoft AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because DisplayName is not a REG_SZ value"); continue; } - manifest.Name = displayName->GetValue(); - if (manifest.Name.empty()) + auto displayNameValue = displayName->GetValue(); + manifest.DefaultLocalization.Add(displayNameValue); + if (displayNameValue.empty()) { AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because DisplayName is empty"); continue; @@ -244,7 +245,7 @@ namespace AppInstaller::Repository::Microsoft auto publisher = arpKey[Publisher]; if (publisher && publisher->GetType() == Registry::Value::Type::String) { - manifest.Publisher = publisher->GetValue(); + manifest.DefaultLocalization.Add(publisher->GetValue()); } // TODO: If we want to keep the constructed manifest around to allow for `show` type commands @@ -274,7 +275,7 @@ namespace AppInstaller::Repository::Microsoft if (!manifestIdOpt) { - Logging::Telemetry().LogDuplicateARPEntry(addHr, scope, architecture, productCode, manifest.Name); + Logging::Telemetry().LogDuplicateARPEntry(addHr, scope, architecture, productCode, manifest.DefaultLocalization.Get()); continue; } @@ -304,7 +305,7 @@ namespace AppInstaller::Repository::Microsoft installedType = Manifest::InstallerTypeEnum::Msi; } - index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledType, Manifest::ManifestInstaller::InstallerTypeToString(installedType)); + index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledType, Manifest::InstallerTypeToString(installedType)); } } } diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp index 4fb4ce9452..fba7b6e097 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp @@ -33,7 +33,7 @@ namespace AppInstaller::Repository::Microsoft // Add one installer for storing the package family name. manifest.Installers.emplace_back(); // Every package will have the same tags currently. - manifest.Tags = { "msix" }; + manifest.DefaultLocalization.Add({ "msix" }); // Fields in the index but not populated: // AppMoniker - Not sure what we would put. @@ -55,11 +55,17 @@ namespace AppInstaller::Repository::Microsoft manifest.Id = familyName; + bool isPackageNameSet = false; // Attempt to get the DisplayName. Since this will retrieve the localized value, it has a chance to fail. // Rather than completely skip this package in that case, we will simply fall back to using the package name below. try { - manifest.Name = Utility::ConvertToUTF8(package.DisplayName()); + auto displayName = Utility::ConvertToUTF8(package.DisplayName()); + if (!displayName.empty()) + { + manifest.DefaultLocalization.Add(displayName); + isPackageNameSet = true; + } } catch (const winrt::hresult_error& hre) { @@ -71,9 +77,9 @@ namespace AppInstaller::Repository::Microsoft AICLI_LOG(Repo, Info, << "Unknown exception thrown when getting DisplayName for " << familyName); } - if (manifest.Name.empty()) + if (!isPackageNameSet) { - manifest.Name = Utility::ConvertToUTF8(packageId.Name()); + manifest.DefaultLocalization.Add(Utility::ConvertToUTF8(packageId.Name())); } std::ostringstream strstr; @@ -88,7 +94,7 @@ namespace AppInstaller::Repository::Microsoft auto manifestId = index.AddManifest(manifest, std::filesystem::path{ packageId.FamilyName().c_str() }); index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledType, - Manifest::ManifestInstaller::InstallerTypeToString(Manifest::InstallerTypeEnum::Msix)); + Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msix)); } } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ChannelTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ChannelTable.h index e3b59f5e52..41dc6e18a4 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ChannelTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ChannelTable.h @@ -18,5 +18,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 } // The table for Channel. + // TODO: Currently only indexing channel from first installer, might need to be OneToMany table using ChannelTable = OneToOneTable; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp index 8bcf1ec77b..a0b6276e42 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp @@ -40,10 +40,10 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 return {}; } - std::optional channelId = ChannelTable::SelectIdByValue(connection, manifest.Channel, true); + std::optional channelId = ChannelTable::SelectIdByValue(connection, manifest.Installers[0].Channel, true); if (!channelId) { - AICLI_LOG(Repo, Info, << "Did not find a Channel { " << manifest.Channel << " }"); + AICLI_LOG(Repo, Info, << "Did not find a Channel { " << manifest.Installers[0].Channel << " }"); return {}; } @@ -51,7 +51,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 if (!result) { - AICLI_LOG(Repo, Info, << "Did not find a manifest row for { " << manifest.Id << ", " << manifest.Version << ", " << manifest.Channel << " }"); + AICLI_LOG(Repo, Info, << "Did not find a manifest row for { " << manifest.Id << ", " << manifest.Version << ", " << manifest.Installers[0].Channel << " }"); } return result; @@ -185,10 +185,10 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 // Ensure that all of the 1:1 data exists. SQLite::rowid_t idId = IdTable::EnsureExists(connection, manifest.Id, true); - SQLite::rowid_t nameId = NameTable::EnsureExists(connection, manifest.Name); - SQLite::rowid_t monikerId = MonikerTable::EnsureExists(connection, manifest.AppMoniker); + SQLite::rowid_t nameId = NameTable::EnsureExists(connection, manifest.DefaultLocalization.Get()); + SQLite::rowid_t monikerId = MonikerTable::EnsureExists(connection, manifest.Moniker); SQLite::rowid_t versionId = VersionTable::EnsureExists(connection, manifest.Version); - SQLite::rowid_t channelId = ChannelTable::EnsureExists(connection, manifest.Channel); + SQLite::rowid_t channelId = ChannelTable::EnsureExists(connection, manifest.Installers[0].Channel); // Insert the manifest entry. SQLite::rowid_t manifestId = ManifestTable::Insert(connection, { @@ -201,8 +201,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 }); // Add all of the 1:N data. - TagsTable::EnsureExistsAndInsert(connection, manifest.Tags, manifestId); - CommandsTable::EnsureExistsAndInsert(connection, manifest.Commands, manifestId); + TagsTable::EnsureExistsAndInsert(connection, manifest.GetAggregatedTags(), manifestId); + CommandsTable::EnsureExistsAndInsert(connection, manifest.GetAggregatedCommands(), manifestId); savepoint.Commit(); @@ -237,21 +237,22 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 indexModified = true; } - if (channelInIndex != manifest.Channel) + if (channelInIndex != manifest.Installers[0].Channel) { - UpdateManifestValueById(connection, manifest.Channel, manifestId); + UpdateManifestValueById(connection, manifest.Installers[0].Channel, manifestId); indexModified = true; } - if (nameInIndex != manifest.Name) + auto packageName = manifest.DefaultLocalization.Get(); + if (nameInIndex != packageName) { - UpdateManifestValueById(connection, manifest.Name, manifestId); + UpdateManifestValueById(connection, packageName, manifestId); indexModified = true; } - if (monikerInIndex != manifest.AppMoniker) + if (monikerInIndex != manifest.Moniker) { - UpdateManifestValueById(connection, manifest.AppMoniker, manifestId); + UpdateManifestValueById(connection, manifest.Moniker, manifestId); indexModified = true; } @@ -273,8 +274,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 } // Update all 1:N tables as necessary - indexModified = TagsTable::UpdateIfNeededByManifestId(connection, manifest.Tags, manifestId) || indexModified; - indexModified = CommandsTable::UpdateIfNeededByManifestId(connection, manifest.Commands, manifestId) || indexModified; + indexModified = TagsTable::UpdateIfNeededByManifestId(connection, manifest.GetAggregatedTags(), manifestId) || indexModified; + indexModified = CommandsTable::UpdateIfNeededByManifestId(connection, manifest.GetAggregatedCommands(), manifestId) || indexModified; savepoint.Commit(); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/NameTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/NameTable.h index 7011826274..571ceb331c 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/NameTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/NameTable.h @@ -18,5 +18,6 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 } // The table for Name. + // TODO: Currently only indexing name from default locale, might need to be OneToMany table using NameTable = OneToOneTable; } diff --git a/src/ManifestSchema/ManifestSchema.h b/src/ManifestSchema/ManifestSchema.h index 7151b481f3..136661d352 100644 --- a/src/ManifestSchema/ManifestSchema.h +++ b/src/ManifestSchema/ManifestSchema.h @@ -9,4 +9,4 @@ #define IDX_MANIFEST_SCHEMA_V1_VERSION 203 #define IDX_MANIFEST_SCHEMA_V1_INSTALLER 204 #define IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE 205 -#define IDX_MANIFEST_SCHEMA_V1_LOCALE 206 \ No newline at end of file +#define IDX_MANIFEST_SCHEMA_V1_LOCALE 206 From 9bcd11787fbe4c1b48df2dddeb32cd41addfed77 Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Mon, 1 Feb 2021 06:10:22 -0800 Subject: [PATCH 06/18] fix --- src/ManifestSchema/schema/schema_v0.1.0.json | 13 ++++++------- .../schema/schema_v1.0.0_defaultLocale.json | 2 ++ .../schema/schema_v1.0.0_installer.json | 4 +++- src/ManifestSchema/schema/schema_v1.0.0_locale.json | 4 +++- .../schema/schema_v1.0.0_singleton.json | 2 ++ .../schema/schema_v1.0.0_version.json | 2 ++ 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/ManifestSchema/schema/schema_v0.1.0.json b/src/ManifestSchema/schema/schema_v0.1.0.json index b7e4d2b395..ce86a6ba04 100644 --- a/src/ManifestSchema/schema/schema_v0.1.0.json +++ b/src/ManifestSchema/schema/schema_v0.1.0.json @@ -233,7 +233,7 @@ "License": { "type": "string", "minLength": 1, - "maxLength": 40, + "maxLength": 1000, "description": "License is a required field. License provides the type of license the package is provided under" }, "MinOSVersion": { @@ -244,25 +244,25 @@ "Tags": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 40, + "maxLength": 100, "description": "Tags is a comma separated list. They represent strings that user may use to search for the package" }, "Commands": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 40, + "maxLength": 100, "description": "Commands is a comma separated list. They are the common executable or alias that user might type trying to run the package" }, "Protocols": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 40, + "maxLength": 100, "description": "Protocols is a comma separated list. Protocols provides the list of protocols the package provides a handler for" }, "FileExtensions": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 40, + "maxLength": 100, "description": "FileExtensions is a comma separated list. FileExtensions provides the list of extensions the package can support" }, "InstallerType": { @@ -310,7 +310,6 @@ "Version", "Publisher", "License", - "Installers", - "ManifestVersion" + "Installers" ] } \ No newline at end of file diff --git a/src/ManifestSchema/schema/schema_v1.0.0_defaultLocale.json b/src/ManifestSchema/schema/schema_v1.0.0_defaultLocale.json index 9e4cad7dc8..df2945fdee 100644 --- a/src/ManifestSchema/schema/schema_v1.0.0_defaultLocale.json +++ b/src/ManifestSchema/schema/schema_v1.0.0_defaultLocale.json @@ -119,11 +119,13 @@ }, "ManifestType": { "type": "string", + "default": "defaultLocale", "const": "defaultLocale", "description": "The manifest type" }, "ManifestVersion": { "type": "string", + "default": "1.0.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } diff --git a/src/ManifestSchema/schema/schema_v1.0.0_installer.json b/src/ManifestSchema/schema/schema_v1.0.0_installer.json index 7a830a1307..83c7b898e1 100644 --- a/src/ManifestSchema/schema/schema_v1.0.0_installer.json +++ b/src/ManifestSchema/schema/schema_v1.0.0_installer.json @@ -420,11 +420,13 @@ }, "ManifestType": { "type": "string", - "const": "singleton", + "default": "installer", + "const": "installer", "description": "The manifest type" }, "ManifestVersion": { "type": "string", + "default": "1.0.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } diff --git a/src/ManifestSchema/schema/schema_v1.0.0_locale.json b/src/ManifestSchema/schema/schema_v1.0.0_locale.json index b6c445bf09..60a505d03d 100644 --- a/src/ManifestSchema/schema/schema_v1.0.0_locale.json +++ b/src/ManifestSchema/schema/schema_v1.0.0_locale.json @@ -119,11 +119,13 @@ }, "ManifestType": { "type": "string", - "const": "defaultLocale", + "default": "locale", + "const": "locale", "description": "The manifest type" }, "ManifestVersion": { "type": "string", + "default": "1.0.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } diff --git a/src/ManifestSchema/schema/schema_v1.0.0_singleton.json b/src/ManifestSchema/schema/schema_v1.0.0_singleton.json index 891a7f2b2a..d72827decb 100644 --- a/src/ManifestSchema/schema/schema_v1.0.0_singleton.json +++ b/src/ManifestSchema/schema/schema_v1.0.0_singleton.json @@ -517,11 +517,13 @@ }, "ManifestType": { "type": "string", + "default": "singleton", "const": "singleton", "description": "The manifest type" }, "ManifestVersion": { "type": "string", + "default": "1.0.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } diff --git a/src/ManifestSchema/schema/schema_v1.0.0_version.json b/src/ManifestSchema/schema/schema_v1.0.0_version.json index 70fdcde64b..cfb4a89c2a 100644 --- a/src/ManifestSchema/schema/schema_v1.0.0_version.json +++ b/src/ManifestSchema/schema/schema_v1.0.0_version.json @@ -24,11 +24,13 @@ }, "ManifestType": { "type": "string", + "default": "version", "const": "version", "description": "The manifest type" }, "ManifestVersion": { "type": "string", + "default": "1.0.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } From 7550362b0e56180fafd9bc264bc31670c6441aab Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Mon, 1 Feb 2021 12:51:21 -0800 Subject: [PATCH 07/18] Preview compatibility fix --- src/ManifestSchema/schema/schema_v0.1.0.json | 36 ++++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ManifestSchema/schema/schema_v0.1.0.json b/src/ManifestSchema/schema/schema_v0.1.0.json index ce86a6ba04..3815f16401 100644 --- a/src/ManifestSchema/schema/schema_v0.1.0.json +++ b/src/ManifestSchema/schema/schema_v0.1.0.json @@ -46,54 +46,54 @@ "description": "LicenseUrl provides a link to the license for the user to read" }, "InstallerSwitches": { - "type": "object", + "type": [ "object", "null" ], "properties": { "Custom": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 128, + "maxLength": 1000, "description": "Custom switches will be passed directly to the installer by winget" }, "Silent": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 40, + "maxLength": 100, "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" }, "SilentWithProgress": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 40, + "maxLength": 100, "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" }, "Interactive": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 40, + "maxLength": 100, "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" }, "Language": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 40, + "maxLength": 100, "description": "Some installers include all localized resources. By specifying a Language switch, winget will pass the value of Language to the installer. This is not yet supported in Preview releases" }, "Log": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 40, + "maxLength": 100, "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" }, "InstallLocation": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 40, + "maxLength": 100, "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" }, "Update": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 40, + "maxLength": 100, "description": "Update is the value that should be passed to the installer when user chooses an upgrade" } } @@ -125,7 +125,7 @@ }, "Language": { "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "minLength": 2, "maxLength": 20, "description": "Language is the specific language of the installer. Language must follow IETF language tag guidelines" }, @@ -161,7 +161,7 @@ "properties": { "Language": { "type": "string", - "pattern": "^([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "minLength": 2, "maxLength": 20, "description": "Language is the specific language of the localization. Language must follow IETF language tag guidelines" }, @@ -214,8 +214,8 @@ }, "AppMoniker": { "type": [ "string", "null" ], - "pattern": "^\\S+$", - "maxLength": 40, + "minLength": 1, + "maxLength": 100, "description": "AppMoniker is the common name someone may use to search for the package" }, "Channel": { @@ -227,7 +227,7 @@ "Author": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 40, + "maxLength": 100, "description": "The person or company responsible for authoring the package" }, "License": { @@ -244,25 +244,25 @@ "Tags": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 100, + "maxLength": 1000, "description": "Tags is a comma separated list. They represent strings that user may use to search for the package" }, "Commands": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 100, + "maxLength": 1000, "description": "Commands is a comma separated list. They are the common executable or alias that user might type trying to run the package" }, "Protocols": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 100, + "maxLength": 1000, "description": "Protocols is a comma separated list. Protocols provides the list of protocols the package provides a handler for" }, "FileExtensions": { "type": [ "string", "null" ], "minLength": 1, - "maxLength": 100, + "maxLength": 1000, "description": "FileExtensions is a comma separated list. FileExtensions provides the list of extensions the package can support" }, "InstallerType": { From 28e21bff6346df56574b24080c6b86c5ff11993e Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Mon, 1 Feb 2021 14:22:00 -0800 Subject: [PATCH 08/18] V2 compatibility fix --- .../Commands/ValidateCommand.cpp | 2 +- .../Workflows/WorkflowBase.cpp | 13 ++++++- .../Workflows/WorkflowBase.h | 16 ++++++++- .../Shared/Strings/en-us/winget.resw | 2 +- .../Manifest/ManifestSchemaValidation.cpp | 1 + .../Manifest/ManifestYamlPopulator.cpp | 20 +++++++++-- .../Manifest/YamlParser.cpp | 34 +++++++++++-------- .../Public/winget/ManifestYamlParser.h | 17 ++++------ src/AppInstallerCommonCore/Yaml.cpp | 2 +- .../schema/schema_v1.0.0_installer.json | 24 +++++++++---- .../schema/schema_v1.0.0_singleton.json | 25 ++++++++++---- 11 files changed, 112 insertions(+), 44 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/ValidateCommand.cpp b/src/AppInstallerCLICore/Commands/ValidateCommand.cpp index 39b27e64ca..8955e13de2 100644 --- a/src/AppInstallerCLICore/Commands/ValidateCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ValidateCommand.cpp @@ -34,7 +34,7 @@ namespace AppInstaller::CLI void ValidateCommand::ExecuteInternal(Execution::Context& context) const { context << - Workflow::VerifyFile(Execution::Args::Type::ValidateManifest) << + Workflow::VerifyPath(Execution::Args::Type::ValidateManifest) << [](Execution::Context& context) { auto inputFile = Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::ValidateManifest)); diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 48a98dcd8b..e68a3bddb3 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -525,12 +525,23 @@ namespace AppInstaller::CLI::Workflow } } + void VerifyPath::operator()(Execution::Context& context) const + { + std::filesystem::path path = Utility::ConvertToUTF16(context.Args.GetArg(m_arg)); + + if (!std::filesystem::exists(path)) + { + context.Reporter.Error() << Resource::String::VerifyFileFailedNotExist << ' ' << path.u8string() << std::endl; + AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)); + } + } + void GetManifestFromArg(Execution::Context& context) { Logging::Telemetry().LogIsManifestLocal(true); context << - VerifyFile(Execution::Args::Type::Manifest) << + VerifyPath(Execution::Args::Type::Manifest) << [](Execution::Context& context) { Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::Manifest))); diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h index 8f457c2132..e6508de072 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.h +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -190,7 +190,7 @@ namespace AppInstaller::CLI::Workflow // Outputs: Manifest, PackageVersion void GetManifestFromPackage(Execution::Context& context); - // Ensures the the file exists and is not a directory. + // Ensures the file exists and is not a directory. // Required Args: the one given // Inputs: None // Outputs: None @@ -204,6 +204,20 @@ namespace AppInstaller::CLI::Workflow Execution::Args::Type m_arg; }; + // Ensures the path exists. + // Required Args: the one given + // Inputs: None + // Outputs: None + struct VerifyPath : public WorkflowTask + { + VerifyPath(Execution::Args::Type arg) : WorkflowTask("VerifyPath"), m_arg(arg) {} + + void operator()(Execution::Context& context) const override; + + private: + Execution::Args::Type m_arg; + }; + // Opens the manifest file provided on the command line. // Required Args: Manifest // Inputs: None diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 456fae16e0..e8d2fc8bcb 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -713,7 +713,7 @@ They can be configured through the settings file 'winget settings'. Path is a directory: - File does not exist: + Path does not exist: Both local manifest and search query args are provided diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp index 06873335f5..5730abc51a 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -131,6 +131,7 @@ namespace AppInstaller::Manifest::YamlParser break; case AppInstaller::Manifest::ManifestTypeEnum::Locale: schemaStr = LoadResourceAsString(resourceModuleName, MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_LOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + break; default: THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index 6451a515df..95fe09314c 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -488,22 +488,38 @@ namespace AppInstaller::Manifest PackageDependenciesFieldInfos = GetPackageDependenciesFieldProcessInfo(manifestVersion); LocalizationFieldInfos = GetLocalizationFieldProcessInfo(manifestVersion); + // Populate root YAML::Node installersNode; m_p_installersNode = &installersNode; m_p_manifest = &manifest; m_p_installer = &(manifest.DefaultInstallerInfo); m_p_localization = &(manifest.DefaultLocalization); - - // Populate root resultErrors = ValidateAndProcessFields(rootNode, RootFieldInfos); // Populate installers for (auto const& entry : installersNode.Sequence()) { ManifestInstaller installer = manifest.DefaultInstallerInfo; + + // Clear these defaults as PackageFamilyName and ProductCode needs to be copied based on InstallerType + installer.PackageFamilyName.clear(); + installer.ProductCode.clear(); + m_p_installer = &installer; auto errors = ValidateAndProcessFields(entry, InstallerFieldInfos); std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + + // Copy in system reference strings from the root if not set in the installer and appropriate + if (installer.PackageFamilyName.empty() && DoesInstallerTypeUsePackageFamilyName(installer.InstallerType)) + { + installer.PackageFamilyName = manifest.DefaultInstallerInfo.PackageFamilyName; + } + + if (installer.ProductCode.empty() && DoesInstallerTypeUseProductCode(installer.InstallerType)) + { + installer.ProductCode = manifest.DefaultInstallerInfo.ProductCode; + } + manifest.Installers.emplace_back(std::move(installer)); } diff --git a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp index 85110c6175..d8004a8836 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp @@ -298,21 +298,27 @@ namespace AppInstaller::Manifest::YamlParser outFileStream.close(); } - std::vector ParseManifest( + std::vector ParseManifestImpl( std::vector& input, Manifest& manifest, bool fullValidation, - bool isPartialManifest, PCWSTR resourceDll, - const std::filesystem::path& mergedManifestPath) + const std::filesystem::path& mergedManifestPath, + bool isPartialManifest) { THROW_HR_IF_MSG(E_INVALIDARG, input.size() == 0, "No manifest file found"); THROW_HR_IF_MSG(E_INVALIDARG, isPartialManifest && !mergedManifestPath.empty(), "Manifest cannot be merged from partial manifest"); THROW_HR_IF_MSG(E_INVALIDARG, input.size() == 1 && !mergedManifestPath.empty(), "Manifest cannot be merged from a single manifest"); + THROW_HR_IF_MSG(E_INVALIDARG, !fullValidation && isPartialManifest, "For partial manifest, only schema validations are performed"); auto manifestVersion = ValidateInput(input, fullValidation, isPartialManifest); - auto resultErrors = ValidateAgainstSchema(input, manifestVersion, resourceDll); + std::vector resultErrors; + + if (fullValidation) + { + resultErrors = ValidateAgainstSchema(input, manifestVersion, resourceDll); + } // For partial manifest, only schema validations are performed if (isPartialManifest) @@ -344,9 +350,9 @@ namespace AppInstaller::Manifest::YamlParser const std::filesystem::path& inputPath, bool fullValidation, bool throwOnWarning, - bool isPartialManifest, PCWSTR resourceDll, - const std::filesystem::path& mergedManifestPath) + const std::filesystem::path& mergedManifestPath, + bool isPartialManifest) { std::vector docList; @@ -377,16 +383,16 @@ namespace AppInstaller::Manifest::YamlParser THROW_EXCEPTION_MSG(ManifestException(), e.what()); } - return CreateImpl(docList, fullValidation, throwOnWarning, isPartialManifest, resourceDll, mergedManifestPath); + return ParseManifest(docList, fullValidation, throwOnWarning, resourceDll, mergedManifestPath, isPartialManifest); } Manifest Create( const std::string& input, bool fullValidation, bool throwOnWarning, - bool isPartialManifest, PCWSTR resourceDll, - const std::filesystem::path& mergedManifestPath) + const std::filesystem::path& mergedManifestPath, + bool isPartialManifest) { std::vector docList; @@ -401,23 +407,23 @@ namespace AppInstaller::Manifest::YamlParser THROW_EXCEPTION_MSG(ManifestException(), e.what()); } - return CreateImpl(docList, fullValidation, throwOnWarning, isPartialManifest, resourceDll, mergedManifestPath); + return ParseManifest(docList, fullValidation, throwOnWarning, resourceDll, mergedManifestPath, isPartialManifest); } - Manifest CreateImpl( + Manifest ParseManifest( std::vector& input, bool fullValidation, bool throwOnWarning, - bool isPartialManifest, PCWSTR resourceDll, - const std::filesystem::path& mergedManifestPath) + const std::filesystem::path& mergedManifestPath, + bool isPartialManifest) { Manifest manifest; std::vector errors; try { - errors = ParseManifest(input, manifest, fullValidation, isPartialManifest, resourceDll, mergedManifestPath); + errors = ParseManifestImpl(input, manifest, fullValidation, resourceDll, mergedManifestPath, isPartialManifest); } catch (const ManifestException&) { diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h index 01caf74086..0418815e95 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h @@ -23,26 +23,23 @@ namespace AppInstaller::Manifest::YamlParser const std::filesystem::path& inputPath, bool fullValidation = false, bool throwOnWarning = false, - bool isPartialManifest = false, PCWSTR resourceDll = nullptr, - const std::filesystem::path& mergedManifestPath = {}); + const std::filesystem::path& mergedManifestPath = {}, + bool isPartialManifest = false); Manifest Create( const std::string& input, bool fullValidation = false, bool throwOnWarning = false, - bool isPartialManifest = false, PCWSTR resourceDll = nullptr, - const std::filesystem::path& mergedManifestPath = {}); + const std::filesystem::path& mergedManifestPath = {}, + bool isPartialManifest = false); - Manifest CreateImpl( + Manifest ParseManifest( std::vector& input, bool fullValidation, bool throwOnWarning, - bool isPartialManifest, PCWSTR resourceDll, - const std::filesystem::path& mergedManifestPath); - - - + const std::filesystem::path& mergedManifestPath, + bool isPartialManifest); } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Yaml.cpp b/src/AppInstallerCommonCore/Yaml.cpp index 64f6c4f38d..5b829befe5 100644 --- a/src/AppInstallerCommonCore/Yaml.cpp +++ b/src/AppInstallerCommonCore/Yaml.cpp @@ -246,7 +246,7 @@ namespace AppInstaller::YAML int Node::as_dispatch(int*) const { - return std::stoi(m_scalar); + return static_cast(std::stoll(m_scalar, 0, 0)); } bool Node::as_dispatch(bool*) const diff --git a/src/ManifestSchema/schema/schema_v1.0.0_installer.json b/src/ManifestSchema/schema/schema_v1.0.0_installer.json index 83c7b898e1..e25ae59a02 100644 --- a/src/ManifestSchema/schema/schema_v1.0.0_installer.json +++ b/src/ManifestSchema/schema/schema_v1.0.0_installer.json @@ -9,6 +9,12 @@ "maxLength": 128, "description": "The package unique identifier" }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, "Locale": { "type": [ "string", "null" ], "minLength": 1, @@ -173,7 +179,7 @@ "description": "List of file extensions the package could support" }, "Dependencies": { - "type": "object", + "type": [ "object", "null" ], "properties": { "WindowsFeatures": { "type": [ "array", "null" ], @@ -200,7 +206,16 @@ "PackageDependencies": { "type": [ "array", "null" ], "items": { - "$ref": "#/definitions/PackageIdentifier" + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] }, "maxItems": 16, "uniqueItems": true, @@ -351,10 +366,7 @@ "$ref": "#/definitions/PackageIdentifier" }, "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" + "$ref": "#/definitions/PackageVersion" }, "Channel": { "$ref": "#/definitions/Channel" diff --git a/src/ManifestSchema/schema/schema_v1.0.0_singleton.json b/src/ManifestSchema/schema/schema_v1.0.0_singleton.json index d72827decb..c67c223cde 100644 --- a/src/ManifestSchema/schema/schema_v1.0.0_singleton.json +++ b/src/ManifestSchema/schema/schema_v1.0.0_singleton.json @@ -9,6 +9,12 @@ "maxLength": 128, "description": "The package unique identifier" }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, "Locale": { "type": [ "string", "null" ], "pattern": "^([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", @@ -187,7 +193,7 @@ "description": "List of file extensions the package could support" }, "Dependencies": { - "type": "object", + "type": [ "object", "null" ], "properties": { "WindowsFeatures": { "type": [ "array", "null" ], @@ -214,10 +220,18 @@ "PackageDependencies": { "type": [ "array", "null" ], "items": { - "$ref": "#/definitions/PackageIdentifier" + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] }, "maxItems": 16, - "uniqueItems": true, "description": "List of package dependencies from current source" }, "ExternalDependencies": { @@ -365,10 +379,7 @@ "$ref": "#/definitions/PackageIdentifier" }, "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" + "$ref": "#/definitions/PackageVersion" }, "PackageLocale": { "$ref": "#/definitions/Locale", From 20a72b59018eed3dbecfa5d1df34c236c1764a6b Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Mon, 1 Feb 2021 21:18:47 -0800 Subject: [PATCH 09/18] Working snapshot 2 --- src/AppInstallerCLI.sln | 2 + .../AppInstallerCLITests.vcxproj | 4 +- .../AppInstallerCLITests.vcxproj.filters | 3 - src/AppInstallerCLITests/CompositeSource.cpp | 18 +- .../PredefinedInstalledSource.cpp | 6 +- src/AppInstallerCLITests/SQLiteIndex.cpp | 166 ++++++++++-------- .../SQLiteIndexSource.cpp | 16 +- .../Manifest-Bad-Channel-NotSupported.yaml | 13 -- ...st-Good-InstallerUniqueness-DiffScope.yaml | 2 +- src/AppInstallerCLITests/TestSource.cpp | 4 +- src/AppInstallerCLITests/YamlManifest.cpp | 163 +++++++++-------- .../Manifest/Manifest.cpp | 4 +- .../Manifest/ManifestYamlPopulator.cpp | 82 +++++---- .../Manifest/YamlParser.cpp | 33 +++- .../Public/winget/ManifestCommon.h | 2 + .../Public/winget/ManifestInstaller.h | 2 +- .../Public/winget/ManifestValidation.h | 6 + .../Public/winget/ManifestYamlPopulator.h | 7 +- src/WinGetUtil/Exports.cpp | 10 +- src/WinGetUtil/WinGetUtil.h | 6 +- src/WinGetUtil/WinGetUtil.vcxproj | 1 + 21 files changed, 298 insertions(+), 252 deletions(-) delete mode 100644 src/AppInstallerCLITests/TestData/Manifest-Bad-Channel-NotSupported.yaml diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index b53714424c..4731df4fe8 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -78,7 +78,9 @@ Global binver\binver.vcxitems*{6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}*SharedItemsImports = 9 ManifestSchema\ManifestSchema.vcxitems*{7d05f64d-ce5a-42aa-a2c1-e91458f061cf}*SharedItemsImports = 9 catch2\catch2.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 + ManifestSchema\ManifestSchema.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 binver\binver.vcxitems*{fb313532-38b0-4676-9303-ab200aa13576}*SharedItemsImports = 4 + ManifestSchema\ManifestSchema.vcxitems*{fb313532-38b0-4676-9303-ab200aa13576}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM = Debug|ARM diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index fc733cf476..02caffba88 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -54,6 +54,7 @@ + @@ -261,9 +262,6 @@ true - - true - true diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 64bc5e2ff0..e2ae3ab86a 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -123,9 +123,6 @@ TestData - - TestData - TestData diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp index ffb9aab73e..2c2e405a6c 100644 --- a/src/AppInstallerCLITests/CompositeSource.cpp +++ b/src/AppInstallerCLITests/CompositeSource.cpp @@ -74,8 +74,8 @@ Manifest::Manifest MakeDefaultManifest() Manifest::Manifest result; result.Id = "Id"; - result.Name = "Name"; - result.Publisher = "Publisher"; + result.DefaultLocalization.Add("Name"); + result.DefaultLocalization.Add("Publisher"); result.Version = "1.0"; result.Installers.push_back({}); @@ -191,7 +191,7 @@ TEST_CASE("CompositeSource_MultiMatch_FindsId", "[CompositeSource]") { SearchResult result; result.Matches.emplace_back(MakeAvailable([](Manifest::Manifest& m) { m.Id = "A different ID"; }), Criteria()); - result.Matches.emplace_back(MakeAvailable([&](Manifest::Manifest& m) { m.Name = name; }), Criteria()); + result.Matches.emplace_back(MakeAvailable([&](Manifest::Manifest& m) { m.DefaultLocalization.Add(name); }), Criteria()); return result; }; @@ -405,7 +405,7 @@ TEST_CASE("CompositePackage_AvailableVersions_ChannelFilteredOut", "[CompositeSo noChannel.Version = "1.0"; Manifest::Manifest hasChannel = MakeDefaultManifest(); - hasChannel.Channel = channel; + hasChannel.Installers[0].Channel = channel; hasChannel.Version = "2.0"; SearchResult result; @@ -434,14 +434,14 @@ TEST_CASE("CompositePackage_AvailableVersions_NoChannelFilteredOut", "[Composite std::string channel = "Channel"; CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(MakeInstalled([&](Manifest::Manifest& m) { m.Installers[0].PackageFamilyName = pfn; m.Channel = channel; }), Criteria()); + setup.Installed->Everything.Matches.emplace_back(MakeInstalled([&](Manifest::Manifest& m) { m.Installers[0].PackageFamilyName = pfn; m.Installers[0].Channel = channel; }), Criteria()); setup.Available->SearchFunction = [&](const SearchRequest&) { Manifest::Manifest noChannel = MakeDefaultManifest(); noChannel.Version = "1.0"; Manifest::Manifest hasChannel = MakeDefaultManifest(); - hasChannel.Channel = channel; + hasChannel.Installers[0].Channel = channel; hasChannel.Version = "2.0"; SearchResult result; @@ -482,7 +482,7 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_MatchFirst", "[CompositeSour REQUIRE(request.Inclusions[0].Value == pfn); SearchResult result; - result.Matches.emplace_back(MakeAvailable([&](Manifest::Manifest& m) { m.Name = firstName; }), Criteria()); + result.Matches.emplace_back(MakeAvailable([&](Manifest::Manifest& m) { m.DefaultLocalization.Add(firstName); }), Criteria()); return result; }; @@ -492,7 +492,7 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_MatchFirst", "[CompositeSour REQUIRE(request.Inclusions[0].Value == pfn); SearchResult result; - result.Matches.emplace_back(MakeAvailable([&](Manifest::Manifest& m) { m.Name = secondName; }), Criteria()); + result.Matches.emplace_back(MakeAvailable([&](Manifest::Manifest& m) { m.DefaultLocalization.Add(secondName); }), Criteria()); return result; }; @@ -522,7 +522,7 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_MatchSecond", "[CompositeSou REQUIRE(request.Inclusions[0].Value == pfn); SearchResult result; - result.Matches.emplace_back(MakeAvailable([&](Manifest::Manifest& m) { m.Name = secondName; }), Criteria()); + result.Matches.emplace_back(MakeAvailable([&](Manifest::Manifest& m) { m.DefaultLocalization.Add(secondName); }), Criteria()); return result; }; diff --git a/src/AppInstallerCLITests/PredefinedInstalledSource.cpp b/src/AppInstallerCLITests/PredefinedInstalledSource.cpp index a5cf63c12e..3fcaf357d7 100644 --- a/src/AppInstallerCLITests/PredefinedInstalledSource.cpp +++ b/src/AppInstallerCLITests/PredefinedInstalledSource.cpp @@ -85,11 +85,11 @@ SQLiteIndex::MetadataResult::const_iterator Find(const SQLiteIndex::MetadataResu return std::find_if(metadata.begin(), metadata.end(), [value](const auto& m) { return m.first == value; }); } -void VerifyInstalledType(const SQLiteIndex::MetadataResult& metadata, ManifestInstaller::InstallerTypeEnum type) +void VerifyInstalledType(const SQLiteIndex::MetadataResult& metadata, InstallerTypeEnum type) { auto itr = Find(metadata, PackageVersionMetadata::InstalledType); REQUIRE(itr != metadata.end()); - REQUIRE(ManifestInstaller::ConvertToInstallerTypeEnum(itr->second) == type); + REQUIRE(ConvertToInstallerTypeEnum(itr->second) == type); } void VerifyTestScope(const SQLiteIndex::MetadataResult& metadata) @@ -126,7 +126,7 @@ void VerifyEntryAgainstIndex(const SQLiteIndex& index, SQLiteIndex::IdType manif auto metadata = index.GetMetadataByManifestId(manifestId); - VerifyInstalledType(metadata, entry.WindowsInstaller.value_or(false) ? ManifestInstaller::InstallerTypeEnum::Msi : ManifestInstaller::InstallerTypeEnum::Exe); + VerifyInstalledType(metadata, entry.WindowsInstaller.value_or(false) ? InstallerTypeEnum::Msi : InstallerTypeEnum::Exe); VerifyTestScope(metadata); VerifyMetadataString(metadata, PackageVersionMetadata::InstalledLocation, entry.InstallLocation); VerifyMetadataString(metadata, PackageVersionMetadata::StandardUninstallCommand, entry.UninstallString); diff --git a/src/AppInstallerCLITests/SQLiteIndex.cpp b/src/AppInstallerCLITests/SQLiteIndex.cpp index 1ec5aee142..08a92a0823 100644 --- a/src/AppInstallerCLITests/SQLiteIndex.cpp +++ b/src/AppInstallerCLITests/SQLiteIndex.cpp @@ -64,13 +64,14 @@ SQLiteIndex SimpleTestSetup(const std::string& filePath, Manifest& manifest, std { SQLiteIndex index = CreateTestIndex(filePath, version); + manifest.Installers.push_back({}); manifest.Id = "Test.Id"; - manifest.Name = "Test Name"; - manifest.AppMoniker = "testmoniker"; + manifest.DefaultLocalization.Add("Test Name"); + manifest.Moniker = "testmoniker"; manifest.Version = "1.0.0"; - manifest.Channel = "test"; - manifest.Tags = { "t1", "t2" }; - manifest.Commands = { "test1", "test2" }; + manifest.Installers[0].Channel = "test"; + manifest.DefaultLocalization.Add({ "t1", "t2" }); + manifest.Installers[0].Commands = { "test1", "test2" }; relativePath = "test/id/1.0.0.yaml"; @@ -143,17 +144,24 @@ SQLiteIndex SearchTestSetup(const std::string& filePath, std::initializer_list(d.Name); + manifest.Moniker = d.Moniker; manifest.Version = d.Version; - manifest.Channel = d.Channel; - manifest.Tags = d.Tags; - manifest.Commands = d.Commands; + manifest.DefaultLocalization.Add(d.Tags); manifest.Installers.resize(std::max(d.PackageFamilyNames.size(), d.ProductCodes.size())); + if (manifest.Installers.size() == 0) + { + manifest.Installers.push_back({}); + } + + manifest.Installers[0].Channel = d.Channel; + manifest.Installers[0].Commands = d.Commands; + for (size_t i = 0; i < d.PackageFamilyNames.size(); ++i) { manifest.Installers[i].PackageFamilyName = d.PackageFamilyNames[i]; @@ -319,24 +327,26 @@ TEST_CASE("SQLiteIndex_RemoveManifest", "[sqliteindex][V1_0]") std::string manifest1Path = "test/id/test.id-1.0.0.yaml"; Manifest manifest1; + manifest1.Installers.push_back({}); manifest1.Id = "test.id"; - manifest1.Name = "Test Name"; - manifest1.AppMoniker = "testmoniker"; + manifest1.DefaultLocalization.Add("Test Name"); + manifest1.Moniker = "testmoniker"; manifest1.Version = "1.0.0"; - manifest1.Channel = "test"; - manifest1.Tags = { "t1", "t2" }; - manifest1.Commands = { "test1", "test2" }; + manifest1.Installers[0].Channel = "test"; + manifest1.DefaultLocalization.Add({ "t1", "t2" }); + manifest1.Installers[0].Commands = { "test1", "test2" }; std::string manifest2Path = "test/woah/test.id-1.0.0.yaml"; Manifest manifest2; + manifest2.Installers.push_back({}); manifest2.Id = "test.woah"; - manifest2.Name = "Test Name WOAH"; - manifest2.AppMoniker = "testmoniker"; + manifest2.DefaultLocalization.Add("Test Name WOAH"); + manifest2.Moniker = "testmoniker"; manifest2.Version = "1.0.0"; - manifest2.Channel = "test"; - manifest2.Tags = {}; - manifest2.Commands = { "test1", "test2", "test3" }; - + manifest2.Installers[0].Channel = "test"; + manifest2.DefaultLocalization.Add({}); + manifest2.Installers[0].Commands = { "test1", "test2", "test3" }; + { SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); @@ -391,23 +401,25 @@ TEST_CASE("SQLiteIndex_RemoveManifest_EnsureConsistentRowId", "[sqliteindex]") std::string manifest1Path = "test/id/test.id-1.0.0.yaml"; Manifest manifest1; + manifest1.Installers.push_back({}); manifest1.Id = "test.id"; - manifest1.Name = "Test Name"; - manifest1.AppMoniker = "testmoniker"; + manifest1.DefaultLocalization.Add("Test Name"); + manifest1.Moniker = "testmoniker"; manifest1.Version = "1.0.0"; - manifest1.Channel = "test"; - manifest1.Tags = { "t1", "t2" }; - manifest1.Commands = { "test1", "test2" }; + manifest1.Installers[0].Channel = "test"; + manifest1.DefaultLocalization.Add({ "t1", "t2" }); + manifest1.Installers[0].Commands = { "test1", "test2" }; std::string manifest2Path = "test/woah/test.id-1.0.0.yaml"; Manifest manifest2; + manifest2.Installers.push_back({}); manifest2.Id = "test.woah"; - manifest2.Name = "Test Name WOAH"; - manifest2.AppMoniker = "testmoniker"; + manifest2.DefaultLocalization.Add("Test Name WOAH"); + manifest2.Moniker = "testmoniker"; manifest2.Version = "1.0.0"; - manifest2.Channel = "test"; - manifest2.Tags = {}; - manifest2.Commands = { "test1", "test2", "test3" }; + manifest2.Installers[0].Channel = "test"; + manifest2.DefaultLocalization.Add({}); + manifest2.Installers[0].Commands = { "test1", "test2", "test3" }; SQLiteIndex index = CreateTestIndex(tempFile); @@ -442,9 +454,9 @@ TEST_CASE("SQLiteIndex_RemoveManifest_EnsureConsistentRowId", "[sqliteindex]") REQUIRE(rowId.value() == manifest2RowId); REQUIRE(manifest2.Id == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Id)); - REQUIRE(manifest2.Name == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Name)); + REQUIRE(manifest2.DefaultLocalization.Get() == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Name)); REQUIRE(manifest2.Version == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Version)); - REQUIRE(manifest2.Channel == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Channel)); + REQUIRE(manifest2.Installers[0].Channel == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Channel)); REQUIRE(manifest2Path == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::RelativePath)); } @@ -486,13 +498,14 @@ TEST_CASE("SQLiteIndex_UpdateManifest", "[sqliteindex][V1_0]") std::string manifestPath = "test/id/test.id-1.0.0.yaml"; Manifest manifest; + manifest.Installers.push_back({}); manifest.Id = "test.id"; - manifest.Name = "Test Name"; - manifest.AppMoniker = "testmoniker"; + manifest.DefaultLocalization.Add < Localization::PackageName>("Test Name"); + manifest.Moniker = "testmoniker"; manifest.Version = "1.0.0"; - manifest.Channel = "test"; - manifest.Tags = { "t1", "t2" }; - manifest.Commands = { "test1", "test2" }; + manifest.Installers[0].Channel = "test"; + manifest.DefaultLocalization.Add({ "t1", "t2" }); + manifest.Installers[0].Commands = { "test1", "test2" }; { SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); @@ -521,16 +534,16 @@ TEST_CASE("SQLiteIndex_UpdateManifest", "[sqliteindex][V1_0]") // Update with no updates should return false REQUIRE(!index.UpdateManifest(manifest, manifestPath)); - manifest.Description = "description2"; + manifest.DefaultLocalization.Add("description2"); // Update with no indexed updates should return false REQUIRE(!index.UpdateManifest(manifest, manifestPath)); // Update with indexed changes - manifest.Name = "Test Name2"; - manifest.AppMoniker = "testmoniker2"; - manifest.Tags = { "t1", "t2", "t3" }; - manifest.Commands = {}; + manifest.DefaultLocalization.Add("Test Name2"); + manifest.Moniker = "testmoniker2"; + manifest.DefaultLocalization.Add({ "t1", "t2", "t3" }); + manifest.Installers[0].Commands = {}; REQUIRE(index.UpdateManifest(manifest, manifestPath)); } @@ -579,13 +592,14 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangePath", "[sqliteindex][V1_0]") std::string manifestPath = "test/id/test.id-1.0.0.yaml"; Manifest manifest; + manifest.Installers.push_back({}); manifest.Id = "test.id"; - manifest.Name = "Test Name"; - manifest.AppMoniker = "testmoniker"; + manifest.DefaultLocalization.Add("Test Name"); + manifest.Moniker = "testmoniker"; manifest.Version = "1.0.0"; - manifest.Channel = "test"; - manifest.Tags = { "t1", "t2" }; - manifest.Commands = { "test1", "test2" }; + manifest.Installers[0].Channel = "test"; + manifest.DefaultLocalization.Add({ "t1", "t2" }); + manifest.Installers[0].Commands = { "test1", "test2" }; { SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); @@ -660,13 +674,14 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]") std::string manifestPath = "test/id/test.id-1.0.0.yaml"; Manifest manifest; + manifest.Installers.push_back({}); manifest.Id = "test.id"; - manifest.Name = "Test Name"; - manifest.AppMoniker = "testmoniker"; + manifest.DefaultLocalization.Add("Test Name"); + manifest.Moniker = "testmoniker"; manifest.Version = "1.0.0-test"; - manifest.Channel = "test"; - manifest.Tags = { "t1", "t2" }; - manifest.Commands = { "test1", "test2" }; + manifest.Installers[0].Channel = "test"; + manifest.DefaultLocalization.Add({ "t1", "t2" }); + manifest.Installers[0].Commands = { "test1", "test2" }; { SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); @@ -695,7 +710,7 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]") { SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); - manifest.Channel = "Test"; + manifest.Installers[0].Channel = "Test"; // Update with path update should indicate change REQUIRE(index.UpdateManifest(manifest, manifestPath)); @@ -704,7 +719,7 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]") { SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); - manifest.Name = "test name"; + manifest.DefaultLocalization.Add("test name"); // Update with path update should indicate change REQUIRE(index.UpdateManifest(manifest, manifestPath)); @@ -738,12 +753,13 @@ TEST_CASE("SQLiteIndex_IdCaseInsensitivity", "[sqliteindex][V1_0]") std::string manifest1Path = "test/id/test.id-1.0.0.yaml"; Manifest manifest1; + manifest1.Installers.push_back({}); manifest1.Id = "test.id"; - manifest1.Name = "Test Name"; - manifest1.AppMoniker = "testmoniker"; + manifest1.DefaultLocalization.Add("Test Name"); + manifest1.Moniker = "testmoniker"; manifest1.Version = "1.0.0"; - manifest1.Tags = { "t1", "t2" }; - manifest1.Commands = { "test1", "test2" }; + manifest1.DefaultLocalization.Add({ "t1", "t2" }); + manifest1.Installers[0].Commands = { "test1", "test2" }; std::string manifest2Path = "test/id/test.id-2.0.0.yaml"; Manifest manifest2 = manifest1; @@ -999,7 +1015,7 @@ TEST_CASE("SQLiteIndex_NameString", "[sqliteindex]") REQUIRE(results.Matches.size() == 1); auto result = GetNameStringById(index, results.Matches[0].first); - REQUIRE(result == manifest.Name); + REQUIRE(result == manifest.DefaultLocalization.Get()); } TEST_CASE("SQLiteIndex_PathString", "[sqliteindex]") @@ -1019,10 +1035,10 @@ TEST_CASE("SQLiteIndex_PathString", "[sqliteindex]") auto results = index.Search(request); REQUIRE(results.Matches.size() == 1); - auto specificResult = GetPathStringByKey(index, results.Matches[0].first, manifest.Version, manifest.Channel); + auto specificResult = GetPathStringByKey(index, results.Matches[0].first, manifest.Version, manifest.Installers[0].Channel); REQUIRE(specificResult == relativePath); - auto latestResult = GetPathStringByKey(index, results.Matches[0].first, "", manifest.Channel); + auto latestResult = GetPathStringByKey(index, results.Matches[0].first, "", manifest.Installers[0].Channel); REQUIRE(latestResult == relativePath); } @@ -1046,7 +1062,7 @@ TEST_CASE("SQLiteIndex_Versions", "[sqliteindex]") auto result = index.GetVersionKeysById(results.Matches[0].first); REQUIRE(result.size() == 1); REQUIRE(result[0].GetVersion().ToString() == manifest.Version); - REQUIRE(result[0].GetChannel().ToString() == manifest.Channel); + REQUIRE(result[0].GetChannel().ToString() == manifest.Installers[0].Channel); } TEST_CASE("SQLiteIndex_Search_VersionSorting", "[sqliteindex]") @@ -1821,23 +1837,25 @@ TEST_CASE("SQLiteIndex_CheckConsistency_Failure", "[sqliteindex][V1_1]") std::string manifest1Path = "test/id/test.id-1.0.0.yaml"; Manifest manifest1; + manifest1.Installers.push_back({}); manifest1.Id = "test.id"; - manifest1.Name = "Test Name"; - manifest1.AppMoniker = "testmoniker"; + manifest1.DefaultLocalization.Add("Test Name"); + manifest1.Moniker = "testmoniker"; manifest1.Version = "1.0.0"; - manifest1.Channel = "test"; - manifest1.Tags = { "t1", "t2" }; - manifest1.Commands = { "test1", "test2" }; + manifest1.Installers[0].Channel = "test"; + manifest1.DefaultLocalization.Add({ "t1", "t2" }); + manifest1.Installers[0].Commands = { "test1", "test2" }; std::string manifest2Path = "test/woah/test.id-1.0.0.yaml"; Manifest manifest2; + manifest2.Installers.push_back({}); manifest2.Id = "test.woah"; - manifest2.Name = "Test Name WOAH"; - manifest2.AppMoniker = "testmoniker"; + manifest2.DefaultLocalization.Add("Test Name WOAH"); + manifest2.Moniker = "testmoniker"; manifest2.Version = "1.0.0"; - manifest2.Channel = "test"; - manifest2.Tags = {}; - manifest2.Commands = { "test1", "test2", "test3" }; + manifest2.Installers[0].Channel = "test"; + manifest2.DefaultLocalization.Add({}); + manifest2.Installers[0].Commands = { "test1", "test2", "test3" }; SQLite::rowid_t manifestRowId = 0; diff --git a/src/AppInstallerCLITests/SQLiteIndexSource.cpp b/src/AppInstallerCLITests/SQLiteIndexSource.cpp index 9d9d3b85d0..07126e8a45 100644 --- a/src/AppInstallerCLITests/SQLiteIndexSource.cpp +++ b/src/AppInstallerCLITests/SQLiteIndexSource.cpp @@ -108,7 +108,7 @@ TEST_CASE("SQLiteIndexSource_Name", "[sqliteindexsource]") REQUIRE(results.Matches[0].Package); auto latestVersion = results.Matches[0].Package->GetLatestAvailableVersion(); - REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Name).get() == manifest.Name); + REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Name).get() == manifest.DefaultLocalization.Get()); } TEST_CASE("SQLiteIndexSource_Versions", "[sqliteindexsource]") @@ -131,7 +131,7 @@ TEST_CASE("SQLiteIndexSource_Versions", "[sqliteindexsource]") auto result = results.Matches[0].Package->GetAvailableVersionKeys(); REQUIRE(result.size() == 1); REQUIRE(result[0].Version == manifest.Version); - REQUIRE(result[0].Channel == manifest.Channel); + REQUIRE(result[0].Channel == manifest.Installers[0].Channel); } TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") @@ -152,21 +152,21 @@ TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") REQUIRE(results.Matches[0].Package); auto package = results.Matches[0].Package.get(); - auto specificResultVersion = package->GetAvailableVersion(PackageVersionKey("", manifest.Version, manifest.Channel)); + auto specificResultVersion = package->GetAvailableVersion(PackageVersionKey("", manifest.Version, manifest.Installers[0].Channel)); REQUIRE(specificResultVersion); auto specificResult = specificResultVersion->GetManifest(); REQUIRE(specificResult.Id == manifest.Id); - REQUIRE(specificResult.Name == manifest.Name); + REQUIRE(specificResult.DefaultLocalization.Get() == manifest.DefaultLocalization.Get()); REQUIRE(specificResult.Version == manifest.Version); - REQUIRE(specificResult.Channel == manifest.Channel); + REQUIRE(specificResult.Installers[0].Channel == manifest.Installers[0].Channel); - auto latestResultVersion = package->GetAvailableVersion(PackageVersionKey("", "", manifest.Channel)); + auto latestResultVersion = package->GetAvailableVersion(PackageVersionKey("", "", manifest.Installers[0].Channel)); REQUIRE(latestResultVersion); auto latestResult = latestResultVersion->GetManifest(); REQUIRE(latestResult.Id == manifest.Id); - REQUIRE(latestResult.Name == manifest.Name); + REQUIRE(latestResult.DefaultLocalization.Get() == manifest.DefaultLocalization.Get()); REQUIRE(latestResult.Version == manifest.Version); - REQUIRE(latestResult.Channel == manifest.Channel); + REQUIRE(latestResult.Installers[0].Channel == manifest.Installers[0].Channel); auto noResultVersion = package->GetAvailableVersion(PackageVersionKey("", "blargle", "flargle")); REQUIRE(!noResultVersion); diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-Channel-NotSupported.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-Channel-NotSupported.yaml deleted file mode 100644 index da6232b0ae..0000000000 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-Channel-NotSupported.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Minimum required -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -Channel: release -InstallerType: Zip -License: Test -Installers: - - Arch: x86 - Url: https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdk-x86.zip - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD -ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerUniqueness-DiffScope.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerUniqueness-DiffScope.yaml index fb921858d9..fdb0d28c02 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerUniqueness-DiffScope.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerUniqueness-DiffScope.yaml @@ -13,5 +13,5 @@ Installers: - Arch: x86 Url: https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdk-x86.zip Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD - Scope: system + Scope: machine ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestSource.cpp b/src/AppInstallerCLITests/TestSource.cpp index 5fc1ca3513..fc54cc3d0a 100644 --- a/src/AppInstallerCLITests/TestSource.cpp +++ b/src/AppInstallerCLITests/TestSource.cpp @@ -19,11 +19,11 @@ namespace TestCommon case PackageVersionProperty::Id: return LocIndString{ VersionManifest.Id }; case PackageVersionProperty::Name: - return LocIndString{ VersionManifest.Name }; + return LocIndString{ VersionManifest.DefaultLocalization.Get() }; case PackageVersionProperty::Version: return LocIndString{ VersionManifest.Version }; case PackageVersionProperty::Channel: - return LocIndString{ VersionManifest.Channel }; + return LocIndString{ VersionManifest.Installers[0].Channel }; default: return {}; } diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 1165811fa7..3a3c659b54 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -28,41 +28,41 @@ bool operator==(const MultiValue& a, const MultiValue& b) return true; } -TEST_CASE("ReadGoodManifestAndVerifyContents", "[ManifestValidation]") +TEST_CASE("ReadPreviewGoodManifestAndVerifyContents", "[ManifestValidation]") { Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good.yaml")); REQUIRE(manifest.Id == "microsoft.msixsdk"); - REQUIRE(manifest.Name == "MSIX SDK"); - REQUIRE(manifest.AppMoniker == "msixsdk"); + REQUIRE(manifest.DefaultLocalization.Get() == "MSIX SDK"); + REQUIRE(manifest.Moniker == "msixsdk"); REQUIRE(manifest.Version == "1.7.32"); - REQUIRE(manifest.Publisher == "Microsoft"); - REQUIRE(manifest.Channel == "release"); - REQUIRE(manifest.Author == "Microsoft"); - REQUIRE(manifest.License == "MIT License"); - REQUIRE(manifest.LicenseUrl == "https://github.com/microsoft/msix-packaging/blob/master/LICENSE"); - REQUIRE(manifest.MinOSVersion == "0.0.0.0"); - REQUIRE(manifest.Description == "The MSIX SDK project is an effort to enable developers"); - REQUIRE(manifest.Homepage == "https://github.com/microsoft/msix-packaging"); - REQUIRE(manifest.Tags == MultiValue{ "msix", "appx" }); - REQUIRE(manifest.Commands == MultiValue{ "makemsix", "makeappx" }); - REQUIRE(manifest.Protocols == MultiValue{ "protocol1", "protocol2" }); - REQUIRE(manifest.FileExtensions == MultiValue{ "appx", "appxbundle", "msix", "msixbundle" }); - REQUIRE(manifest.InstallerType == ManifestInstaller::InstallerTypeEnum::Zip); - REQUIRE(manifest.PackageFamilyName == "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); - REQUIRE(manifest.ProductCode == "{Foo}"); - REQUIRE(manifest.UpdateBehavior == ManifestInstaller::UpdateBehaviorEnum::UninstallPrevious); + REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); + REQUIRE(manifest.DefaultInstallerInfo.Channel == "release"); + REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); + REQUIRE(manifest.DefaultLocalization.Get() == "MIT License"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://github.com/microsoft/msix-packaging/blob/master/LICENSE"); + REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "0.0.0.0"); + REQUIRE(manifest.DefaultLocalization.Get() == "The MSIX SDK project is an effort to enable developers"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://github.com/microsoft/msix-packaging"); + REQUIRE(manifest.DefaultLocalization.Get() == MultiValue{ "msix", "appx" }); + REQUIRE(manifest.DefaultInstallerInfo.Commands == MultiValue{ "makemsix", "makeappx" }); + REQUIRE(manifest.DefaultInstallerInfo.Protocols == MultiValue{ "protocol1", "protocol2" }); + REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "appxbundle", "msix", "msixbundle" }); + REQUIRE(manifest.DefaultInstallerInfo.InstallerType == InstallerTypeEnum::Zip); + REQUIRE(manifest.DefaultInstallerInfo.PackageFamilyName == "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); + REQUIRE(manifest.DefaultInstallerInfo.ProductCode == "{Foo}"); + REQUIRE(manifest.DefaultInstallerInfo.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious); // default switches - auto switches = manifest.Switches; - REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::Custom) == "/custom"); - REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); - REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::Silent) == "/silence"); - REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::Interactive) == "/interactive"); - REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::Language) == "/en-us"); - REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::Log) == "/log="); - REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::InstallLocation) == "/dir="); - REQUIRE(switches.at(ManifestInstaller::InstallerSwitchType::Update) == "/update"); + auto switches = manifest.DefaultInstallerInfo.Switches; + REQUIRE(switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(switches.at(InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); + REQUIRE(switches.at(InstallerSwitchType::Silent) == "/silence"); + REQUIRE(switches.at(InstallerSwitchType::Interactive) == "/interactive"); + REQUIRE(switches.at(InstallerSwitchType::Language) == "/en-us"); + REQUIRE(switches.at(InstallerSwitchType::Log) == "/log="); + REQUIRE(switches.at(InstallerSwitchType::InstallLocation) == "/dir="); + REQUIRE(switches.at(InstallerSwitchType::Update) == "/update"); // installers REQUIRE(manifest.Installers.size() == 2); @@ -70,52 +70,52 @@ TEST_CASE("ReadGoodManifestAndVerifyContents", "[ManifestValidation]") REQUIRE(installer1.Arch == Architecture::X86); REQUIRE(installer1.Url == "https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdkx86.zip"); REQUIRE(installer1.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer1.Language == "en-US"); - REQUIRE(installer1.InstallerType == ManifestInstaller::InstallerTypeEnum::Zip); + REQUIRE(installer1.Locale == "en-US"); + REQUIRE(installer1.InstallerType == InstallerTypeEnum::Zip); REQUIRE(installer1.Scope == ScopeEnum::User); REQUIRE(installer1.PackageFamilyName == ""); REQUIRE(installer1.ProductCode == ""); - REQUIRE(installer1.UpdateBehavior == ManifestInstaller::UpdateBehaviorEnum::Install); + REQUIRE(installer1.UpdateBehavior == UpdateBehaviorEnum::Install); auto installer1Switches = installer1.Switches; - REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::Custom) == "/c"); - REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::SilentWithProgress) == "/sp"); - REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::Silent) == "/s"); - REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::Interactive) == "/i"); - REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::Language) == "/en"); - REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::Log) == "/l="); - REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::InstallLocation) == "/d="); - REQUIRE(installer1Switches.at(ManifestInstaller::InstallerSwitchType::Update) == "/u"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Custom) == "/c"); + REQUIRE(installer1Switches.at(InstallerSwitchType::SilentWithProgress) == "/sp"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Language) == "/en"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Log) == "/l="); + REQUIRE(installer1Switches.at(InstallerSwitchType::InstallLocation) == "/d="); + REQUIRE(installer1Switches.at(InstallerSwitchType::Update) == "/u"); ManifestInstaller installer2 = manifest.Installers.at(1); REQUIRE(installer2.Arch == Architecture::X64); REQUIRE(installer2.Url == "https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdkx64.zip"); REQUIRE(installer2.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF0000")); - REQUIRE(installer2.Language == "en-US"); - REQUIRE(installer2.InstallerType == ManifestInstaller::InstallerTypeEnum::Zip); + REQUIRE(installer2.Locale == "en-US"); + REQUIRE(installer2.InstallerType == InstallerTypeEnum::Zip); REQUIRE(installer2.Scope == ScopeEnum::User); REQUIRE(installer2.PackageFamilyName == ""); REQUIRE(installer2.ProductCode == ""); - REQUIRE(installer2.UpdateBehavior == ManifestInstaller::UpdateBehaviorEnum::UninstallPrevious); + REQUIRE(installer2.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious); // Installer2 does not declare switches, it inherits switches from package default. auto installer2Switches = installer2.Switches; - REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::Custom) == "/custom"); - REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); - REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::Silent) == "/silence"); - REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::Interactive) == "/interactive"); - REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::Language) == "/en-us"); - REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::Log) == "/log="); - REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::InstallLocation) == "/dir="); - REQUIRE(installer2Switches.at(ManifestInstaller::InstallerSwitchType::Update) == "/update"); + REQUIRE(installer2Switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(installer2Switches.at(InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); + REQUIRE(installer2Switches.at(InstallerSwitchType::Silent) == "/silence"); + REQUIRE(installer2Switches.at(InstallerSwitchType::Interactive) == "/interactive"); + REQUIRE(installer2Switches.at(InstallerSwitchType::Language) == "/en-us"); + REQUIRE(installer2Switches.at(InstallerSwitchType::Log) == "/log="); + REQUIRE(installer2Switches.at(InstallerSwitchType::InstallLocation) == "/dir="); + REQUIRE(installer2Switches.at(InstallerSwitchType::Update) == "/update"); // Localization - REQUIRE(manifest.Localization.size() == 1); - ManifestLocalization localization1 = manifest.Localization.at(0); - REQUIRE(localization1.Language == "es-MX"); - REQUIRE(localization1.Description == "El proyecto MSIX SDK es habilita desarrolladores de diferentes"); - REQUIRE(localization1.Homepage == "https://github.com/microsoft/msix-packaging/es-MX"); - REQUIRE(localization1.LicenseUrl == "https://github.com/microsoft/msix-packaging/blob/master/LICENSE-es-MX"); + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization localization1 = manifest.Localizations.at(0); + REQUIRE(localization1.Locale == "es-MX"); + REQUIRE(localization1.Get() == "El proyecto MSIX SDK es habilita desarrolladores de diferentes"); + REQUIRE(localization1.Get() == "https://github.com/microsoft/msix-packaging/es-MX"); + REQUIRE(localization1.Get() == "https://github.com/microsoft/msix-packaging/blob/master/LICENSE-es-MX"); } TEST_CASE("ReadGoodManifestWithSpaces", "[ManifestValidation]") @@ -123,15 +123,15 @@ TEST_CASE("ReadGoodManifestWithSpaces", "[ManifestValidation]") Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-Spaces.yaml")); REQUIRE(manifest.Id == "microsoft.msixsdk"); - REQUIRE(manifest.Name == "MSIX SDK"); - REQUIRE(manifest.AppMoniker == "msixsdk"); + REQUIRE(manifest.DefaultLocalization.Get() == "MSIX SDK"); + REQUIRE(manifest.Moniker == "msixsdk"); REQUIRE(manifest.Version == "1.7.32"); - REQUIRE(manifest.Channel == "release"); - REQUIRE(manifest.MinOSVersion == "0.0.0.0"); - REQUIRE(manifest.Tags == MultiValue{ "msix", "appx" }); - REQUIRE(manifest.Commands == MultiValue{ "makemsix", "makeappx" }); - REQUIRE(manifest.Protocols == MultiValue{ "protocol1", "protocol2" }); - REQUIRE(manifest.FileExtensions == MultiValue{ "appx", "appxbundle", "msix", "msixbundle" }); + REQUIRE(manifest.DefaultInstallerInfo.Channel == "release"); + REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "0.0.0.0"); + REQUIRE(manifest.DefaultLocalization.Get() == MultiValue{ "msix", "appx" }); + REQUIRE(manifest.DefaultInstallerInfo.Commands == MultiValue{ "makemsix", "makeappx" }); + REQUIRE(manifest.DefaultInstallerInfo.Protocols == MultiValue{ "protocol1", "protocol2" }); + REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "appxbundle", "msix", "msixbundle" }); } struct ManifestExceptionMatcher : public Catch::MatcherBase @@ -204,17 +204,16 @@ TEST_CASE("ReadBadManifests", "[ManifestValidation]") ManifestTestCase TestCases[] = { { "Manifest-Bad-ArchInvalid.yaml", "Invalid field value. Field: Arch" }, - { "Manifest-Bad-ArchMissing.yaml", "Required field missing. Field: Arch" }, - { "Manifest-Bad-Channel-NotSupported.yaml", "Field is not supported. Field: Channel" }, + { "Manifest-Bad-ArchMissing.yaml", "Missing required property 'Arch'" }, { "Manifest-Bad-DifferentCase-camelCase.yaml", "All field names should be PascalCased. Field: installerType" }, { "Manifest-Bad-DifferentCase-lower.yaml", "All field names should be PascalCased. Field: installertype" }, { "Manifest-Bad-DifferentCase-UPPER.yaml", "All field names should be PascalCased. Field: INSTALLERTYPE" }, { "Manifest-Bad-DuplicateKey.yaml", "Duplicate field found in the manifest." }, { "Manifest-Bad-DuplicateKey-DifferentCase.yaml", "Duplicate field found in the manifest." }, { "Manifest-Bad-DuplicateKey-DifferentCase-lower.yaml", "Duplicate field found in the manifest." }, - { "Manifest-Bad-IdInvalid.yaml", "Invalid field value. Field: Id" }, - { "Manifest-Bad-IdMissing.yaml", "Required field missing. Field: Id" }, - { "Manifest-Bad-InstallersMissing.yaml", "Required field missing. Field: Installers" }, + { "Manifest-Bad-IdInvalid.yaml", "Failed to validate against schema associated with property name 'Id'" }, + { "Manifest-Bad-IdMissing.yaml", "Missing required property 'Id'" }, + { "Manifest-Bad-InstallersMissing.yaml", "Missing required property 'Installers'" }, { "Manifest-Bad-InstallerTypeExe-NoSilent.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, { "Manifest-Bad-InstallerTypeExe-NoSilentRoot.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, { "Manifest-Bad-InstallerTypeExeRoot-NoSilent.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, @@ -225,19 +224,19 @@ TEST_CASE("ReadBadManifests", "[ManifestValidation]") { "Manifest-Bad-InstallerUniqueness-DefaultScope.yaml", "Duplicate installer entry found." }, { "Manifest-Bad-InstallerUniqueness-DefaultValues.yaml", "Duplicate installer entry found." }, { "Manifest-Bad-InstallerUniqueness-SameLang.yaml", "Duplicate installer entry found." }, - { "Manifest-Bad-LicenseMissing.yaml", "Required field missing. Field: License" }, - { "Manifest-Bad-NameMissing.yaml", "Required field missing. Field: Name" }, - { "Manifest-Bad-PublisherMissing.yaml", "Required field missing. Field: Publisher" }, - { "Manifest-Bad-Sha256Invalid.yaml", "Invalid field value. Field: Sha256" }, + { "Manifest-Bad-LicenseMissing.yaml", "Missing required property 'License'" }, + { "Manifest-Bad-NameMissing.yaml", "Missing required property 'Name'" }, + { "Manifest-Bad-PublisherMissing.yaml", "Missing required property 'Publisher'" }, + { "Manifest-Bad-Sha256Invalid.yaml", "Failed to validate against schema associated with property name 'Sha256'" }, { "Manifest-Bad-Sha256Missing.yaml", "Required field missing. Field: Sha256" }, { "Manifest-Bad-SwitchInvalid.yaml", "Unknown field. Field: NotASwitch", true }, { "Manifest-Bad-UnknownProperty.yaml", "Unknown field. Field: Fake", true }, { "Manifest-Bad-UnsupportedVersion.yaml", "Unsupported ManifestVersion" }, { "Manifest-Bad-UrlInvalid.yaml", "Invalid field value. Field: Url" }, { "Manifest-Bad-UrlMissing.yaml", "Required field missing. Field: Url" }, - { "Manifest-Bad-VersionInvalid.yaml", "Invalid field value. Field: Version" }, - { "Manifest-Bad-VersionMissing.yaml", "Required field missing. Field: Version" }, - { "Manifest-Bad-InvalidManifestVersionValue.yaml", "Invalid field value. Field: ManifestVersion" }, + { "Manifest-Bad-VersionInvalid.yaml", "Failed to validate against schema associated with property name 'Version'" }, + { "Manifest-Bad-VersionMissing.yaml", "Missing required property 'Version'" }, + { "Manifest-Bad-InvalidManifestVersionValue.yaml", "Failed to validate against schema associated with property name 'ManifestVersion'" }, { "InstallFlowTest_MSStore.yaml", "Field value is not supported. Field: InstallerType Value: MSStore" }, { "Manifest-Bad-PackageFamilyNameOnMSI.yaml", "The specified installer type does not support PackageFamilyName. Field: InstallerType Value: Msi" }, { "Manifest-Bad-ProductCodeOnMSIX.yaml", "The specified installer type does not support ProductCode. Field: InstallerType Value: Msix" }, @@ -267,7 +266,7 @@ TEST_CASE("ManifestEncoding", "[ManifestValidation]") { INFO(testCase.TestFile); Manifest manifest = YamlParser::CreateFromPath(TestDataFile(testCase.TestFile), true, true); - REQUIRE(manifest.Name == u8"MSIX SDK\xA9"); + REQUIRE(manifest.DefaultLocalization.Get() == u8"MSIX SDK\xA9"); } } @@ -278,30 +277,30 @@ TEST_CASE("ComplexSystemReference", "[ManifestValidation]") REQUIRE(manifest.Installers.size() == 5); // Zip installer does not inherit - REQUIRE(manifest.Installers[0].InstallerType == ManifestInstaller::InstallerTypeEnum::Zip); + REQUIRE(manifest.Installers[0].InstallerType == InstallerTypeEnum::Zip); REQUIRE(manifest.Installers[0].PackageFamilyName == ""); REQUIRE(manifest.Installers[0].ProductCode == ""); // MSIX installer does inherit - REQUIRE(manifest.Installers[1].InstallerType == ManifestInstaller::InstallerTypeEnum::Msix); + REQUIRE(manifest.Installers[1].InstallerType == InstallerTypeEnum::Msix); REQUIRE(manifest.Installers[1].Arch == Architecture::X86); REQUIRE(manifest.Installers[1].PackageFamilyName == "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); REQUIRE(manifest.Installers[1].ProductCode == ""); // MSI installer does inherit - REQUIRE(manifest.Installers[2].InstallerType == ManifestInstaller::InstallerTypeEnum::Msi); + REQUIRE(manifest.Installers[2].InstallerType == InstallerTypeEnum::Msi); REQUIRE(manifest.Installers[2].Arch == Architecture::X86); REQUIRE(manifest.Installers[2].PackageFamilyName == ""); REQUIRE(manifest.Installers[2].ProductCode == "{Foo}"); // MSIX installer with override - REQUIRE(manifest.Installers[3].InstallerType == ManifestInstaller::InstallerTypeEnum::Msix); + REQUIRE(manifest.Installers[3].InstallerType == InstallerTypeEnum::Msix); REQUIRE(manifest.Installers[3].Arch == Architecture::X64); REQUIRE(manifest.Installers[3].PackageFamilyName == "Override_8wekyb3d8bbwe"); REQUIRE(manifest.Installers[3].ProductCode == ""); // MSI installer with override - REQUIRE(manifest.Installers[4].InstallerType == ManifestInstaller::InstallerTypeEnum::Msi); + REQUIRE(manifest.Installers[4].InstallerType == InstallerTypeEnum::Msi); REQUIRE(manifest.Installers[4].Arch == Architecture::X64); REQUIRE(manifest.Installers[4].PackageFamilyName == ""); REQUIRE(manifest.Installers[4].ProductCode == "Override"); diff --git a/src/AppInstallerCommonCore/Manifest/Manifest.cpp b/src/AppInstallerCommonCore/Manifest/Manifest.cpp index de21be7f38..4e52c0a7c2 100644 --- a/src/AppInstallerCommonCore/Manifest/Manifest.cpp +++ b/src/AppInstallerCommonCore/Manifest/Manifest.cpp @@ -20,7 +20,7 @@ namespace AppInstaller::Manifest auto tags = locale.Get(); for (const auto& tag : tags) { - if (std::find(resultTags.begin(), resultTags.end(), tag) != resultTags.end()) + if (std::find(resultTags.begin(), resultTags.end(), tag) == resultTags.end()) { resultTags.emplace_back(tag); } @@ -38,7 +38,7 @@ namespace AppInstaller::Manifest { for (const auto& command : installer.Commands) { - if (std::find(resultCommands.begin(), resultCommands.end(), command) != resultCommands.end()) + if (std::find(resultCommands.begin(), resultCommands.end(), command) == resultCommands.end()) { resultCommands.emplace_back(command); } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index 95fe09314c..c58b7b7a39 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -108,8 +108,8 @@ namespace AppInstaller::Manifest std::vector result = { { "ManifestVersion", [](const YAML::Node&)->ValidationErrors { /* ManifestVersion already populated. Field listed here for duplicate and PascalCase check */ return {}; } }, - { "Installers", [this](const YAML::Node& value)->ValidationErrors { *m_p_installersNode = value; return {}; } }, - { "Localization", [this](const YAML::Node& value)->ValidationErrors { return ProcessLocalizationNode(value, m_p_manifest->Localizations); } }, + { "Installers", [this](const YAML::Node& value)->ValidationErrors { m_p_installersNode = &value; return {}; } }, + { "Localization", [this](const YAML::Node& value)->ValidationErrors { m_p_localizationsNode = &value; return {}; } }, }; // Additional version specific fields @@ -134,6 +134,7 @@ namespace AppInstaller::Manifest { "PackageIdentifier", [this](const YAML::Node& value)->ValidationErrors { m_p_manifest->Id = Utility::Trim(value.as()); return {}; } }, { "PackageVersion", [this](const YAML::Node& value)->ValidationErrors { m_p_manifest->Version = Utility::Trim(value.as()); return {}; } }, { "Moniker", [this](const YAML::Node& value)->ValidationErrors { m_p_manifest->Moniker = Utility::Trim(value.as()); return {}; } }, + { "ManifestType", [](const YAML::Node&)->ValidationErrors { /* ManifestType already checked. Field listed here for duplicate and PascalCase check */ return {}; } }, }; std::move(v1RootFields.begin(), v1RootFields.end(), std::inserter(result, result.end())); @@ -144,7 +145,7 @@ namespace AppInstaller::Manifest auto rootInstallerFields = GetInstallerFieldProcessInfo(manifestVersion, true); std::move(rootInstallerFields.begin(), rootInstallerFields.end(), std::inserter(result, result.end())); - auto rootLocalizationFields = GetInstallerFieldProcessInfo(manifestVersion, true); + auto rootLocalizationFields = GetLocalizationFieldProcessInfo(manifestVersion, true); std::move(rootLocalizationFields.begin(), rootLocalizationFields.end(), std::inserter(result, result.end())); return result; @@ -264,7 +265,6 @@ namespace AppInstaller::Manifest { "Interactive", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::Interactive] = value.as(); return{}; } }, { "Log", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::Log] = value.as(); return{}; } }, { "InstallLocation", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::InstallLocation] = value.as(); return{}; } }, - { "Update", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::Update] = value.as(); return{}; } }, }; // Additional version specific fields @@ -272,10 +272,11 @@ namespace AppInstaller::Manifest { // Language only exists in preview manifests. Though we don't use it in our code yet, keep it here to be consistent with schema. result.emplace_back("Language", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::Language] = value.as(); return{}; }); + result.emplace_back("Update", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::Update] = value.as(); return{}; }); } else if (manifestVersion.Major() == 1) { - // No new fields in v1 yet + result.emplace_back("Upgrade", [this](const YAML::Node& value)->ValidationErrors { (*m_p_switches)[InstallerSwitchType::Update] = value.as(); return{}; }); } return result; @@ -388,7 +389,7 @@ namespace AppInstaller::Manifest if (!rootNode.IsMap() || rootNode.size() == 0) { - resultErrors.emplace_back(ManifestError::InvalidRootNode, "", "", rootNode.Mark().line, rootNode.Mark().column); + resultErrors.emplace_back(ManifestError::InvalidRootNode, "", "", m_isMergedManifest ? 0 : rootNode.Mark().line, m_isMergedManifest ? 0 : rootNode.Mark().column); return resultErrors; } @@ -414,19 +415,26 @@ namespace AppInstaller::Manifest // Make sure the found key is in Pascal Case if (key != fieldInfo.Name) { - resultErrors.emplace_back(ManifestError::FieldIsNotPascalCase, key, "", keyValuePair.first.Mark().line, keyValuePair.first.Mark().column); + resultErrors.emplace_back(ManifestError::FieldIsNotPascalCase, key, "", m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column); } // Make sure it's not a duplicate key if (!processedFields.insert(fieldInfo.Name).second) { - resultErrors.emplace_back(ManifestError::FieldDuplicate, fieldInfo.Name, "", keyValuePair.first.Mark().line, keyValuePair.first.Mark().column); + resultErrors.emplace_back(ManifestError::FieldDuplicate, fieldInfo.Name, "", m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column); } if (!valueNode.IsNull()) { - auto errors = fieldInfo.ProcessFunc(valueNode); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + try + { + auto errors = fieldInfo.ProcessFunc(valueNode); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + } + catch (const std::exception&) + { + resultErrors.emplace_back(ManifestError::FieldFailedToProcess, fieldInfo.Name); + } } } else @@ -434,7 +442,7 @@ namespace AppInstaller::Manifest // For full validation, also reports unrecognized fields as warning if (m_fullValidation) { - resultErrors.emplace_back(ManifestError::FieldUnknown, key, "", keyValuePair.first.Mark().line, keyValuePair.first.Mark().column, ValidationError::Level::Warning); + resultErrors.emplace_back(ManifestError::FieldUnknown, key, "", m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column, ValidationError::Level::Warning); } } } @@ -442,22 +450,6 @@ namespace AppInstaller::Manifest return resultErrors; } - ValidationErrors ManifestYamlPopulator::ProcessLocalizationNode(const YAML::Node& rootNode, std::vector& localizations) - { - ValidationErrors resultErrors; - - for (auto const& entry : rootNode.Sequence()) - { - ManifestLocalization localization; - m_p_localization = &localization; - auto errors = ValidateAndProcessFields(entry, LocalizationFieldInfos); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - localizations.emplace_back(std::move(std::move(localization))); - } - - return resultErrors; - } - ValidationErrors ManifestYamlPopulator::ProcessPackageDependenciesNode(const YAML::Node& rootNode, std::vector& packageDependencies) { ValidationErrors resultErrors; @@ -478,6 +470,7 @@ namespace AppInstaller::Manifest { ValidationErrors resultErrors; m_fullValidation = fullValidation; + m_isMergedManifest = (rootNode["ManifestType"sv].as() == "merged"); manifest.ManifestVersion = manifestVersion; // Prepare field infos @@ -489,15 +482,18 @@ namespace AppInstaller::Manifest LocalizationFieldInfos = GetLocalizationFieldProcessInfo(manifestVersion); // Populate root - YAML::Node installersNode; - m_p_installersNode = &installersNode; m_p_manifest = &manifest; m_p_installer = &(manifest.DefaultInstallerInfo); m_p_localization = &(manifest.DefaultLocalization); resultErrors = ValidateAndProcessFields(rootNode, RootFieldInfos); + if (!m_p_installersNode) + { + return resultErrors; + } + // Populate installers - for (auto const& entry : installersNode.Sequence()) + for (auto const& entry : m_p_installersNode->Sequence()) { ManifestInstaller installer = manifest.DefaultInstallerInfo; @@ -520,19 +516,29 @@ namespace AppInstaller::Manifest installer.ProductCode = manifest.DefaultInstallerInfo.ProductCode; } + // Populate installer default switches if not exists + auto defaultSwitches = GetDefaultKnownSwitches(installer.InstallerType); + for (auto const& defaultSwitch : defaultSwitches) + { + if (installer.Switches.find(defaultSwitch.first) == installer.Switches.end()) + { + installer.Switches[defaultSwitch.first] = defaultSwitch.second; + } + } + manifest.Installers.emplace_back(std::move(installer)); } - // Populate installer default switches if not exists - for (auto& installer : manifest.Installers) + // Populate additional localizations + if (m_p_localizationsNode && m_p_localizationsNode->IsSequence()) { - auto defaultSwitches = GetDefaultKnownSwitches(installer.InstallerType); - for (auto const& entry : defaultSwitches) + for (auto const& entry : m_p_localizationsNode->Sequence()) { - if (installer.Switches.find(entry.first) == installer.Switches.end()) - { - installer.Switches[entry.first] = entry.second; - } + ManifestLocalization localization; + m_p_localization = &localization; + auto errors = ValidateAndProcessFields(entry, LocalizationFieldInfos); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + manifest.Localizations.emplace_back(std::move(std::move(localization))); } } diff --git a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp index d8004a8836..d7f9fe2f53 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp @@ -55,6 +55,8 @@ namespace AppInstaller::Manifest::YamlParser std::string packageId = firstYamlManifest.Root["PackageIdentifier"].as(); std::string packageVersion = firstYamlManifest.Root["PackageVersion"].as(); + std::set localesSet; + bool isVersionManifestFound = false; bool isInstallerManifestFound = false; bool isDefaultLocaleManifestFound = false; @@ -127,11 +129,33 @@ namespace AppInstaller::Manifest::YamlParser else { isDefaultLocaleManifestFound = true; - defaultLocaleFromDefaultLocaleManifest = entry.Root["PackageLocale"sv].as(); + auto packageLocale = entry.Root["PackageLocale"sv].as(); + defaultLocaleFromDefaultLocaleManifest = packageLocale; + + if (localesSet.find(packageLocale) != localesSet.end()) + { + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::DuplicateMultiFileManifestLocale, "PackageLocale", packageLocale, entry.FileName)); + } + else + { + localesSet.insert(packageLocale); + } } break; case ManifestTypeEnum::Locale: - // Nothing to validate + { + auto packageLocale = entry.Root["PackageLocale"sv].as(); + if (localesSet.find(packageLocale) != localesSet.end()) + { + errors.emplace_back(ValidationError::MessageFieldValueWithFile( + ManifestError::DuplicateMultiFileManifestLocale, "PackageLocale", packageLocale, entry.FileName)); + } + else + { + localesSet.insert(packageLocale); + } + } break; default: errors.emplace_back(ValidationError::MessageFieldValueWithFile( @@ -328,12 +352,13 @@ namespace AppInstaller::Manifest::YamlParser const YAML::Node& manifestDoc = (input.size() > 1) ? MergeMultiFileManifest(input) : input[0].Root; - ManifestYamlPopulator::PopulateManifest(manifestDoc, manifest, manifestVersion, fullValidation); + auto errors = ManifestYamlPopulator::PopulateManifest(manifestDoc, manifest, manifestVersion, fullValidation); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); // Extra semantic validations after basic validation and field population if (fullValidation) { - auto errors = ValidateManifest(manifest); + errors = ValidateManifest(manifest); std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); } diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 80358080dc..64be7711bb 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -4,6 +4,8 @@ #include #include +#include + namespace AppInstaller::Manifest { using string_t = Utility::NormalizedString; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h index 58a6d5e7d4..4128d444d0 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -44,7 +44,7 @@ namespace AppInstaller::Manifest // If present, has more precedence than root InstallerTypeEnum InstallerType = InstallerTypeEnum::Unknown; - ScopeEnum Scope; + ScopeEnum Scope = ScopeEnum::User; std::vector InstallModes; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h index e321ebfad2..580cae6e7a 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h @@ -34,8 +34,10 @@ namespace AppInstaller::Manifest const char* const IncompleteMultiFileManifest = "The multi file manifest is incomplete."; const char* const InconsistentMultiFileManifestFieldValue = "The multi file manifest has inconsistent field values."; const char* const DuplicateMultiFileManifestType = "The multi file manifest should contain only one file with the particular ManifestType."; + const char* const DuplicateMultiFileManifestLocale = "The multi file manifest contains duplicate PackageLocale."; const char* const UnsupportedMultiFileManifestType = "The multi file manifest should not contain file with the particular ManifestType."; const char* const InconsistentMultiFileManifestDefaultLocale = "DefaultLocale value in version manifest does not match PackageLocale value in defaultLocale manifest."; + const char* const FieldFailedToProcess = "Failed to process field."; } struct ValidationError @@ -141,6 +143,10 @@ namespace AppInstaller::Manifest { m_manifestErrorMessage += " Line: " + std::to_string(error.Line) + ", Column: " + std::to_string(error.Column); } + if (!error.FileName.empty()) + { + m_manifestErrorMessage += " File: " + error.FileName; + } m_manifestErrorMessage += '\n'; } } diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h index 1b7444f5d9..ff5a042a53 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h @@ -23,6 +23,7 @@ namespace AppInstaller::Manifest }; bool m_fullValidation = false; + bool m_isMergedManifest = false; std::vector RootFieldInfos; std::vector InstallerFieldInfos; @@ -39,8 +40,9 @@ namespace AppInstaller::Manifest AppInstaller::Manifest::PackageDependency* m_p_packageDependency = nullptr; AppInstaller::Manifest::ManifestLocalization* m_p_localization = nullptr; - // Cache of Installers node. This needs to be processed after other fields in package root for default installer values - YAML::Node* m_p_installersNode; + // Cache of Installers node and Localization node + YAML::Node const* m_p_installersNode = nullptr; + YAML::Node const* m_p_localizationsNode = nullptr; std::vector GetRootFieldProcessInfo(const ManifestVer& manifestVersion); std::vector GetInstallerFieldProcessInfo(const ManifestVer& manifestVersion, bool forRootFields = false); @@ -57,7 +59,6 @@ namespace AppInstaller::Manifest const YAML::Node& rootNode, const std::vector& fieldInfos); - std::vector ProcessLocalizationNode(const YAML::Node& rootNode, std::vector& localizations); std::vector ProcessPackageDependenciesNode(const YAML::Node& rootNode, std::vector& packageDependencies); std::vector PopulateManifestInternal(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, bool fullValidation); diff --git a/src/WinGetUtil/Exports.cpp b/src/WinGetUtil/Exports.cpp index 0e416a760c..8082221cd8 100644 --- a/src/WinGetUtil/Exports.cpp +++ b/src/WinGetUtil/Exports.cpp @@ -173,16 +173,18 @@ extern "C" CATCH_RETURN() WINGET_UTIL_API WinGetValidateManifest( - WINGET_STRING manifestPath, + WINGET_STRING inputPath, BOOL* succeeded, - WINGET_STRING_OUT* message) try + WINGET_STRING_OUT* message, + WINGET_STRING mergedManifestPath, + BOOL isPartialManifest) try { - THROW_HR_IF(E_INVALIDARG, !manifestPath); + THROW_HR_IF(E_INVALIDARG, !inputPath); THROW_HR_IF(E_INVALIDARG, !succeeded); try { - (void)YamlParser::CreateFromPath(manifestPath, true, true); + (void)YamlParser::CreateFromPath(inputPath, true, true, L"WinGetUtil.dll", mergedManifestPath, isPartialManifest); *succeeded = TRUE; } catch (const ManifestException& e) diff --git a/src/WinGetUtil/WinGetUtil.h b/src/WinGetUtil/WinGetUtil.h index e4892969e0..448a758fde 100644 --- a/src/WinGetUtil/WinGetUtil.h +++ b/src/WinGetUtil/WinGetUtil.h @@ -76,9 +76,11 @@ extern "C" // Validates a given manifest. Returns a bool for validation result and // a string representing validation errors if validation failed. WINGET_UTIL_API WinGetValidateManifest( - WINGET_STRING manifestPath, + WINGET_STRING inputPath, BOOL* succeeded, - WINGET_STRING_OUT* message); + WINGET_STRING_OUT* message, + WINGET_STRING mergedManifestPath, + BOOL isPartialManifest); // Downloads a file to the given path, returning the SHA 256 hash of the file. WINGET_UTIL_API WinGetDownload( diff --git a/src/WinGetUtil/WinGetUtil.vcxproj b/src/WinGetUtil/WinGetUtil.vcxproj index ec5ff0d1af..801b1f1594 100644 --- a/src/WinGetUtil/WinGetUtil.vcxproj +++ b/src/WinGetUtil/WinGetUtil.vcxproj @@ -70,6 +70,7 @@ + From e4595e050f42c071bf83cde525322f65f791a5b5 Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Tue, 2 Feb 2021 10:16:16 -0800 Subject: [PATCH 10/18] new tests --- .../AppInstallerCLITests.vcxproj | 15 ++ .../AppInstallerCLITests.vcxproj.filters | 15 ++ .../ManifestV1-MultiFile-DefaultLocale.yaml | 23 +++ .../ManifestV1-MultiFile-Installer.yaml | 109 ++++++++++++ .../TestData/ManifestV1-MultiFile-Locale.yaml | 22 +++ .../ManifestV1-MultiFile-Version.yaml | 6 + .../TestData/ManifestV1-Singleton.yaml | 122 +++++++++++++ src/AppInstallerCLITests/YamlManifest.cpp | 166 +++++++++++++++++- .../Manifest/ManifestYamlPopulator.cpp | 1 + 9 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-DefaultLocale.yaml create mode 100644 src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Installer.yaml create mode 100644 src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Locale.yaml create mode 100644 src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Version.yaml create mode 100644 src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 02caffba88..f499402546 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -430,6 +430,21 @@ true + + true + + + true + + + true + + + true + + + true + diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index e2ae3ab86a..3ad48a398c 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -330,5 +330,20 @@ TestData + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-DefaultLocale.yaml b/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-DefaultLocale.yaml new file mode 100644 index 0000000000..09e4ceb365 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-DefaultLocale.yaml @@ -0,0 +1,23 @@ +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "sdkformsix" + +ManifestType: defaultLocale +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Installer.yaml new file mode 100644 index 0000000000..cacfe0064c --- /dev/null +++ b/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Installer.yaml @@ -0,0 +1,109 @@ +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +Channel: release +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: zip +Scope: machine +InstallModes: + - user + - machine +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" + +Installers: + - Architecture: x86 + Channel: preview + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - user + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + - Architecture: x64 + InstallerType: exe + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + ProductCode: "{Bar}" + +ManifestType: installer +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Locale.yaml b/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Locale.yaml new file mode 100644 index 0000000000..8f03e4f05c --- /dev/null +++ b/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Locale.yaml @@ -0,0 +1,22 @@ +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-GB +Publisher: Microsoft UK +PublisherUrl: https://www.microsoft.com/UK +PublisherSupportUrl: https://www.microsoft.com/support/UK +PrivacyUrl: https://www.microsoft.com/privacy/UK +Author: Microsoft UK +PackageName: MSIX SDK UK +PackageUrl: https://www.microsoft.com/msixsdk/home/UK +License: MIT License UK +LicenseUrl: https://www.microsoft.com/msixsdk/license/UK +Copyright: Copyright Microsoft Corporation UK +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright/UK +ShortDescription: This is MSIX SDK UK +Description: The MSIX SDK project is an effort to enable developers UK +Tags: + - "appxsdkUK" + - "sdkformsixUK" + +ManifestType: locale +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Version.yaml b/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Version.yaml new file mode 100644 index 0000000000..34bd203939 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Version.yaml @@ -0,0 +1,6 @@ +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +DefaultLocale: en-US + +ManifestType: version +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml new file mode 100644 index 0000000000..4c61dfa753 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml @@ -0,0 +1,122 @@ +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "sdkformsix" +Channel: release +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: zip +Scope: machine +InstallModes: + - user + - machine +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" + +Installers: + - Architecture: x86 + Channel: preview + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - user + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + +ManifestType: singleton +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 3a3c659b54..652c255a1c 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -148,7 +148,7 @@ struct ManifestExceptionMatcher : public Catch::MatcherBase virtual std::string describe() const override { std::ostringstream ss; - ss << std::boolalpha << "Expected exception message: " << m_expectedMessage << "Expected IsWarningOnly: " << m_expectedWarningOnly; + ss << std::boolalpha << "Expected exception message: " << m_expectedMessage << " Expected IsWarningOnly: " << m_expectedWarningOnly; return ss.str(); } @@ -317,3 +317,167 @@ TEST_CASE("ManifestVersionExtensions", "[ManifestValidation]") REQUIRE(ManifestVer("1.0.0-other-msstore.2"sv).HasExtension("msstore")); REQUIRE(ManifestVer("1.0.0-msstore.2-other"sv).HasExtension("msstore")); } + +void CopyTestDataFilesToFolder(const std::vector& testDataFiles, const std::filesystem::path& dest) +{ + for (const auto& fileName : testDataFiles) + { + std::filesystem::copy(TestDataFile(fileName), dest); + } +} + +void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton) +{ + REQUIRE(manifest.Id == "microsoft.msixsdk"); + REQUIRE(manifest.Version == "1.7.32"); + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/support"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/privacy"); + REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); + REQUIRE(manifest.DefaultLocalization.Get() == "MSIX SDK"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/msixsdk/home"); + REQUIRE(manifest.DefaultLocalization.Get() == "MIT License"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/msixsdk/license"); + REQUIRE(manifest.DefaultLocalization.Get() == "Copyright Microsoft Corporation"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/msixsdk/copyright"); + REQUIRE(manifest.DefaultLocalization.Get() == "This is MSIX SDK"); + REQUIRE(manifest.DefaultLocalization.Get() == "The MSIX SDK project is an effort to enable developers"); + REQUIRE(manifest.Moniker == "msixsdk"); + REQUIRE(manifest.DefaultLocalization.Get() == MultiValue{ "appxsdk", "sdkformsix" }); + REQUIRE(manifest.DefaultInstallerInfo.Channel == "release"); + REQUIRE(manifest.DefaultInstallerInfo.Locale == "en-US"); + REQUIRE(manifest.DefaultInstallerInfo.Platform == std::vector{ PlatformEnum::Desktop, PlatformEnum::Universal }); + REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "10.0.0.0"); + REQUIRE(manifest.DefaultInstallerInfo.InstallerType == InstallerTypeEnum::Zip); + REQUIRE(manifest.DefaultInstallerInfo.Scope == ScopeEnum::Machine); + REQUIRE(manifest.DefaultInstallerInfo.InstallModes == std::vector{ ScopeEnum::User ,ScopeEnum::Machine }); + + auto defaultSwitches = manifest.DefaultInstallerInfo.Switches; + REQUIRE(defaultSwitches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::Silent) == "/silence"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::Interactive) == "/interactive"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::Log) == "/log="); + REQUIRE(defaultSwitches.at(InstallerSwitchType::InstallLocation) == "/dir="); + REQUIRE(defaultSwitches.at(InstallerSwitchType::Update) == "/upgrade"); + + REQUIRE(manifest.DefaultInstallerInfo.InstallerSuccessCodes == std::vector{ 1, static_cast(0x80070005) }); + REQUIRE(manifest.DefaultInstallerInfo.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious); + REQUIRE(manifest.DefaultInstallerInfo.Commands == MultiValue{ "makemsix", "makeappx" }); + REQUIRE(manifest.DefaultInstallerInfo.Protocols == MultiValue{ "protocol1", "protocol2" }); + REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "msix", "appxbundle", "msixbundle" }); + + auto dependencies = manifest.DefaultInstallerInfo.Dependencies; + REQUIRE(dependencies.WindowsFeatures == MultiValue{ "IIS" }); + REQUIRE(dependencies.WindowsLibraries == MultiValue{ "VC Runtime" }); + REQUIRE(dependencies.PackageDependencies.size() == 1); + REQUIRE(dependencies.PackageDependencies[0].Id == "Microsoft.MsixSdkDep"); + REQUIRE(dependencies.PackageDependencies[0].MinVersion == "1.0.0"); + REQUIRE(dependencies.ExternalDependencies == MultiValue{ "Outside dependencies" }); + + REQUIRE(manifest.DefaultInstallerInfo.Capabilities == MultiValue{ "internetClient" }); + REQUIRE(manifest.DefaultInstallerInfo.RestrictedCapabilities == MultiValue{ "runFullTrust" }); + REQUIRE(manifest.DefaultInstallerInfo.PackageFamilyName == "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); + REQUIRE(manifest.DefaultInstallerInfo.ProductCode == "{Foo}"); + + if (isSingleton) + { + REQUIRE(manifest.Installers.size() == 1); + } + else + { + REQUIRE(manifest.Installers.size() == 2); + } + + ManifestInstaller installer1 = manifest.Installers.at(0); + REQUIRE(installer1.Arch == Architecture::X86); + REQUIRE(installer1.Channel == "preview"); + REQUIRE(installer1.Locale == "en-GB"); + REQUIRE(installer1.Platform == std::vector{ PlatformEnum::Desktop }); + REQUIRE(installer1.MinOSVersion == "10.0.1.0"); + REQUIRE(installer1.InstallerType == InstallerTypeEnum::Msix); + REQUIRE(installer1.Url == "https://www.microsoft.com/msixsdk/msixsdkx86.msix"); + REQUIRE(installer1.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer1.SignatureSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer1.Scope == ScopeEnum::User); + REQUIRE(installer1.InstallModes == std::vector{ ScopeEnum::User }); + + auto installer1Switches = installer1.Switches; + REQUIRE(installer1Switches.at(InstallerSwitchType::Custom) == "/c"); + REQUIRE(installer1Switches.at(InstallerSwitchType::SilentWithProgress) == "/sp"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Log) == "/l="); + REQUIRE(installer1Switches.at(InstallerSwitchType::InstallLocation) == "/d="); + REQUIRE(installer1Switches.at(InstallerSwitchType::Update) == "/u"); + + REQUIRE(installer1.UpdateBehavior == UpdateBehaviorEnum::Install); + REQUIRE(installer1.Commands == MultiValue{ "makemsixPreview", "makeappxPreview" }); + REQUIRE(installer1.Protocols == MultiValue{ "protocol1preview", "protocol2preview" }); + REQUIRE(installer1.FileExtensions == MultiValue{ "appxbundle", "msixbundle", "appx", "msix" }); + + auto installer1Dependencies = installer1.Dependencies; + REQUIRE(installer1Dependencies.WindowsFeatures == MultiValue{ "PreviewIIS" }); + REQUIRE(installer1Dependencies.WindowsLibraries == MultiValue{ "Preview VC Runtime" }); + REQUIRE(installer1Dependencies.PackageDependencies.size() == 1); + REQUIRE(installer1Dependencies.PackageDependencies[0].Id == "Microsoft.MsixSdkDepPreview"); + REQUIRE(installer1Dependencies.ExternalDependencies == MultiValue{ "Preview Outside dependencies" }); + + REQUIRE(installer1.Capabilities == MultiValue{ "internetClientPreview" }); + REQUIRE(installer1.RestrictedCapabilities == MultiValue{ "runFullTrustPreview" }); + REQUIRE(installer1.PackageFamilyName == "Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe"); + + if (!isSingleton) + { + ManifestInstaller installer2 = manifest.Installers.at(1); + REQUIRE(installer2.Arch == Architecture::X64); + REQUIRE(installer2.InstallerType == InstallerTypeEnum::Exe); + REQUIRE(installer2.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); + REQUIRE(installer2.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer2.ProductCode == "{Bar}"); + + // Localization + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization localization1 = manifest.Localizations.at(0); + REQUIRE(localization1.Locale == "en-GB"); + REQUIRE(localization1.Get() == "Microsoft UK"); + REQUIRE(localization1.Get() == "https://www.microsoft.com/UK"); + REQUIRE(localization1.Get() == "https://www.microsoft.com/support/UK"); + REQUIRE(localization1.Get() == "https://www.microsoft.com/privacy/UK"); + REQUIRE(localization1.Get() == "Microsoft UK"); + REQUIRE(localization1.Get() == "MSIX SDK UK"); + REQUIRE(localization1.Get() == "https://www.microsoft.com/msixsdk/home/UK"); + REQUIRE(localization1.Get() == "MIT License UK"); + REQUIRE(localization1.Get() == "https://www.microsoft.com/msixsdk/license/UK"); + REQUIRE(localization1.Get() == "Copyright Microsoft Corporation UK"); + REQUIRE(localization1.Get() == "https://www.microsoft.com/msixsdk/copyright/UK"); + REQUIRE(localization1.Get() == "This is MSIX SDK UK"); + REQUIRE(localization1.Get() == "The MSIX SDK project is an effort to enable developers UK"); + REQUIRE(localization1.Get() == MultiValue{ "appxsdkUK", "sdkformsixUK" }); + } +} + +TEST_CASE("ValidateV1GoodManifestAndVerifyContents", "[ManifestValidation]") +{ + TempDirectory singletonDirectory{ "SingletonManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1-Singleton.yaml" }, singletonDirectory); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, true, true); + VerifyV1ManifestContent(singletonManifest, true); + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1-MultiFile-Version.yaml", + "ManifestV1-MultiFile-Installer.yaml", + "ManifestV1-MultiFile-DefaultLocale.yaml", + "ManifestV1-MultiFile-Locale.yaml" }, multiFileDirectory); + + TempFile mergedManifestFile{ "merged.yaml" }; + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, true, true, nullptr, mergedManifestFile); + VerifyV1ManifestContent(multiFileManifest, false); + + // Read from merged manifest should have the same content as multi file manifest + Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); + VerifyV1ManifestContent(mergedManifest, false); +} \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index c58b7b7a39..b250408a27 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -454,6 +454,7 @@ namespace AppInstaller::Manifest { ValidationErrors resultErrors; + packageDependencies.clear(); for (auto const& entry : rootNode.Sequence()) { PackageDependency packageDependency; From 77b40dff7de410ad0232dc3c025a82b02852cee7 Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Tue, 2 Feb 2021 13:36:25 -0800 Subject: [PATCH 11/18] Release build and spell checking fix --- .github/actions/spelling/allow.txt | 16 ++++++++++++++++ .github/actions/spelling/patterns.txt | 1 + src/AppInstallerCommonCore/pch.h | 3 +-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index e1eff4a27e..5528a6fc78 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -13,6 +13,7 @@ apps appx appxbundle appxmanifest +appxsdk APSTUDIO argc args @@ -68,6 +69,7 @@ dbconn DBId declspec decltype +defaultLocale delstore Demitrius denelon @@ -127,14 +129,18 @@ getline github githubusercontent hfile +HGLOBAL hinternet HKEY hmac +HMODULE +homepage Homepage hostname hpp HRESULT hresult +HRSRC hstring html http @@ -145,11 +151,13 @@ IAsync IBuffer icu IDisposable +IDX ifdef ifndef ifstream IInput IInspectable +IIS ILogger impl Inet @@ -187,7 +195,9 @@ LPVOID mailto MAJORVERSION makeappx +MAKEINTRESOURCE makemsix +MANIFESTSCHEMA MANIFESTVERSION mday metadata @@ -208,6 +218,7 @@ msixsdk msixsdkx msixtest msrc +Multifile Multimatch mutex namespace @@ -242,11 +253,13 @@ ostringstream OSVERSIONINFOEXW outfile OUTOFMEMORY +OWC parentidx pathpart Pathto PBYTE pch +PCWSTR pdb PEVENT pfp @@ -271,6 +284,7 @@ ptr publiccontainer PUCHAR PVOID +pwa QCol RAII rdbuf @@ -430,6 +444,8 @@ usersources utext utf uuidof +validator +valijson vcxitems vcxproj vdproj diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt index a50481a79e..28c5000218 100644 --- a/.github/actions/spelling/patterns.txt +++ b/.github/actions/spelling/patterns.txt @@ -17,3 +17,4 @@ data:[a-zA-Z=;,/0-9+-]+ El proyecto .* diferentes # Package family names \b[-.A-Za-z0-9]+_[a-z0-9]{13}\b +"pattern": \b.*\b diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h index 97d66c7f8f..0fad9653b6 100644 --- a/src/AppInstallerCommonCore/pch.h +++ b/src/AppInstallerCommonCore/pch.h @@ -18,9 +18,8 @@ #include -#include #pragma warning( push ) -#pragma warning ( disable : 4458 4100 ) +#pragma warning ( disable : 4458 4100 4702 ) #include #include #include From 42c6b10444f9c9d3e09178acc7234c4761e233b7 Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Tue, 2 Feb 2021 14:33:32 -0800 Subject: [PATCH 12/18] More spell check fix --- .github/actions/spelling/allow.txt | 2 +- .github/actions/spelling/patterns.txt | 3 ++- .../TestData/ManifestV1-MultiFile-DefaultLocale.yaml | 2 +- .../TestData/ManifestV1-MultiFile-Locale.yaml | 2 +- src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml | 2 +- src/AppInstallerCLITests/YamlManifest.cpp | 4 ++-- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 5528a6fc78..201a7cf985 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -69,7 +69,7 @@ dbconn DBId declspec decltype -defaultLocale +defaultlocale delstore Demitrius denelon diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt index 28c5000218..cc5dd8900b 100644 --- a/.github/actions/spelling/patterns.txt +++ b/.github/actions/spelling/patterns.txt @@ -17,4 +17,5 @@ data:[a-zA-Z=;,/0-9+-]+ El proyecto .* diferentes # Package family names \b[-.A-Za-z0-9]+_[a-z0-9]{13}\b -"pattern": \b.*\b +# schema regex +"pattern": .*$ diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-DefaultLocale.yaml b/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-DefaultLocale.yaml index 09e4ceb365..dfd1c3e69a 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-DefaultLocale.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-DefaultLocale.yaml @@ -17,7 +17,7 @@ Description: The MSIX SDK project is an effort to enable developers Moniker: msixsdk Tags: - "appxsdk" - - "sdkformsix" + - "msixsdk" ManifestType: defaultLocale ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Locale.yaml b/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Locale.yaml index 8f03e4f05c..9fe96f8a44 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Locale.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Locale.yaml @@ -16,7 +16,7 @@ ShortDescription: This is MSIX SDK UK Description: The MSIX SDK project is an effort to enable developers UK Tags: - "appxsdkUK" - - "sdkformsixUK" + - "msixsdkUK" ManifestType: locale ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml index 4c61dfa753..83b1e5b995 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml @@ -17,7 +17,7 @@ Description: The MSIX SDK project is an effort to enable developers Moniker: msixsdk Tags: - "appxsdk" - - "sdkformsix" + - "msixsdk" Channel: release InstallerLocale: en-US Platform: diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 652c255a1c..8d76fd20a3 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -345,7 +345,7 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton) REQUIRE(manifest.DefaultLocalization.Get() == "This is MSIX SDK"); REQUIRE(manifest.DefaultLocalization.Get() == "The MSIX SDK project is an effort to enable developers"); REQUIRE(manifest.Moniker == "msixsdk"); - REQUIRE(manifest.DefaultLocalization.Get() == MultiValue{ "appxsdk", "sdkformsix" }); + REQUIRE(manifest.DefaultLocalization.Get() == MultiValue{ "appxsdk", "msixsdk" }); REQUIRE(manifest.DefaultInstallerInfo.Channel == "release"); REQUIRE(manifest.DefaultInstallerInfo.Locale == "en-US"); REQUIRE(manifest.DefaultInstallerInfo.Platform == std::vector{ PlatformEnum::Desktop, PlatformEnum::Universal }); @@ -455,7 +455,7 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton) REQUIRE(localization1.Get() == "https://www.microsoft.com/msixsdk/copyright/UK"); REQUIRE(localization1.Get() == "This is MSIX SDK UK"); REQUIRE(localization1.Get() == "The MSIX SDK project is an effort to enable developers UK"); - REQUIRE(localization1.Get() == MultiValue{ "appxsdkUK", "sdkformsixUK" }); + REQUIRE(localization1.Get() == MultiValue{ "appxsdkUK", "msixsdkUK" }); } } From 16c1cfd2aada9b9552cfbe5d30d09317243ed362 Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Tue, 2 Feb 2021 16:03:04 -0800 Subject: [PATCH 13/18] Fix test failure --- azure-pipelines.yml | 4 ++-- src/AppInstallerCLI.sln | 1 + src/AppInstallerCLIE2ETests/HashCommand.cs | 2 +- .../Manifest/ManifestYamlPopulator.cpp | 8 ++++++-- src/WinGetYamlFuzzing/WinGetYamlFuzzing.vcxproj | 1 + 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0d304bbd24..157fb6f821 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -201,13 +201,13 @@ jobs: name: AppInstallerTest displayName: 'Download Source Package Certificate' inputs: - secureFile: 'AppInstallerTest.pfx' + secureFile: 'AppInstallerTest.pfx' - task: DownloadSecureFile@1 name: HTTPSDevCert displayName: 'Download Kestrel Certificate' inputs: - secureFile: 'HTTPSDevCert.pfx' + secureFile: 'HTTPSDevCert.pfx' - task: MSBuild@1 displayName: Build MSIX Test Installer File diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index 4731df4fe8..3aeab12573 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -69,6 +69,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ManifestSchema", "ManifestS EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution + ManifestSchema\ManifestSchema.vcxitems*{1622da16-914f-4f57-a259-d5169003cc8c}*SharedItemsImports = 4 Valijson\Valijson.vcxitems*{358bc478-0624-4ad1-a933-0422b5292af8}*SharedItemsImports = 9 catch2\catch2.vcxitems*{5295e21e-9868-4de2-a177-fbb97b36579b}*SharedItemsImports = 9 ManifestSchema\ManifestSchema.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 diff --git a/src/AppInstallerCLIE2ETests/HashCommand.cs b/src/AppInstallerCLIE2ETests/HashCommand.cs index f8525ed3ba..6d5435bec0 100644 --- a/src/AppInstallerCLIE2ETests/HashCommand.cs +++ b/src/AppInstallerCLIE2ETests/HashCommand.cs @@ -39,7 +39,7 @@ public void HashFileNotFound() { var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("DoesNot.Exist")); Assert.AreEqual(Constants.ErrorCode.ERROR_FILE_NOT_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("File does not exist")); + Assert.True(result.StdOut.Contains("Path does not exist")); } } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index b250408a27..ff9d80914f 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -469,9 +469,13 @@ namespace AppInstaller::Manifest ValidationErrors ManifestYamlPopulator::PopulateManifestInternal(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, bool fullValidation) { - ValidationErrors resultErrors; m_fullValidation = fullValidation; - m_isMergedManifest = (rootNode["ManifestType"sv].as() == "merged"); + if (manifestVersion.Major() >= 1) + { + m_isMergedManifest = (rootNode["ManifestType"sv].as() == "merged"); + } + + ValidationErrors resultErrors; manifest.ManifestVersion = manifestVersion; // Prepare field infos diff --git a/src/WinGetYamlFuzzing/WinGetYamlFuzzing.vcxproj b/src/WinGetYamlFuzzing/WinGetYamlFuzzing.vcxproj index c7a2e474b8..72a24a78df 100644 --- a/src/WinGetYamlFuzzing/WinGetYamlFuzzing.vcxproj +++ b/src/WinGetYamlFuzzing/WinGetYamlFuzzing.vcxproj @@ -25,6 +25,7 @@ + From c9b16aa46a149bc281a0e1756e22cf6f86fc9b77 Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Wed, 3 Feb 2021 12:46:15 -0800 Subject: [PATCH 14/18] Add more tests and e2e test fix and add some comments --- .../Workflows/ShowFlow.cpp | 1 + src/AppInstallerCLIE2ETests/Constants.cs | 1 + src/AppInstallerCLIE2ETests/InstallCommand.cs | 3 +- .../ValidateCommand.cs | 2 +- src/AppInstallerCLITests/YamlManifest.cpp | 95 ++++++++++++++++++- .../Manifest/ManifestSchemaValidation.cpp | 5 +- .../Manifest/ManifestYamlPopulator.cpp | 6 +- .../Manifest/YamlParser.cpp | 15 ++- .../Public/winget/Manifest.h | 4 + .../Public/winget/ManifestCommon.h | 1 + .../Public/winget/ManifestInstaller.h | 2 + .../Public/winget/ManifestLocalization.h | 11 +-- .../Public/winget/ManifestSchemaValidation.h | 3 + .../Public/winget/ManifestYamlParser.h | 18 +++- .../Public/winget/ManifestYamlPopulator.h | 7 +- src/AppInstallerCommonCore/Yaml.cpp | 1 + src/ManifestSchema/resource.h | 2 +- 17 files changed, 153 insertions(+), 24 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp index 0b4dc42bd5..bdfaef241a 100644 --- a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -80,6 +80,7 @@ namespace AppInstaller::CLI::Workflow { const auto& manifest = context.Get(); + // Channel is moved to installer level, get all channels from all installers std::set channels; for (const auto& installer : manifest.Installers) { diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index abce1b5929..a5d7809af6 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -57,6 +57,7 @@ public class ErrorCode { public const int S_OK = 0; public const int ERROR_FILE_NOT_FOUND = unchecked((int)0x80070002); + public const int ERROR_PATH_NOT_FOUND = unchecked((int)0x80070003); public const int ERROR_NO_RANGES_PROCESSED = unchecked((int)0x80070138); public const int OPC_E_ZIP_MISSING_END_OF_CENTRAL_DIRECTORY = unchecked((int)0x8051100f); public const int ERROR_OLD_WIN_VERSION = unchecked((int)0x8007047e); diff --git a/src/AppInstallerCLIE2ETests/InstallCommand.cs b/src/AppInstallerCLIE2ETests/InstallCommand.cs index 99630b2812..8223012463 100644 --- a/src/AppInstallerCLIE2ETests/InstallCommand.cs +++ b/src/AppInstallerCLIE2ETests/InstallCommand.cs @@ -44,7 +44,8 @@ public void InstallExeWithInsufficientMinOsVersion() { var installDir = TestCommon.GetRandomTestDir(); var result = TestCommon.RunAICLICommand("install", $"InapplicableOsVersion --silent -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_OLD_WIN_VERSION, result.ExitCode); + // MinOSVersion is moved to installer level, the check is performed during installer selection + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICABLE_INSTALLER, result.ExitCode); Assert.True(result.StdOut.Contains("Cannot install package, as it requires a higher version of Windows")); Assert.False(VerifyTestExeInstalled(installDir)); } diff --git a/src/AppInstallerCLIE2ETests/ValidateCommand.cs b/src/AppInstallerCLIE2ETests/ValidateCommand.cs index 59d4abc218..78ec4aef9e 100644 --- a/src/AppInstallerCLIE2ETests/ValidateCommand.cs +++ b/src/AppInstallerCLIE2ETests/ValidateCommand.cs @@ -35,7 +35,7 @@ public void ValidateInvalidManifest() public void ValidateManifestDoesNotExist() { var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\DoesNotExist")); - Assert.AreEqual(Constants.ErrorCode.ERROR_FILE_NOT_FOUND, result.ExitCode); + Assert.AreEqual(Constants.ErrorCode.ERROR_PATH_NOT_FOUND, result.ExitCode); Assert.True(result.StdOut.Contains("File does not exist")); } } diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 8d76fd20a3..7236334c3a 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -4,10 +4,13 @@ #include "TestCommon.h" #include #include +#include using namespace TestCommon; using namespace AppInstaller::Manifest; +using namespace AppInstaller::Manifest::YamlParser; using namespace AppInstaller::Utility; +using namespace AppInstaller::YAML; using MultiValue = std::vector; bool operator==(const MultiValue& a, const MultiValue& b) @@ -136,7 +139,7 @@ TEST_CASE("ReadGoodManifestWithSpaces", "[ManifestValidation]") struct ManifestExceptionMatcher : public Catch::MatcherBase { - ManifestExceptionMatcher(std::string expectedMessage, bool expectedWarningOnly) : + ManifestExceptionMatcher(std::string expectedMessage, bool expectedWarningOnly = false) : m_expectedMessage(expectedMessage), m_expectedWarningOnly(expectedWarningOnly) {} // Performs the test for this matcher @@ -480,4 +483,94 @@ TEST_CASE("ValidateV1GoodManifestAndVerifyContents", "[ManifestValidation]") // Read from merged manifest should have the same content as multi file manifest Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); VerifyV1ManifestContent(mergedManifest, false); +} + +YamlManifestInfo CreateYamlManifestInfo(std::string testDataFile) +{ + YamlManifestInfo result; + result.Root = AppInstaller::YAML::Load(TestDataFile(testDataFile)); + result.FileName = testDataFile; + return result; +} + +TEST_CASE("MultifileManifestInputValidation", "[ManifestValidation]") +{ + auto previewManifest = CreateYamlManifestInfo("Manifest-Good.yaml"); + auto v1SingletonManifest = CreateYamlManifestInfo("ManifestV1-Singleton.yaml"); + auto v1VersionManifest = CreateYamlManifestInfo("ManifestV1-MultiFile-Version.yaml"); + auto v1InstallerManifest = CreateYamlManifestInfo("ManifestV1-MultiFile-Installer.yaml"); + auto v1DefaultLocaleManifest = CreateYamlManifestInfo("ManifestV1-MultiFile-DefaultLocale.yaml"); + auto v1LocaleManifest = CreateYamlManifestInfo("ManifestV1-MultiFile-Locale.yaml"); + + { + // Preview and multi file manifest together + std::vector input = { previewManifest, v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("Preview manifest does not support multi file manifest format")); + } + + { + // Singleton and multi file manifest together + std::vector input = { v1SingletonManifest, v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should not contain file with the particular ManifestType. Field: ManifestType Value: singleton")); + } + + { + // More than 1 version manifest + std::vector input = { v1VersionManifest, v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. Field: ManifestType Value: version")); + } + + { + // More than 1 installer manifest + std::vector input = { v1VersionManifest, v1InstallerManifest, v1InstallerManifest, v1DefaultLocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. Field: ManifestType Value: installer")); + } + + { + // More than 1 default locale manifest + std::vector input = { v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest, v1DefaultLocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. Field: ManifestType Value: defaultLocale")); + } + + { + // Duplicate locales + std::vector input = { v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest, v1LocaleManifest, v1LocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest contains duplicate PackageLocale. Field: PackageLocale Value: en-GB")); + } + + { + // default locale not match + auto defaultLocaleManifestCopy = v1DefaultLocaleManifest; + defaultLocaleManifestCopy.Root["PackageLocale"].SetScalar("fr-fr"); + std::vector input = { v1VersionManifest, v1InstallerManifest, defaultLocaleManifestCopy, v1LocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("DefaultLocale value in version manifest does not match PackageLocale value in defaultLocale manifest")); + } + + { + // Package Id does not match + auto installerManifestCopy = v1InstallerManifest; + installerManifestCopy.Root["PackageIdentifier"].SetScalar("Another.Identifier"); + std::vector input = { v1VersionManifest, installerManifestCopy, v1DefaultLocaleManifest, v1LocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest has inconsistent field values. Field: PackageIdentifier Value: Another.Identifier")); + } + + { + // Package Version does not match + auto installerManifestCopy = v1InstallerManifest; + installerManifestCopy.Root["PackageVersion"].SetScalar("Another.Version"); + std::vector input = { v1VersionManifest, installerManifestCopy, v1DefaultLocaleManifest, v1LocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest has inconsistent field values. Field: PackageVersion Value: Another.Version")); + } + + { + // Incomplete multi file manifest, missing installer + std::vector input = { v1VersionManifest, v1DefaultLocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest is incomplete")); + } + + { + // Incomplete multi file manifest, missing default locale + std::vector input = { v1VersionManifest, v1InstallerManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest is incomplete")); + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp index 5730abc51a..212951bc55 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -10,6 +10,8 @@ namespace AppInstaller::Manifest::YamlParser { + using namespace std::string_view_literals; + namespace { enum class YamlScalarType @@ -18,7 +20,7 @@ namespace AppInstaller::Manifest::YamlParser Int }; - using namespace std::string_view_literals; + // List of fields that use non string scalar types const std::map ManifestFieldTypes= { { "InstallerSuccessCodes"sv, YamlScalarType::Int } @@ -156,6 +158,7 @@ namespace AppInstaller::Manifest::YamlParser std::vector ValidateAgainstSchema(const std::vector& manifestList, const ManifestVer& manifestVersion, PCWSTR resourceModuleName) { std::vector errors; + // A list of schema validators to avoid multiple loadings of same schema std::map schemaList; valijson::Validator schemaValidator; diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index ff9d80914f..600fe746c9 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -10,6 +10,7 @@ namespace AppInstaller::Manifest namespace { + // Only used in preview manifest std::vector SplitMultiValueField(const std::string& input) { if (input.empty()) @@ -470,10 +471,7 @@ namespace AppInstaller::Manifest ValidationErrors ManifestYamlPopulator::PopulateManifestInternal(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, bool fullValidation) { m_fullValidation = fullValidation; - if (manifestVersion.Major() >= 1) - { - m_isMergedManifest = (rootNode["ManifestType"sv].as() == "merged"); - } + m_isMergedManifest = !rootNode["ManifestType"sv].IsNull() && rootNode["ManifestType"sv].as() == "merged"; ValidationErrors resultErrors; manifest.ManifestVersion = manifestVersion; diff --git a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp index d7f9fe2f53..93ab05f9e9 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp @@ -11,6 +11,16 @@ namespace AppInstaller::Manifest::YamlParser { namespace { + // Input validations: + // - Determine manifest version + // - Check multi file manifest input integrity + // - All manifests use same PackageIdentifier, PackageVersion, ManifestVersion + // - All required types exist and exist only once. i.e. version, installer, defaultLocale + // - No duplicate locales across manifests + // - DefaultLocale matches in version manifest and defaultLocale manifest + // - Validate manifest type correctness + // - Allowed file type in multi file manifest: version, installer, defaultLocale, locale + // - Allowed file type in multi file manifest: preview manifest, merged and singleton ManifestVer ValidateInput(std::vector& input, bool fullValidation, bool isPartialManifest) { std::vector errors; @@ -206,9 +216,9 @@ namespace AppInstaller::Manifest::YamlParser return manifestVersion; } + // Find a unique required manifest from the input in multi manifest case const YAML::Node& FindUniqueRequiredDocFromMultiFileManifest(const std::vector& input, ManifestTypeEnum manifestType) { - // We'll do case insensitive search first and validate correct case later. auto iter = std::find_if(input.begin(), input.end(), [=](auto const& s) { @@ -220,6 +230,7 @@ namespace AppInstaller::Manifest::YamlParser return iter->Root; } + // Merge one manifest file to the final merged manifest, basically copying the mapping but excluding certain common fields void MergeOneManifestToMultiFileManifest(const YAML::Node& input, YAML::Node& destination) { THROW_HR_IF(E_UNEXPECTED, !input.IsMap()); @@ -350,6 +361,7 @@ namespace AppInstaller::Manifest::YamlParser return resultErrors; } + // Merge manifests in multi file manifest case const YAML::Node& manifestDoc = (input.size() > 1) ? MergeMultiFileManifest(input) : input[0].Root; auto errors = ManifestYamlPopulator::PopulateManifest(manifestDoc, manifest, manifestVersion, fullValidation); @@ -362,6 +374,7 @@ namespace AppInstaller::Manifest::YamlParser std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); } + // Output merged manifest if requested if (!mergedManifestPath.empty()) { OutputYamlDoc(manifestDoc, mergedManifestPath); diff --git a/src/AppInstallerCommonCore/Public/winget/Manifest.h b/src/AppInstallerCommonCore/Public/winget/Manifest.h index 1a6c024143..2c0729f2a6 100644 --- a/src/AppInstallerCommonCore/Public/winget/Manifest.h +++ b/src/AppInstallerCommonCore/Public/winget/Manifest.h @@ -32,10 +32,14 @@ namespace AppInstaller::Manifest ManifestLocalization CurrentLocalization; + // ApplyLocale will update the CurrentLocalization according to the specified locale // If locale is empty, user setting locale will be used void ApplyLocale(const std::string& locale = {}); + // Get all tags across localizations std::vector GetAggregatedTags() const; + + // Get all commands across installers std::vector GetAggregatedCommands() const; }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 64be7711bb..207ee9fc52 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -141,5 +141,6 @@ namespace AppInstaller::Manifest // Checks whether 2 installer types are compatible. E.g. inno and exe are update compatible bool IsInstallerTypeCompatible(InstallerTypeEnum type1, InstallerTypeEnum type2); + // Get a list of default switches for known installer types std::map GetDefaultKnownSwitches(InstallerTypeEnum installerType); } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h index 4128d444d0..8b66c7df5d 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -67,8 +67,10 @@ namespace AppInstaller::Manifest // Product code for ARP (Add/Remove Programs) installers. string_t ProductCode; + // For msix only std::vector Capabilities; + // For msix only std::vector RestrictedCapabilities; Dependency Dependencies; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h index 2a4de81473..5553402aed 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h @@ -120,14 +120,14 @@ namespace AppInstaller::Manifest using value_t = std::vector; }; - // Used to deduce the DataVariant type; making a variant that includes std::monostate and all DataMapping types. + // Used to deduce the LocalizationVariant type; making a variant that includes std::monostate and all LocalizationMapping types. template inline auto Deduce(std::index_sequence) { return std::variant(I)>::value_t...>{}; } - // Holds data of any type listed in a DataMapping. + // Holds data of any type listed in a LocalizationMapping. using LocalizationVariant = decltype(Deduce(std::make_index_sequence(Localization::Max)>())); - // Gets the index into the variant for the given Data. + // Gets the index into the variant for the given Localization. constexpr inline size_t LocalizationIndex(Localization l) { return static_cast(l) + 1; } } @@ -136,7 +136,6 @@ namespace AppInstaller::Manifest string_t Locale; // Adds a value to the Localization data, or overwrites an existing entry. - // This must be used to create the initial entry, but Get can be used to modify. template void Add(typename details::LocalizationMapping::value_t&& v) { @@ -148,10 +147,10 @@ namespace AppInstaller::Manifest m_data[L].emplace(v); } - // Return a value indicating whether the given data type is stored in the context. + // Return a value indicating whether the given localization type exists. bool Contains(Localization l) { return (m_data.find(l) != m_data.end()); } - // Gets context data + // Gets the localization value if exists, otherwise empty for easier access template typename details::LocalizationMapping::value_t Get() const { diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h index 51aeb61314..b52eb02855 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h @@ -11,10 +11,13 @@ namespace AppInstaller::Manifest::YamlParser // Forward declarations struct YamlManifestInfo; + // Load an embedded resource from binary and return as std::string std::string LoadResourceAsString(PCWSTR resourceModuleName, PCWSTR resourceName, PCWSTR resourceType); + // Load manifest schema as parsed json doc Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType, PCWSTR resourceModuleName); + // resourceModuleName is the binary name where the schemas are embedded, or nullptr indicating the binary that created the process std::vector ValidateAgainstSchema( const std::vector& manifestList, const ManifestVer& manifestVersion, diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h index 0418815e95..43dc773fc6 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h @@ -11,14 +11,22 @@ namespace AppInstaller::Manifest::YamlParser { struct YamlManifestInfo { + // Root node of a yaml manifest file YAML::Node Root; + + // File name of the manifest file if applicable for error reporting std::string FileName; + ManifestTypeEnum ManifestType; }; // fullValidation: Bool to set if manifest creation should perform extra validation that client does not need. // e.g. Channel should be null. Client code does not need this check to work properly. // throwOnWarning: Bool to indicate if an exception should be thrown with only warnings detected in the manifest. + // resourceDll: Binary where schemas are embedded, or nullptr if they are embedded in the binary that created the process + // mergedManifestPath: Output file for merged manifest after processing a multi file manifest + // isPartialManifest: Bool to indicate if the input only consists of partial of a multi file manifest. In this + // case, only schema validation will be performed.. Manifest CreateFromPath( const std::filesystem::path& inputPath, bool fullValidation = false, @@ -37,9 +45,9 @@ namespace AppInstaller::Manifest::YamlParser Manifest ParseManifest( std::vector& input, - bool fullValidation, - bool throwOnWarning, - PCWSTR resourceDll, - const std::filesystem::path& mergedManifestPath, - bool isPartialManifest); + bool fullValidation = false, + bool throwOnWarning = false, + PCWSTR resourceDll = nullptr, + const std::filesystem::path& mergedManifestPath = {}, + bool isPartialManifest = false); } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h index ff5a042a53..886cf2184c 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h @@ -13,6 +13,10 @@ namespace AppInstaller::Manifest private: + bool m_fullValidation = false; + bool m_isMergedManifest = false; + + // Struct mapping a manifest field to its population logic struct FieldProcessInfo { FieldProcessInfo(std::string name, std::function(const YAML::Node&)> func) : @@ -22,9 +26,6 @@ namespace AppInstaller::Manifest std::function(const YAML::Node&)> ProcessFunc; }; - bool m_fullValidation = false; - bool m_isMergedManifest = false; - std::vector RootFieldInfos; std::vector InstallerFieldInfos; std::vector SwitchesFieldInfos; diff --git a/src/AppInstallerCommonCore/Yaml.cpp b/src/AppInstallerCommonCore/Yaml.cpp index 5b829befe5..74f4b5d773 100644 --- a/src/AppInstallerCommonCore/Yaml.cpp +++ b/src/AppInstallerCommonCore/Yaml.cpp @@ -246,6 +246,7 @@ namespace AppInstaller::YAML int Node::as_dispatch(int*) const { + // To allow HResult representation return static_cast(std::stoll(m_scalar, 0, 0)); } diff --git a/src/ManifestSchema/resource.h b/src/ManifestSchema/resource.h index 0f5790ae89..1f8b4abe6d 100644 --- a/src/ManifestSchema/resource.h +++ b/src/ManifestSchema/resource.h @@ -3,7 +3,7 @@ // Used by ManifestSchema.rc // Next default values for new objects -// +// #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 101 From cd13a064514e2042bdae205a3eef5b35ddbf9c62 Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Thu, 4 Feb 2021 13:48:44 -0800 Subject: [PATCH 15/18] Update e2e test expected message --- src/AppInstallerCLIE2ETests/InstallCommand.cs | 1 - src/AppInstallerCLIE2ETests/ValidateCommand.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/AppInstallerCLIE2ETests/InstallCommand.cs b/src/AppInstallerCLIE2ETests/InstallCommand.cs index 8223012463..6788d9679e 100644 --- a/src/AppInstallerCLIE2ETests/InstallCommand.cs +++ b/src/AppInstallerCLIE2ETests/InstallCommand.cs @@ -46,7 +46,6 @@ public void InstallExeWithInsufficientMinOsVersion() var result = TestCommon.RunAICLICommand("install", $"InapplicableOsVersion --silent -l {installDir}"); // MinOSVersion is moved to installer level, the check is performed during installer selection Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICABLE_INSTALLER, result.ExitCode); - Assert.True(result.StdOut.Contains("Cannot install package, as it requires a higher version of Windows")); Assert.False(VerifyTestExeInstalled(installDir)); } diff --git a/src/AppInstallerCLIE2ETests/ValidateCommand.cs b/src/AppInstallerCLIE2ETests/ValidateCommand.cs index 78ec4aef9e..1a8e45e54d 100644 --- a/src/AppInstallerCLIE2ETests/ValidateCommand.cs +++ b/src/AppInstallerCLIE2ETests/ValidateCommand.cs @@ -36,7 +36,7 @@ public void ValidateManifestDoesNotExist() { var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\DoesNotExist")); Assert.AreEqual(Constants.ErrorCode.ERROR_PATH_NOT_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("File does not exist")); + Assert.True(result.StdOut.Contains("Path does not exist")); } } } \ No newline at end of file From 2f805d90b2c5d5958834bad493bb9358c0efbfd1 Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Tue, 16 Feb 2021 13:39:33 -0800 Subject: [PATCH 16/18] PR feedback --- src/AppInstallerCLICore/Resources.h | 1 + .../Workflows/InstallFlow.cpp | 6 +- .../Workflows/ManifestComparator.cpp | 29 ++----- .../ShellExecuteInstallerHandler.cpp | 3 +- .../Workflows/ShowFlow.cpp | 29 +------ .../Workflows/WorkflowBase.cpp | 2 +- src/AppInstallerCLIE2ETests/HashCommand.cs | 2 +- .../Shared/Strings/en-us/winget.resw | 5 +- .../AppInstallerCLITests.vcxproj | 10 +-- .../AppInstallerCLITests.vcxproj.filters | 21 ++--- src/AppInstallerCLITests/CompositeSource.cpp | 6 +- src/AppInstallerCLITests/SQLiteIndex.cpp | 32 ++++---- .../SQLiteIndexSource.cpp | 10 +-- .../TestData/ManifestV1-Singleton.yaml | 1 - .../ManifestV1-MultiFile-DefaultLocale.yaml | 0 .../ManifestV1-MultiFile-Installer.yaml | 1 - .../ManifestV1-MultiFile-Locale.yaml | 0 .../ManifestV1-MultiFile-Version.yaml | 0 src/AppInstallerCLITests/TestSource.cpp | 2 +- src/AppInstallerCLITests/YamlManifest.cpp | 9 +-- .../Manifest/ManifestSchemaValidation.cpp | 26 ++++--- .../Manifest/ManifestYamlPopulator.cpp | 3 +- .../Manifest/YamlParser.cpp | 38 ++++----- .../Public/winget/Manifest.h | 2 + .../Public/winget/ManifestInstaller.h | 2 - .../Public/winget/ManifestLocalization.h | 78 ------------------- .../Public/winget/ManifestSchemaValidation.h | 9 +-- .../Public/winget/ManifestYamlParser.h | 13 +--- .../Microsoft/Schema/1_0/ChannelTable.h | 1 - .../Microsoft/Schema/1_0/Interface_1_0.cpp | 12 +-- src/WinGetUtil/Exports.cpp | 30 ++++++- src/WinGetUtil/WinGetUtil.h | 17 +++- 32 files changed, 154 insertions(+), 246 deletions(-) rename src/AppInstallerCLITests/TestData/{ => MultiFileManifestV1}/ManifestV1-MultiFile-DefaultLocale.yaml (100%) rename src/AppInstallerCLITests/TestData/{ => MultiFileManifestV1}/ManifestV1-MultiFile-Installer.yaml (99%) rename src/AppInstallerCLITests/TestData/{ => MultiFileManifestV1}/ManifestV1-MultiFile-Locale.yaml (100%) rename src/AppInstallerCLITests/TestData/{ => MultiFileManifestV1}/ManifestV1-MultiFile-Version.yaml (100%) diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 095c038566..c51b0c108a 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -225,6 +225,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileFailedIsDirectory); WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileFailedNotExist); WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileSignedMsix); + WINGET_DEFINE_RESOURCE_STRINGID(VerifyPathFailedNotExist); WINGET_DEFINE_RESOURCE_STRINGID(VersionArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(VersionsArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(WordArgumentDescription); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 9b3cf93807..a74ea9df3d 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -183,8 +183,7 @@ namespace AppInstaller::CLI::Workflow bool overrideHashMismatch = context.Args.Contains(Execution::Args::Type::Force); const auto& manifest = context.Get(); - const auto& installer = context.Get().value(); - Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, installer.Channel, hashPair.first, hashPair.second, overrideHashMismatch); + Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, manifest.Channel, hashPair.first, hashPair.second, overrideHashMismatch); // If running as admin, do not allow the user to override the hash failure. if (Runtime::IsRunningAsAdmin()) @@ -325,8 +324,7 @@ namespace AppInstaller::CLI::Workflow catch (const wil::ResultException& re) { const auto& manifest = context.Get(); - const auto& installer = context.Get().value(); - Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, installer.Channel, "MSIX", re.GetErrorCode()); + Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, "MSIX", re.GetErrorCode()); context.Reporter.Error() << GetUserPresentableMessage(re) << std::endl; AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp index 4d8fd695aa..97ff49ff77 100644 --- a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp +++ b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp @@ -12,6 +12,7 @@ namespace AppInstaller::CLI::Workflow namespace { // Determine if the installer is applicable. + // TODO: Implement a mechanism for better error messaging for no applicable installer scenario bool IsInstallerApplicable(const Manifest::ManifestInstaller& installer, Manifest::InstallerTypeEnum installedType) { // Check MinOSVersion @@ -42,24 +43,6 @@ namespace AppInstaller::CLI::Workflow const Manifest::ManifestInstaller& installer2, Manifest::InstallerTypeEnum installedType) { - bool isOSVersionSatisfied1 = installer1.MinOSVersion.empty() || Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version(installer1.MinOSVersion)); - bool isOSVersionSatisfied2 = installer2.MinOSVersion.empty() || Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version(installer2.MinOSVersion)); - - if (isOSVersionSatisfied1 && !isOSVersionSatisfied2) - { - return true; - } - - auto arch1 = Utility::IsApplicableArchitecture(installer1.Arch); - auto arch2 = Utility::IsApplicableArchitecture(installer2.Arch); - - // Applicable architecture should always come before inapplicable architecture - if (arch1 != Utility::InapplicableArchitecture && - arch2 == Utility::InapplicableArchitecture) - { - return true; - } - // If there's installation metadata, pick the preferred one or compatible one if (installedType != Manifest::InstallerTypeEnum::Unknown) { @@ -67,14 +50,12 @@ namespace AppInstaller::CLI::Workflow { return true; } - if (Manifest::IsInstallerTypeCompatible(installer1.InstallerType, installedType) && - !Manifest::IsInstallerTypeCompatible(installer2.InstallerType, installedType)) - { - return true; - } } // Todo: Compare only architecture for now. Need more work and spec. + auto arch1 = Utility::IsApplicableArchitecture(installer1.Arch); + auto arch2 = Utility::IsApplicableArchitecture(installer2.Arch); + if (arch1 > arch2) { return true; @@ -107,7 +88,7 @@ namespace AppInstaller::CLI::Workflow result = &installer; } } - else if (IsInstallerBetterMatch(installer, *result, installedType)) + else if (IsInstallerApplicable(installer, installedType) && IsInstallerBetterMatch(installer, *result, installedType)) { result = &installer; } diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp index c3e527628c..633ff52c03 100644 --- a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp +++ b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp @@ -208,8 +208,7 @@ namespace AppInstaller::CLI::Workflow else if (installResult.value() != 0) { const auto& manifest = context.Get(); - const auto& installer = context.Get(); - Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, installer->Channel, "ShellExecute", installResult.value()); + Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, "ShellExecute", installResult.value()); context.Reporter.Error() << "Installer failed with exit code: " << installResult.value() << std::endl; // Show installer log path if exists diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp index bdfaef241a..c6da146fd0 100644 --- a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -79,35 +79,8 @@ namespace AppInstaller::CLI::Workflow void ShowManifestVersion(Execution::Context& context) { const auto& manifest = context.Get(); - - // Channel is moved to installer level, get all channels from all installers - std::set channels; - for (const auto& installer : manifest.Installers) - { - if (!installer.Channel.empty()) - { - channels.insert(installer.Channel); - } - } - - std::string channelsStr; - bool firstEntry = true; - for (const auto& c : channels) - { - if (firstEntry) - { - firstEntry = false; - } - else - { - channelsStr += ", "; - } - - channelsStr += c; - } - Execution::TableOutput<2> table(context.Reporter, { Resource::String::ShowVersion, Resource::String::ShowChannel }); - table.OutputLine({ manifest.Version, channelsStr }); + table.OutputLine({ manifest.Version, manifest.Channel }); table.Complete(); } diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index e68a3bddb3..ba76701149 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -531,7 +531,7 @@ namespace AppInstaller::CLI::Workflow if (!std::filesystem::exists(path)) { - context.Reporter.Error() << Resource::String::VerifyFileFailedNotExist << ' ' << path.u8string() << std::endl; + context.Reporter.Error() << Resource::String::VerifyPathFailedNotExist << ' ' << path.u8string() << std::endl; AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)); } } diff --git a/src/AppInstallerCLIE2ETests/HashCommand.cs b/src/AppInstallerCLIE2ETests/HashCommand.cs index 6d5435bec0..f8525ed3ba 100644 --- a/src/AppInstallerCLIE2ETests/HashCommand.cs +++ b/src/AppInstallerCLIE2ETests/HashCommand.cs @@ -39,7 +39,7 @@ public void HashFileNotFound() { var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("DoesNot.Exist")); Assert.AreEqual(Constants.ErrorCode.ERROR_FILE_NOT_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("Path does not exist")); + Assert.True(result.StdOut.Contains("File does not exist")); } } } \ No newline at end of file diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index e8d2fc8bcb..0d8de796dd 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -713,7 +713,7 @@ They can be configured through the settings file 'winget settings'. Path is a directory: - Path does not exist: + File does not exist: Both local manifest and search query args are provided @@ -756,4 +756,7 @@ They can be configured through the settings file 'winget settings'. Uninstall failed with exit code: + + Path does not exist: + \ No newline at end of file diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index f499402546..d337807a64 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -430,19 +430,19 @@ true - + true - + true - + true - + true - + true diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 3ad48a398c..1deacbddbf 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -16,6 +16,9 @@ {d5cac203-3846-4b39-a1cd-8de9303757b4} + + {69fcd25c-e737-4d28-a6d1-39ce491bf293} + @@ -330,20 +333,20 @@ TestData - + TestData - - TestData + + TestData\MultiFileManifestV1 - - TestData + + TestData\MultiFileManifestV1 - - TestData + + TestData\MultiFileManifestV1 - - TestData + + TestData\MultiFileManifestV1 \ No newline at end of file diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp index 2c2e405a6c..07824cab23 100644 --- a/src/AppInstallerCLITests/CompositeSource.cpp +++ b/src/AppInstallerCLITests/CompositeSource.cpp @@ -405,7 +405,7 @@ TEST_CASE("CompositePackage_AvailableVersions_ChannelFilteredOut", "[CompositeSo noChannel.Version = "1.0"; Manifest::Manifest hasChannel = MakeDefaultManifest(); - hasChannel.Installers[0].Channel = channel; + hasChannel.Channel = channel; hasChannel.Version = "2.0"; SearchResult result; @@ -434,14 +434,14 @@ TEST_CASE("CompositePackage_AvailableVersions_NoChannelFilteredOut", "[Composite std::string channel = "Channel"; CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(MakeInstalled([&](Manifest::Manifest& m) { m.Installers[0].PackageFamilyName = pfn; m.Installers[0].Channel = channel; }), Criteria()); + setup.Installed->Everything.Matches.emplace_back(MakeInstalled([&](Manifest::Manifest& m) { m.Installers[0].PackageFamilyName = pfn; m.Channel = channel; }), Criteria()); setup.Available->SearchFunction = [&](const SearchRequest&) { Manifest::Manifest noChannel = MakeDefaultManifest(); noChannel.Version = "1.0"; Manifest::Manifest hasChannel = MakeDefaultManifest(); - hasChannel.Installers[0].Channel = channel; + hasChannel.Channel = channel; hasChannel.Version = "2.0"; SearchResult result; diff --git a/src/AppInstallerCLITests/SQLiteIndex.cpp b/src/AppInstallerCLITests/SQLiteIndex.cpp index 08a92a0823..7c52da9049 100644 --- a/src/AppInstallerCLITests/SQLiteIndex.cpp +++ b/src/AppInstallerCLITests/SQLiteIndex.cpp @@ -69,7 +69,7 @@ SQLiteIndex SimpleTestSetup(const std::string& filePath, Manifest& manifest, std manifest.DefaultLocalization.Add("Test Name"); manifest.Moniker = "testmoniker"; manifest.Version = "1.0.0"; - manifest.Installers[0].Channel = "test"; + manifest.Channel = "test"; manifest.DefaultLocalization.Add({ "t1", "t2" }); manifest.Installers[0].Commands = { "test1", "test2" }; @@ -159,7 +159,7 @@ SQLiteIndex SearchTestSetup(const std::string& filePath, std::initializer_list("Test Name"); manifest1.Moniker = "testmoniker"; manifest1.Version = "1.0.0"; - manifest1.Installers[0].Channel = "test"; + manifest1.Channel = "test"; manifest1.DefaultLocalization.Add({ "t1", "t2" }); manifest1.Installers[0].Commands = { "test1", "test2" }; @@ -343,7 +343,7 @@ TEST_CASE("SQLiteIndex_RemoveManifest", "[sqliteindex][V1_0]") manifest2.DefaultLocalization.Add("Test Name WOAH"); manifest2.Moniker = "testmoniker"; manifest2.Version = "1.0.0"; - manifest2.Installers[0].Channel = "test"; + manifest2.Channel = "test"; manifest2.DefaultLocalization.Add({}); manifest2.Installers[0].Commands = { "test1", "test2", "test3" }; @@ -406,7 +406,7 @@ TEST_CASE("SQLiteIndex_RemoveManifest_EnsureConsistentRowId", "[sqliteindex]") manifest1.DefaultLocalization.Add("Test Name"); manifest1.Moniker = "testmoniker"; manifest1.Version = "1.0.0"; - manifest1.Installers[0].Channel = "test"; + manifest1.Channel = "test"; manifest1.DefaultLocalization.Add({ "t1", "t2" }); manifest1.Installers[0].Commands = { "test1", "test2" }; @@ -417,7 +417,7 @@ TEST_CASE("SQLiteIndex_RemoveManifest_EnsureConsistentRowId", "[sqliteindex]") manifest2.DefaultLocalization.Add("Test Name WOAH"); manifest2.Moniker = "testmoniker"; manifest2.Version = "1.0.0"; - manifest2.Installers[0].Channel = "test"; + manifest2.Channel = "test"; manifest2.DefaultLocalization.Add({}); manifest2.Installers[0].Commands = { "test1", "test2", "test3" }; @@ -456,7 +456,7 @@ TEST_CASE("SQLiteIndex_RemoveManifest_EnsureConsistentRowId", "[sqliteindex]") REQUIRE(manifest2.Id == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Id)); REQUIRE(manifest2.DefaultLocalization.Get() == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Name)); REQUIRE(manifest2.Version == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Version)); - REQUIRE(manifest2.Installers[0].Channel == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Channel)); + REQUIRE(manifest2.Channel == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::Channel)); REQUIRE(manifest2Path == index.GetPropertyByManifestId(manifest2RowId, PackageVersionProperty::RelativePath)); } @@ -503,7 +503,7 @@ TEST_CASE("SQLiteIndex_UpdateManifest", "[sqliteindex][V1_0]") manifest.DefaultLocalization.Add < Localization::PackageName>("Test Name"); manifest.Moniker = "testmoniker"; manifest.Version = "1.0.0"; - manifest.Installers[0].Channel = "test"; + manifest.Channel = "test"; manifest.DefaultLocalization.Add({ "t1", "t2" }); manifest.Installers[0].Commands = { "test1", "test2" }; @@ -597,7 +597,7 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangePath", "[sqliteindex][V1_0]") manifest.DefaultLocalization.Add("Test Name"); manifest.Moniker = "testmoniker"; manifest.Version = "1.0.0"; - manifest.Installers[0].Channel = "test"; + manifest.Channel = "test"; manifest.DefaultLocalization.Add({ "t1", "t2" }); manifest.Installers[0].Commands = { "test1", "test2" }; @@ -679,7 +679,7 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]") manifest.DefaultLocalization.Add("Test Name"); manifest.Moniker = "testmoniker"; manifest.Version = "1.0.0-test"; - manifest.Installers[0].Channel = "test"; + manifest.Channel = "test"; manifest.DefaultLocalization.Add({ "t1", "t2" }); manifest.Installers[0].Commands = { "test1", "test2" }; @@ -710,7 +710,7 @@ TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]") { SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteIndex::OpenDisposition::ReadWrite); - manifest.Installers[0].Channel = "Test"; + manifest.Channel = "Test"; // Update with path update should indicate change REQUIRE(index.UpdateManifest(manifest, manifestPath)); @@ -1035,10 +1035,10 @@ TEST_CASE("SQLiteIndex_PathString", "[sqliteindex]") auto results = index.Search(request); REQUIRE(results.Matches.size() == 1); - auto specificResult = GetPathStringByKey(index, results.Matches[0].first, manifest.Version, manifest.Installers[0].Channel); + auto specificResult = GetPathStringByKey(index, results.Matches[0].first, manifest.Version, manifest.Channel); REQUIRE(specificResult == relativePath); - auto latestResult = GetPathStringByKey(index, results.Matches[0].first, "", manifest.Installers[0].Channel); + auto latestResult = GetPathStringByKey(index, results.Matches[0].first, "", manifest.Channel); REQUIRE(latestResult == relativePath); } @@ -1062,7 +1062,7 @@ TEST_CASE("SQLiteIndex_Versions", "[sqliteindex]") auto result = index.GetVersionKeysById(results.Matches[0].first); REQUIRE(result.size() == 1); REQUIRE(result[0].GetVersion().ToString() == manifest.Version); - REQUIRE(result[0].GetChannel().ToString() == manifest.Installers[0].Channel); + REQUIRE(result[0].GetChannel().ToString() == manifest.Channel); } TEST_CASE("SQLiteIndex_Search_VersionSorting", "[sqliteindex]") @@ -1842,7 +1842,7 @@ TEST_CASE("SQLiteIndex_CheckConsistency_Failure", "[sqliteindex][V1_1]") manifest1.DefaultLocalization.Add("Test Name"); manifest1.Moniker = "testmoniker"; manifest1.Version = "1.0.0"; - manifest1.Installers[0].Channel = "test"; + manifest1.Channel = "test"; manifest1.DefaultLocalization.Add({ "t1", "t2" }); manifest1.Installers[0].Commands = { "test1", "test2" }; @@ -1853,7 +1853,7 @@ TEST_CASE("SQLiteIndex_CheckConsistency_Failure", "[sqliteindex][V1_1]") manifest2.DefaultLocalization.Add("Test Name WOAH"); manifest2.Moniker = "testmoniker"; manifest2.Version = "1.0.0"; - manifest2.Installers[0].Channel = "test"; + manifest2.Channel = "test"; manifest2.DefaultLocalization.Add({}); manifest2.Installers[0].Commands = { "test1", "test2", "test3" }; diff --git a/src/AppInstallerCLITests/SQLiteIndexSource.cpp b/src/AppInstallerCLITests/SQLiteIndexSource.cpp index 07126e8a45..fb39d58088 100644 --- a/src/AppInstallerCLITests/SQLiteIndexSource.cpp +++ b/src/AppInstallerCLITests/SQLiteIndexSource.cpp @@ -131,7 +131,7 @@ TEST_CASE("SQLiteIndexSource_Versions", "[sqliteindexsource]") auto result = results.Matches[0].Package->GetAvailableVersionKeys(); REQUIRE(result.size() == 1); REQUIRE(result[0].Version == manifest.Version); - REQUIRE(result[0].Channel == manifest.Installers[0].Channel); + REQUIRE(result[0].Channel == manifest.Channel); } TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") @@ -152,21 +152,21 @@ TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") REQUIRE(results.Matches[0].Package); auto package = results.Matches[0].Package.get(); - auto specificResultVersion = package->GetAvailableVersion(PackageVersionKey("", manifest.Version, manifest.Installers[0].Channel)); + auto specificResultVersion = package->GetAvailableVersion(PackageVersionKey("", manifest.Version, manifest.Channel)); REQUIRE(specificResultVersion); auto specificResult = specificResultVersion->GetManifest(); REQUIRE(specificResult.Id == manifest.Id); REQUIRE(specificResult.DefaultLocalization.Get() == manifest.DefaultLocalization.Get()); REQUIRE(specificResult.Version == manifest.Version); - REQUIRE(specificResult.Installers[0].Channel == manifest.Installers[0].Channel); + REQUIRE(specificResult.Channel == manifest.Channel); - auto latestResultVersion = package->GetAvailableVersion(PackageVersionKey("", "", manifest.Installers[0].Channel)); + auto latestResultVersion = package->GetAvailableVersion(PackageVersionKey("", "", manifest.Channel)); REQUIRE(latestResultVersion); auto latestResult = latestResultVersion->GetManifest(); REQUIRE(latestResult.Id == manifest.Id); REQUIRE(latestResult.DefaultLocalization.Get() == manifest.DefaultLocalization.Get()); REQUIRE(latestResult.Version == manifest.Version); - REQUIRE(latestResult.Installers[0].Channel == manifest.Installers[0].Channel); + REQUIRE(latestResult.Channel == manifest.Channel); auto noResultVersion = package->GetAvailableVersion(PackageVersionKey("", "blargle", "flargle")); REQUIRE(!noResultVersion); diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml index 83b1e5b995..356e4d1c88 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1-Singleton.yaml @@ -71,7 +71,6 @@ ProductCode: "{Foo}" Installers: - Architecture: x86 - Channel: preview InstallerLocale: en-GB Platform: - Windows.Desktop diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-DefaultLocale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1/ManifestV1-MultiFile-DefaultLocale.yaml similarity index 100% rename from src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-DefaultLocale.yaml rename to src/AppInstallerCLITests/TestData/MultiFileManifestV1/ManifestV1-MultiFile-DefaultLocale.yaml diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1/ManifestV1-MultiFile-Installer.yaml similarity index 99% rename from src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Installer.yaml rename to src/AppInstallerCLITests/TestData/MultiFileManifestV1/ManifestV1-MultiFile-Installer.yaml index cacfe0064c..decfc9d294 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Installer.yaml +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1/ManifestV1-MultiFile-Installer.yaml @@ -53,7 +53,6 @@ ProductCode: "{Foo}" Installers: - Architecture: x86 - Channel: preview InstallerLocale: en-GB Platform: - Windows.Desktop diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Locale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1/ManifestV1-MultiFile-Locale.yaml similarity index 100% rename from src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Locale.yaml rename to src/AppInstallerCLITests/TestData/MultiFileManifestV1/ManifestV1-MultiFile-Locale.yaml diff --git a/src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Version.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1/ManifestV1-MultiFile-Version.yaml similarity index 100% rename from src/AppInstallerCLITests/TestData/ManifestV1-MultiFile-Version.yaml rename to src/AppInstallerCLITests/TestData/MultiFileManifestV1/ManifestV1-MultiFile-Version.yaml diff --git a/src/AppInstallerCLITests/TestSource.cpp b/src/AppInstallerCLITests/TestSource.cpp index fc54cc3d0a..c18bb3b820 100644 --- a/src/AppInstallerCLITests/TestSource.cpp +++ b/src/AppInstallerCLITests/TestSource.cpp @@ -23,7 +23,7 @@ namespace TestCommon case PackageVersionProperty::Version: return LocIndString{ VersionManifest.Version }; case PackageVersionProperty::Channel: - return LocIndString{ VersionManifest.Installers[0].Channel }; + return LocIndString{ VersionManifest.Channel }; default: return {}; } diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 7236334c3a..20bf5a183f 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -40,7 +40,7 @@ TEST_CASE("ReadPreviewGoodManifestAndVerifyContents", "[ManifestValidation]") REQUIRE(manifest.Moniker == "msixsdk"); REQUIRE(manifest.Version == "1.7.32"); REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); - REQUIRE(manifest.DefaultInstallerInfo.Channel == "release"); + REQUIRE(manifest.Channel == "release"); REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); REQUIRE(manifest.DefaultLocalization.Get() == "MIT License"); REQUIRE(manifest.DefaultLocalization.Get() == "https://github.com/microsoft/msix-packaging/blob/master/LICENSE"); @@ -129,7 +129,7 @@ TEST_CASE("ReadGoodManifestWithSpaces", "[ManifestValidation]") REQUIRE(manifest.DefaultLocalization.Get() == "MSIX SDK"); REQUIRE(manifest.Moniker == "msixsdk"); REQUIRE(manifest.Version == "1.7.32"); - REQUIRE(manifest.DefaultInstallerInfo.Channel == "release"); + REQUIRE(manifest.Channel == "release"); REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "0.0.0.0"); REQUIRE(manifest.DefaultLocalization.Get() == MultiValue{ "msix", "appx" }); REQUIRE(manifest.DefaultInstallerInfo.Commands == MultiValue{ "makemsix", "makeappx" }); @@ -349,7 +349,7 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton) REQUIRE(manifest.DefaultLocalization.Get() == "The MSIX SDK project is an effort to enable developers"); REQUIRE(manifest.Moniker == "msixsdk"); REQUIRE(manifest.DefaultLocalization.Get() == MultiValue{ "appxsdk", "msixsdk" }); - REQUIRE(manifest.DefaultInstallerInfo.Channel == "release"); + REQUIRE(manifest.Channel == "release"); REQUIRE(manifest.DefaultInstallerInfo.Locale == "en-US"); REQUIRE(manifest.DefaultInstallerInfo.Platform == std::vector{ PlatformEnum::Desktop, PlatformEnum::Universal }); REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "10.0.0.0"); @@ -396,7 +396,6 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton) ManifestInstaller installer1 = manifest.Installers.at(0); REQUIRE(installer1.Arch == Architecture::X86); - REQUIRE(installer1.Channel == "preview"); REQUIRE(installer1.Locale == "en-GB"); REQUIRE(installer1.Platform == std::vector{ PlatformEnum::Desktop }); REQUIRE(installer1.MinOSVersion == "10.0.1.0"); @@ -477,7 +476,7 @@ TEST_CASE("ValidateV1GoodManifestAndVerifyContents", "[ManifestValidation]") "ManifestV1-MultiFile-Locale.yaml" }, multiFileDirectory); TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, true, true, nullptr, mergedManifestFile); + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, true, true, mergedManifestFile); VerifyV1ManifestContent(multiFileManifest, false); // Read from merged manifest should have the same content as multi file manifest diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp index 212951bc55..b533b35e1b 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -86,9 +86,13 @@ namespace AppInstaller::Manifest::YamlParser } } - std::string LoadResourceAsString(PCWSTR resourceModuleName, PCWSTR resourceName, PCWSTR resourceType) + std::string LoadResourceAsString(PCWSTR resourceName, PCWSTR resourceType) { - HMODULE resourceModule = GetModuleHandle(resourceModuleName); + HMODULE resourceModule = NULL; + GetModuleHandleEx( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + (PCWSTR)LoadResourceAsString, + &resourceModule); THROW_LAST_ERROR_IF_NULL(resourceModule); HRSRC resourceInfoHandle = FindResource(resourceModule, resourceName, resourceType); @@ -111,7 +115,7 @@ namespace AppInstaller::Manifest::YamlParser return resourceStr; } - Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType, PCWSTR resourceModuleName) + Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType) { std::string schemaStr; @@ -120,19 +124,19 @@ namespace AppInstaller::Manifest::YamlParser switch (manifestType) { case AppInstaller::Manifest::ManifestTypeEnum::Singleton: - schemaStr = LoadResourceAsString(resourceModuleName, MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_SINGLETON), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_SINGLETON), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); break; case AppInstaller::Manifest::ManifestTypeEnum::Version: - schemaStr = LoadResourceAsString(resourceModuleName, MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_VERSION), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_VERSION), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); break; case AppInstaller::Manifest::ManifestTypeEnum::Installer: - schemaStr = LoadResourceAsString(resourceModuleName, MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_INSTALLER), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_INSTALLER), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); break; case AppInstaller::Manifest::ManifestTypeEnum::DefaultLocale: - schemaStr = LoadResourceAsString(resourceModuleName, MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); break; case AppInstaller::Manifest::ManifestTypeEnum::Locale: - schemaStr = LoadResourceAsString(resourceModuleName, MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_LOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_LOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); break; default: THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); @@ -140,7 +144,7 @@ namespace AppInstaller::Manifest::YamlParser } else { - schemaStr = LoadResourceAsString(resourceModuleName, MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_PREVIEW), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_PREVIEW), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); } Json::Value schemaJson; @@ -155,7 +159,7 @@ namespace AppInstaller::Manifest::YamlParser return schemaJson; } - std::vector ValidateAgainstSchema(const std::vector& manifestList, const ManifestVer& manifestVersion, PCWSTR resourceModuleName) + std::vector ValidateAgainstSchema(const std::vector& manifestList, const ManifestVer& manifestVersion) { std::vector errors; // A list of schema validators to avoid multiple loadings of same schema @@ -170,7 +174,7 @@ namespace AppInstaller::Manifest::YamlParser valijson::Schema& newSchema = schemaList.emplace( std::piecewise_construct, std::make_tuple(entry.ManifestType), std::make_tuple()).first->second; valijson::SchemaParser schemaParser; - Json::Value schemaJson = LoadSchemaDoc(manifestVersion, entry.ManifestType, resourceModuleName); + Json::Value schemaJson = LoadSchemaDoc(manifestVersion, entry.ManifestType); valijson::adapters::JsonCppAdapter jsonSchemaAdapter(schemaJson); schemaParser.populateSchema(jsonSchemaAdapter, newSchema); } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index 600fe746c9..bab55f332f 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -111,6 +111,7 @@ namespace AppInstaller::Manifest { "ManifestVersion", [](const YAML::Node&)->ValidationErrors { /* ManifestVersion already populated. Field listed here for duplicate and PascalCase check */ return {}; } }, { "Installers", [this](const YAML::Node& value)->ValidationErrors { m_p_installersNode = &value; return {}; } }, { "Localization", [this](const YAML::Node& value)->ValidationErrors { m_p_localizationsNode = &value; return {}; } }, + { "Channel", [this](const YAML::Node& value)->ValidationErrors { m_p_manifest->Channel = Utility::Trim(value.as()); return {}; } }, }; // Additional version specific fields @@ -199,7 +200,6 @@ namespace AppInstaller::Manifest // Root node only std::vector rootOnlyFields = { - { "Channel", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Channel = Utility::Trim(value.as()); return {}; } }, { "MinOSVersion", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->MinOSVersion = value.as(); return {}; } }, { "Commands", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Commands = SplitMultiValueField(value.as()); return {}; } }, { "Protocols", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Protocols = SplitMultiValueField(value.as()); return {}; } }, @@ -217,7 +217,6 @@ namespace AppInstaller::Manifest // Root level and Localization node level std::vector v1CommonFields = { - { "Channel", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Channel = Utility::Trim(value.as()); return {}; } }, { "InstallerLocale", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Locale = value.as(); return {}; } }, { "Platform", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Platform = ProcessPlatformSequenceNode(value); return {}; } }, { "MinimumOSVersion", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->MinOSVersion = value.as(); return {}; } }, diff --git a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp index 93ab05f9e9..d6b5fc50eb 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp @@ -20,8 +20,8 @@ namespace AppInstaller::Manifest::YamlParser // - DefaultLocale matches in version manifest and defaultLocale manifest // - Validate manifest type correctness // - Allowed file type in multi file manifest: version, installer, defaultLocale, locale - // - Allowed file type in multi file manifest: preview manifest, merged and singleton - ManifestVer ValidateInput(std::vector& input, bool fullValidation, bool isPartialManifest) + // - Allowed file type in single file manifest: preview manifest, merged and singleton + ManifestVer ValidateInput(std::vector& input, bool fullValidation, bool schemaValidationOnly) { std::vector errors; @@ -178,7 +178,7 @@ namespace AppInstaller::Manifest::YamlParser errors.emplace_back(ManifestError::InconsistentMultiFileManifestDefaultLocale); } - if (!isPartialManifest && !(isVersionManifestFound && isInstallerManifestFound && isDefaultLocaleManifestFound)) + if (!schemaValidationOnly && !(isVersionManifestFound && isInstallerManifestFound && isDefaultLocaleManifestFound)) { errors.emplace_back(ManifestError::IncompleteMultiFileManifest); } @@ -196,7 +196,7 @@ namespace AppInstaller::Manifest::YamlParser errors.emplace_back(ValidationError::MessageFieldValueWithFile(ManifestError::FieldValueNotSupported, "ManifestType", manifestTypeStr, firstYamlManifest.FileName)); } - if (!isPartialManifest && manifestType != ManifestTypeEnum::Merged && manifestType != ManifestTypeEnum::Singleton) + if (!schemaValidationOnly && manifestType != ManifestTypeEnum::Merged && manifestType != ManifestTypeEnum::Singleton) { errors.emplace_back(ValidationError::MessageWithFile(ManifestError::IncompleteMultiFileManifest, firstYamlManifest.FileName)); } @@ -337,26 +337,23 @@ namespace AppInstaller::Manifest::YamlParser std::vector& input, Manifest& manifest, bool fullValidation, - PCWSTR resourceDll, const std::filesystem::path& mergedManifestPath, - bool isPartialManifest) + bool schemaValidationOnly) { THROW_HR_IF_MSG(E_INVALIDARG, input.size() == 0, "No manifest file found"); - THROW_HR_IF_MSG(E_INVALIDARG, isPartialManifest && !mergedManifestPath.empty(), "Manifest cannot be merged from partial manifest"); + THROW_HR_IF_MSG(E_INVALIDARG, schemaValidationOnly && !mergedManifestPath.empty(), "Manifest cannot be merged if only schema validation is performed"); THROW_HR_IF_MSG(E_INVALIDARG, input.size() == 1 && !mergedManifestPath.empty(), "Manifest cannot be merged from a single manifest"); - THROW_HR_IF_MSG(E_INVALIDARG, !fullValidation && isPartialManifest, "For partial manifest, only schema validations are performed"); - auto manifestVersion = ValidateInput(input, fullValidation, isPartialManifest); + auto manifestVersion = ValidateInput(input, fullValidation, schemaValidationOnly); std::vector resultErrors; - if (fullValidation) + if (fullValidation || schemaValidationOnly) { - resultErrors = ValidateAgainstSchema(input, manifestVersion, resourceDll); + resultErrors = ValidateAgainstSchema(input, manifestVersion); } - // For partial manifest, only schema validations are performed - if (isPartialManifest) + if (schemaValidationOnly) { return resultErrors; } @@ -388,9 +385,8 @@ namespace AppInstaller::Manifest::YamlParser const std::filesystem::path& inputPath, bool fullValidation, bool throwOnWarning, - PCWSTR resourceDll, const std::filesystem::path& mergedManifestPath, - bool isPartialManifest) + bool schemaValidationOnly) { std::vector docList; @@ -421,16 +417,15 @@ namespace AppInstaller::Manifest::YamlParser THROW_EXCEPTION_MSG(ManifestException(), e.what()); } - return ParseManifest(docList, fullValidation, throwOnWarning, resourceDll, mergedManifestPath, isPartialManifest); + return ParseManifest(docList, fullValidation, throwOnWarning, mergedManifestPath, schemaValidationOnly); } Manifest Create( const std::string& input, bool fullValidation, bool throwOnWarning, - PCWSTR resourceDll, const std::filesystem::path& mergedManifestPath, - bool isPartialManifest) + bool schemaValidationOnly) { std::vector docList; @@ -445,23 +440,22 @@ namespace AppInstaller::Manifest::YamlParser THROW_EXCEPTION_MSG(ManifestException(), e.what()); } - return ParseManifest(docList, fullValidation, throwOnWarning, resourceDll, mergedManifestPath, isPartialManifest); + return ParseManifest(docList, fullValidation, throwOnWarning, mergedManifestPath, schemaValidationOnly); } Manifest ParseManifest( std::vector& input, bool fullValidation, bool throwOnWarning, - PCWSTR resourceDll, const std::filesystem::path& mergedManifestPath, - bool isPartialManifest) + bool schemaValidationOnly) { Manifest manifest; std::vector errors; try { - errors = ParseManifestImpl(input, manifest, fullValidation, resourceDll, mergedManifestPath, isPartialManifest); + errors = ParseManifestImpl(input, manifest, fullValidation, mergedManifestPath, schemaValidationOnly); } catch (const ManifestException&) { diff --git a/src/AppInstallerCommonCore/Public/winget/Manifest.h b/src/AppInstallerCommonCore/Public/winget/Manifest.h index 2c0729f2a6..1b2c59e6ef 100644 --- a/src/AppInstallerCommonCore/Public/winget/Manifest.h +++ b/src/AppInstallerCommonCore/Public/winget/Manifest.h @@ -18,6 +18,8 @@ namespace AppInstaller::Manifest string_t Version; + string_t Channel; + string_t Moniker; ManifestVer ManifestVersion; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h index 8b66c7df5d..8601eff3ca 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -33,8 +33,6 @@ namespace AppInstaller::Manifest // Store Product Id string_t ProductId; - string_t Channel; - string_t Locale; std::vector Platform; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h index 5553402aed..ab9d83b00b 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h @@ -32,84 +32,6 @@ namespace AppInstaller::Manifest { template struct LocalizationMapping - { - // value_t type specifies the type of this data - }; - - template <> - struct LocalizationMapping - { - using value_t = string_t; - }; - - template <> - struct LocalizationMapping - { - using value_t = string_t; - }; - - template <> - struct LocalizationMapping - { - using value_t = string_t; - }; - - template <> - struct LocalizationMapping - { - using value_t = string_t; - }; - - template <> - struct LocalizationMapping - { - using value_t = string_t; - }; - - template <> - struct LocalizationMapping - { - using value_t = string_t; - }; - - template <> - struct LocalizationMapping - { - using value_t = string_t; - }; - - template <> - struct LocalizationMapping - { - using value_t = string_t; - }; - - template <> - struct LocalizationMapping - { - using value_t = string_t; - }; - - template <> - struct LocalizationMapping - { - using value_t = string_t; - }; - - template <> - struct LocalizationMapping - { - using value_t = string_t; - }; - - template <> - struct LocalizationMapping - { - using value_t = string_t; - }; - - template <> - struct LocalizationMapping { using value_t = string_t; }; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h index b52eb02855..1dc8793d5e 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h @@ -12,14 +12,13 @@ namespace AppInstaller::Manifest::YamlParser struct YamlManifestInfo; // Load an embedded resource from binary and return as std::string - std::string LoadResourceAsString(PCWSTR resourceModuleName, PCWSTR resourceName, PCWSTR resourceType); + std::string LoadResourceAsString(PCWSTR resourceName, PCWSTR resourceType); // Load manifest schema as parsed json doc - Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType, PCWSTR resourceModuleName); + Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType); - // resourceModuleName is the binary name where the schemas are embedded, or nullptr indicating the binary that created the process + // Validate a list of individual manifests against schema std::vector ValidateAgainstSchema( const std::vector& manifestList, - const ManifestVer& manifestVersion, - PCWSTR resourceModuleName); + const ManifestVer& manifestVersion); } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h index 43dc773fc6..02d96d712f 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h @@ -23,31 +23,26 @@ namespace AppInstaller::Manifest::YamlParser // fullValidation: Bool to set if manifest creation should perform extra validation that client does not need. // e.g. Channel should be null. Client code does not need this check to work properly. // throwOnWarning: Bool to indicate if an exception should be thrown with only warnings detected in the manifest. - // resourceDll: Binary where schemas are embedded, or nullptr if they are embedded in the binary that created the process // mergedManifestPath: Output file for merged manifest after processing a multi file manifest - // isPartialManifest: Bool to indicate if the input only consists of partial of a multi file manifest. In this - // case, only schema validation will be performed.. + // schemaValidationOnly: Bool to indicate if only schema validation should be performed Manifest CreateFromPath( const std::filesystem::path& inputPath, bool fullValidation = false, bool throwOnWarning = false, - PCWSTR resourceDll = nullptr, const std::filesystem::path& mergedManifestPath = {}, - bool isPartialManifest = false); + bool schemaValidationOnly = false); Manifest Create( const std::string& input, bool fullValidation = false, bool throwOnWarning = false, - PCWSTR resourceDll = nullptr, const std::filesystem::path& mergedManifestPath = {}, - bool isPartialManifest = false); + bool schemaValidationOnly = false); Manifest ParseManifest( std::vector& input, bool fullValidation = false, bool throwOnWarning = false, - PCWSTR resourceDll = nullptr, const std::filesystem::path& mergedManifestPath = {}, - bool isPartialManifest = false); + bool schemaValidationOnly = false); } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ChannelTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ChannelTable.h index 41dc6e18a4..e3b59f5e52 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ChannelTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ChannelTable.h @@ -18,6 +18,5 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 } // The table for Channel. - // TODO: Currently only indexing channel from first installer, might need to be OneToMany table using ChannelTable = OneToOneTable; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp index a0b6276e42..f84504428a 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp @@ -40,10 +40,10 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 return {}; } - std::optional channelId = ChannelTable::SelectIdByValue(connection, manifest.Installers[0].Channel, true); + std::optional channelId = ChannelTable::SelectIdByValue(connection, manifest.Channel, true); if (!channelId) { - AICLI_LOG(Repo, Info, << "Did not find a Channel { " << manifest.Installers[0].Channel << " }"); + AICLI_LOG(Repo, Info, << "Did not find a Channel { " << manifest.Channel << " }"); return {}; } @@ -51,7 +51,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 if (!result) { - AICLI_LOG(Repo, Info, << "Did not find a manifest row for { " << manifest.Id << ", " << manifest.Version << ", " << manifest.Installers[0].Channel << " }"); + AICLI_LOG(Repo, Info, << "Did not find a manifest row for { " << manifest.Id << ", " << manifest.Version << ", " << manifest.Channel << " }"); } return result; @@ -188,7 +188,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 SQLite::rowid_t nameId = NameTable::EnsureExists(connection, manifest.DefaultLocalization.Get()); SQLite::rowid_t monikerId = MonikerTable::EnsureExists(connection, manifest.Moniker); SQLite::rowid_t versionId = VersionTable::EnsureExists(connection, manifest.Version); - SQLite::rowid_t channelId = ChannelTable::EnsureExists(connection, manifest.Installers[0].Channel); + SQLite::rowid_t channelId = ChannelTable::EnsureExists(connection, manifest.Channel); // Insert the manifest entry. SQLite::rowid_t manifestId = ManifestTable::Insert(connection, { @@ -237,9 +237,9 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 indexModified = true; } - if (channelInIndex != manifest.Installers[0].Channel) + if (channelInIndex != manifest.Channel) { - UpdateManifestValueById(connection, manifest.Installers[0].Channel, manifestId); + UpdateManifestValueById(connection, manifest.Channel, manifestId); indexModified = true; } diff --git a/src/WinGetUtil/Exports.cpp b/src/WinGetUtil/Exports.cpp index 8082221cd8..96a13921d5 100644 --- a/src/WinGetUtil/Exports.cpp +++ b/src/WinGetUtil/Exports.cpp @@ -173,18 +173,44 @@ extern "C" CATCH_RETURN() WINGET_UTIL_API WinGetValidateManifest( + WINGET_STRING manifestPath, + BOOL* succeeded, + WINGET_STRING_OUT* message) try + { + THROW_HR_IF(E_INVALIDARG, !manifestPath); + THROW_HR_IF(E_INVALIDARG, !succeeded); + + try + { + (void)YamlParser::CreateFromPath(manifestPath, true, true); + *succeeded = TRUE; + } + catch (const ManifestException& e) + { + *succeeded = e.IsWarningOnly(); + if (message) + { + *message = ::SysAllocString(ConvertToUTF16(e.GetManifestErrorMessage()).c_str()); + } + } + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetValidateManifestV2( WINGET_STRING inputPath, BOOL* succeeded, WINGET_STRING_OUT* message, WINGET_STRING mergedManifestPath, - BOOL isPartialManifest) try + WinGetValidateManifestOption option) try { THROW_HR_IF(E_INVALIDARG, !inputPath); THROW_HR_IF(E_INVALIDARG, !succeeded); try { - (void)YamlParser::CreateFromPath(inputPath, true, true, L"WinGetUtil.dll", mergedManifestPath, isPartialManifest); + (void)YamlParser::CreateFromPath(inputPath, true, true, mergedManifestPath, option == WinGetValidateManifestOption::SchemaValidationOnly); *succeeded = TRUE; } catch (const ManifestException& e) diff --git a/src/WinGetUtil/WinGetUtil.h b/src/WinGetUtil/WinGetUtil.h index 448a758fde..fe6171cb73 100644 --- a/src/WinGetUtil/WinGetUtil.h +++ b/src/WinGetUtil/WinGetUtil.h @@ -17,6 +17,12 @@ extern "C" #define WINGET_SQLITE_INDEX_VERSION_LATEST ((UINT32)-1) + enum WinGetValidateManifestOption + { + Defaut, + SchemaValidationOnly + }; + // Initializes the logging infrastructure. WINGET_UTIL_API WinGetLoggingInit( WINGET_STRING logPath); @@ -76,11 +82,20 @@ extern "C" // Validates a given manifest. Returns a bool for validation result and // a string representing validation errors if validation failed. WINGET_UTIL_API WinGetValidateManifest( + WINGET_STRING manifestPath, + BOOL* succeeded, + WINGET_STRING_OUT* message); + + // Validates a given manifest. Returns a bool for validation result and + // a string representing validation errors if validation failed. + // If mergedManifestPath is provided, this method will write a merged manifest + // to the location specified by mergedManifestPath + WINGET_UTIL_API WinGetValidateManifestV2( WINGET_STRING inputPath, BOOL* succeeded, WINGET_STRING_OUT* message, WINGET_STRING mergedManifestPath, - BOOL isPartialManifest); + WinGetValidateManifestOption option); // Downloads a file to the given path, returning the SHA 256 hash of the file. WINGET_UTIL_API WinGetDownload( From a237be10572613430a027beecff31f60f80c2ecc Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Wed, 17 Feb 2021 01:12:58 -0800 Subject: [PATCH 17/18] Move manifest schemas under /schemas/JSON folder --- .../manifests/preview/manifest.0.1.0.json | 0 .../v1.0.0/manifest.defaultLocale.1.0.0.json | 0 .../v1.0.0/manifest.installer.1.0.0.json | 3 --- .../v1.0.0/manifest.locale.1.0.0.json | 0 .../v1.0.0/manifest.singleton.1.0.0.json | 3 --- .../v1.0.0/manifest.version.1.0.0.json | 0 src/ManifestSchema/ManifestSchema.rc | 12 ++++----- src/ManifestSchema/ManifestSchema.vcxitems | 12 ++++----- .../ManifestSchema.vcxitems.filters | 26 +++++++++---------- 9 files changed, 25 insertions(+), 31 deletions(-) rename src/ManifestSchema/schema/schema_v0.1.0.json => schemas/JSON/manifests/preview/manifest.0.1.0.json (100%) rename src/ManifestSchema/schema/schema_v1.0.0_defaultLocale.json => schemas/JSON/manifests/v1.0.0/manifest.defaultLocale.1.0.0.json (100%) rename src/ManifestSchema/schema/schema_v1.0.0_installer.json => schemas/JSON/manifests/v1.0.0/manifest.installer.1.0.0.json (96%) rename src/ManifestSchema/schema/schema_v1.0.0_locale.json => schemas/JSON/manifests/v1.0.0/manifest.locale.1.0.0.json (100%) rename src/ManifestSchema/schema/schema_v1.0.0_singleton.json => schemas/JSON/manifests/v1.0.0/manifest.singleton.1.0.0.json (96%) rename src/ManifestSchema/schema/schema_v1.0.0_version.json => schemas/JSON/manifests/v1.0.0/manifest.version.1.0.0.json (100%) diff --git a/src/ManifestSchema/schema/schema_v0.1.0.json b/schemas/JSON/manifests/preview/manifest.0.1.0.json similarity index 100% rename from src/ManifestSchema/schema/schema_v0.1.0.json rename to schemas/JSON/manifests/preview/manifest.0.1.0.json diff --git a/src/ManifestSchema/schema/schema_v1.0.0_defaultLocale.json b/schemas/JSON/manifests/v1.0.0/manifest.defaultLocale.1.0.0.json similarity index 100% rename from src/ManifestSchema/schema/schema_v1.0.0_defaultLocale.json rename to schemas/JSON/manifests/v1.0.0/manifest.defaultLocale.1.0.0.json diff --git a/src/ManifestSchema/schema/schema_v1.0.0_installer.json b/schemas/JSON/manifests/v1.0.0/manifest.installer.1.0.0.json similarity index 96% rename from src/ManifestSchema/schema/schema_v1.0.0_installer.json rename to schemas/JSON/manifests/v1.0.0/manifest.installer.1.0.0.json index e25ae59a02..4bc94b537d 100644 --- a/src/ManifestSchema/schema/schema_v1.0.0_installer.json +++ b/schemas/JSON/manifests/v1.0.0/manifest.installer.1.0.0.json @@ -271,9 +271,6 @@ "Installer": { "type": "object", "properties": { - "Channel": { - "$ref": "#/definitions/Channel" - }, "InstallerLocale": { "$ref": "#/definitions/Locale" }, diff --git a/src/ManifestSchema/schema/schema_v1.0.0_locale.json b/schemas/JSON/manifests/v1.0.0/manifest.locale.1.0.0.json similarity index 100% rename from src/ManifestSchema/schema/schema_v1.0.0_locale.json rename to schemas/JSON/manifests/v1.0.0/manifest.locale.1.0.0.json diff --git a/src/ManifestSchema/schema/schema_v1.0.0_singleton.json b/schemas/JSON/manifests/v1.0.0/manifest.singleton.1.0.0.json similarity index 96% rename from src/ManifestSchema/schema/schema_v1.0.0_singleton.json rename to schemas/JSON/manifests/v1.0.0/manifest.singleton.1.0.0.json index c67c223cde..20ee70c3c2 100644 --- a/src/ManifestSchema/schema/schema_v1.0.0_singleton.json +++ b/schemas/JSON/manifests/v1.0.0/manifest.singleton.1.0.0.json @@ -284,9 +284,6 @@ "Installer": { "type": "object", "properties": { - "Channel": { - "$ref": "#/definitions/Channel" - }, "InstallerLocale": { "$ref": "#/definitions/Locale" }, diff --git a/src/ManifestSchema/schema/schema_v1.0.0_version.json b/schemas/JSON/manifests/v1.0.0/manifest.version.1.0.0.json similarity index 100% rename from src/ManifestSchema/schema/schema_v1.0.0_version.json rename to schemas/JSON/manifests/v1.0.0/manifest.version.1.0.0.json diff --git a/src/ManifestSchema/ManifestSchema.rc b/src/ManifestSchema/ManifestSchema.rc index 2de872b34f..b624ed6561 100644 --- a/src/ManifestSchema/ManifestSchema.rc +++ b/src/ManifestSchema/ManifestSchema.rc @@ -63,9 +63,9 @@ END // // Manifest schema // -IDX_MANIFEST_SCHEMA_PREVIEW MANIFESTSCHEMA_RESOURCE_TYPE "schema\\schema_v0.1.0.json" -IDX_MANIFEST_SCHEMA_V1_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "schema\\schema_v1.0.0_singleton.json" -IDX_MANIFEST_SCHEMA_V1_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "schema\\schema_v1.0.0_version.json" -IDX_MANIFEST_SCHEMA_V1_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "schema\\schema_v1.0.0_installer.json" -IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "schema\\schema_v1.0.0_defaultLocale.json" -IDX_MANIFEST_SCHEMA_V1_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "schema\\schema_v1.0.0_locale.json" +IDX_MANIFEST_SCHEMA_PREVIEW MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\preview\\manifest.0.1.0.json" +IDX_MANIFEST_SCHEMA_V1_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.singleton.1.0.0.json" +IDX_MANIFEST_SCHEMA_V1_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.version.1.0.0.json" +IDX_MANIFEST_SCHEMA_V1_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.installer.1.0.0.json" +IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.defaultLocale.1.0.0.json" +IDX_MANIFEST_SCHEMA_V1_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.locale.1.0.0.json" diff --git a/src/ManifestSchema/ManifestSchema.vcxitems b/src/ManifestSchema/ManifestSchema.vcxitems index 671144d5c5..09f45ae722 100644 --- a/src/ManifestSchema/ManifestSchema.vcxitems +++ b/src/ManifestSchema/ManifestSchema.vcxitems @@ -21,11 +21,11 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/src/ManifestSchema/ManifestSchema.vcxitems.filters b/src/ManifestSchema/ManifestSchema.vcxitems.filters index 9f37ebc1f6..31bafd420b 100644 --- a/src/ManifestSchema/ManifestSchema.vcxitems.filters +++ b/src/ManifestSchema/ManifestSchema.vcxitems.filters @@ -6,30 +6,30 @@ - + + + + + + + + schema - + schema - + schema - + schema - + schema - + schema - - - - - - - \ No newline at end of file From b5b8d7477905595c843d3f3c1bdcae9e0c9a188a Mon Sep 17 00:00:00 2001 From: Yao Sun Date: Wed, 17 Feb 2021 01:17:46 -0800 Subject: [PATCH 18/18] spelling --- .../Manifest/ManifestSchemaValidation.cpp | 2 +- src/WinGetUtil/WinGetUtil.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp index b533b35e1b..3c272369ab 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -162,7 +162,7 @@ namespace AppInstaller::Manifest::YamlParser std::vector ValidateAgainstSchema(const std::vector& manifestList, const ManifestVer& manifestVersion) { std::vector errors; - // A list of schema validators to avoid multiple loadings of same schema + // A list of schema validator to avoid multiple loadings of same schema std::map schemaList; valijson::Validator schemaValidator; diff --git a/src/WinGetUtil/WinGetUtil.h b/src/WinGetUtil/WinGetUtil.h index fe6171cb73..925ac926cb 100644 --- a/src/WinGetUtil/WinGetUtil.h +++ b/src/WinGetUtil/WinGetUtil.h @@ -19,7 +19,7 @@ extern "C" enum WinGetValidateManifestOption { - Defaut, + Default, SchemaValidationOnly };