diff --git a/azure-pipelines/e2e_projects/registries-package-patterns/no-patterns.json.in b/azure-pipelines/e2e_projects/registries-package-patterns/no-patterns.json.in
new file mode 100644
index 0000000000..e1d3651205
--- /dev/null
+++ b/azure-pipelines/e2e_projects/registries-package-patterns/no-patterns.json.in
@@ -0,0 +1,29 @@
+{
+ "vcpkg-configuration": {
+ "default-registry": null,
+ "registries": [
+ {
+ "kind": "git",
+ "repository": "$E2ERegistryPath",
+ "baseline": "$E2ERegistryBaseline",
+ "packages": [
+ "foo",
+ "bar"
+ ]
+ },
+ {
+ "kind": "git",
+ "repository": "$E2ERegistryPath",
+ "baseline": "$E2ERegistryBaseline",
+ "packages": [
+ "baz"
+ ]
+ }
+ ]
+ },
+ "dependencies": [
+ "bar",
+ "baz",
+ "foo"
+ ]
+}
diff --git a/azure-pipelines/e2e_projects/registries-package-patterns/only-patterns.json.in b/azure-pipelines/e2e_projects/registries-package-patterns/only-patterns.json.in
new file mode 100644
index 0000000000..caf728240f
--- /dev/null
+++ b/azure-pipelines/e2e_projects/registries-package-patterns/only-patterns.json.in
@@ -0,0 +1,28 @@
+{
+ "vcpkg-configuration": {
+ "default-registry": null,
+ "registries": [
+ {
+ "kind": "git",
+ "repository": "$E2ERegistryPath",
+ "baseline": "$E2ERegistryBaseline",
+ "packages": [
+ "*"
+ ]
+ },
+ {
+ "kind": "git",
+ "repository": "$E2ERegistryPath",
+ "baseline": "$E2ERegistryBaseline",
+ "packages": [
+ "b*"
+ ]
+ }
+ ]
+ },
+ "dependencies": [
+ "bar",
+ "baz",
+ "foo"
+ ]
+}
diff --git a/azure-pipelines/e2e_projects/registries-package-patterns/with-default.json.in b/azure-pipelines/e2e_projects/registries-package-patterns/with-default.json.in
new file mode 100644
index 0000000000..4945519a47
--- /dev/null
+++ b/azure-pipelines/e2e_projects/registries-package-patterns/with-default.json.in
@@ -0,0 +1,24 @@
+{
+ "vcpkg-configuration": {
+ "default-registry": {
+ "kind": "git",
+ "repository": "$E2ERegistryPath",
+ "baseline": "$E2ERegistryBaseline"
+ },
+ "registries": [
+ {
+ "kind": "git",
+ "repository": "$E2ERegistryPath",
+ "baseline": "$E2ERegistryBaseline",
+ "packages": [
+ "b*"
+ ]
+ }
+ ]
+ },
+ "dependencies": [
+ "bar",
+ "baz",
+ "foo"
+ ]
+}
diff --git a/azure-pipelines/e2e_projects/registries-package-patterns/with-redeclaration.json.in b/azure-pipelines/e2e_projects/registries-package-patterns/with-redeclaration.json.in
new file mode 100644
index 0000000000..c26697bec8
--- /dev/null
+++ b/azure-pipelines/e2e_projects/registries-package-patterns/with-redeclaration.json.in
@@ -0,0 +1,32 @@
+{
+ "vcpkg-configuration": {
+ "default-registry": {
+ "kind": "git",
+ "repository": "$E2ERegistryPath",
+ "baseline": "$E2ERegistryBaseline"
+ },
+ "registries": [
+ {
+ "kind": "git",
+ "repository": "$E2ERegistryPath",
+ "baseline": "$E2ERegistryBaseline",
+ "packages": [
+ "b*"
+ ]
+ },
+ {
+ "kind": "git",
+ "repository": "$E2ERegistryPath",
+ "baseline": "$E2ERegistryBaseline",
+ "packages": [
+ "b*"
+ ]
+ }
+ ]
+ },
+ "dependencies": [
+ "bar",
+ "baz",
+ "foo"
+ ]
+}
diff --git a/azure-pipelines/e2e_registry/extra-ports/foo/portfile.cmake b/azure-pipelines/e2e_registry/extra-ports/foo/portfile.cmake
new file mode 100644
index 0000000000..9aefc82414
--- /dev/null
+++ b/azure-pipelines/e2e_registry/extra-ports/foo/portfile.cmake
@@ -0,0 +1 @@
+set(VCPKG_POLICY_EMPTY_PACKAGE enabled)
diff --git a/azure-pipelines/e2e_registry/extra-ports/foo/vcpkg.json b/azure-pipelines/e2e_registry/extra-ports/foo/vcpkg.json
new file mode 100644
index 0000000000..5b8a456e0c
--- /dev/null
+++ b/azure-pipelines/e2e_registry/extra-ports/foo/vcpkg.json
@@ -0,0 +1,5 @@
+{
+ "name": "foo",
+ "version": "1.1.0",
+ "description": "e2e test port"
+}
diff --git a/azure-pipelines/e2e_registry/ports/bar/portfile.cmake b/azure-pipelines/e2e_registry/ports/bar/portfile.cmake
new file mode 100644
index 0000000000..9aefc82414
--- /dev/null
+++ b/azure-pipelines/e2e_registry/ports/bar/portfile.cmake
@@ -0,0 +1 @@
+set(VCPKG_POLICY_EMPTY_PACKAGE enabled)
diff --git a/azure-pipelines/e2e_registry/ports/bar/vcpkg.json b/azure-pipelines/e2e_registry/ports/bar/vcpkg.json
new file mode 100644
index 0000000000..f15bd6183c
--- /dev/null
+++ b/azure-pipelines/e2e_registry/ports/bar/vcpkg.json
@@ -0,0 +1,5 @@
+{
+ "name": "bar",
+ "version": "1.0.0",
+ "description": "e2e test port"
+}
diff --git a/azure-pipelines/e2e_registry/ports/baz/portfile.cmake b/azure-pipelines/e2e_registry/ports/baz/portfile.cmake
new file mode 100644
index 0000000000..9aefc82414
--- /dev/null
+++ b/azure-pipelines/e2e_registry/ports/baz/portfile.cmake
@@ -0,0 +1 @@
+set(VCPKG_POLICY_EMPTY_PACKAGE enabled)
diff --git a/azure-pipelines/e2e_registry/ports/baz/vcpkg.json b/azure-pipelines/e2e_registry/ports/baz/vcpkg.json
new file mode 100644
index 0000000000..ec7f7978de
--- /dev/null
+++ b/azure-pipelines/e2e_registry/ports/baz/vcpkg.json
@@ -0,0 +1,5 @@
+{
+ "name": "baz",
+ "version": "1.0.0",
+ "description": "e2e test port"
+}
diff --git a/azure-pipelines/e2e_registry/ports/foo/portfile.cmake b/azure-pipelines/e2e_registry/ports/foo/portfile.cmake
new file mode 100644
index 0000000000..9aefc82414
--- /dev/null
+++ b/azure-pipelines/e2e_registry/ports/foo/portfile.cmake
@@ -0,0 +1 @@
+set(VCPKG_POLICY_EMPTY_PACKAGE enabled)
diff --git a/azure-pipelines/e2e_registry/ports/foo/vcpkg.json b/azure-pipelines/e2e_registry/ports/foo/vcpkg.json
new file mode 100644
index 0000000000..e7580a5d15
--- /dev/null
+++ b/azure-pipelines/e2e_registry/ports/foo/vcpkg.json
@@ -0,0 +1,5 @@
+{
+ "name": "foo",
+ "version": "1.0.0",
+ "description": "e2e test port"
+}
diff --git a/azure-pipelines/e2e_registry/versions/b-/bar.json b/azure-pipelines/e2e_registry/versions/b-/bar.json
new file mode 100644
index 0000000000..daee2745ad
--- /dev/null
+++ b/azure-pipelines/e2e_registry/versions/b-/bar.json
@@ -0,0 +1,9 @@
+{
+ "versions": [
+ {
+ "git-tree": "51562fff2b6db21a115564871c7174d59e77e637",
+ "version": "1.0.0",
+ "port-version": 0
+ }
+ ]
+}
diff --git a/azure-pipelines/e2e_registry/versions/b-/baz.json b/azure-pipelines/e2e_registry/versions/b-/baz.json
new file mode 100644
index 0000000000..f9cabbb0ba
--- /dev/null
+++ b/azure-pipelines/e2e_registry/versions/b-/baz.json
@@ -0,0 +1,9 @@
+{
+ "versions": [
+ {
+ "git-tree": "85462e55644fe3c9cb42928545e01784dda3e8d7",
+ "version": "1.0.0",
+ "port-version": 0
+ }
+ ]
+}
diff --git a/azure-pipelines/e2e_registry/versions/baseline.json b/azure-pipelines/e2e_registry/versions/baseline.json
new file mode 100644
index 0000000000..49cd0f29fb
--- /dev/null
+++ b/azure-pipelines/e2e_registry/versions/baseline.json
@@ -0,0 +1,16 @@
+{
+ "default": {
+ "bar": {
+ "baseline": "1.0.0",
+ "port-version": 0
+ },
+ "baz": {
+ "baseline": "1.0.0",
+ "port-version": 0
+ },
+ "foo": {
+ "baseline": "1.1.0",
+ "port-version": 0
+ }
+ }
+}
diff --git a/azure-pipelines/e2e_registry/versions/f-/foo.json b/azure-pipelines/e2e_registry/versions/f-/foo.json
new file mode 100644
index 0000000000..27bbc67ffd
--- /dev/null
+++ b/azure-pipelines/e2e_registry/versions/f-/foo.json
@@ -0,0 +1,14 @@
+{
+ "versions": [
+ {
+ "git-tree": "a1626920f5bbc6a6ccd007ba5a194f38eb8e6222",
+ "version": "1.1.0",
+ "port-version": 0
+ },
+ {
+ "git-tree": "011346ce22c43b3312e32a18c751ca6dd35c4578",
+ "version": "1.0.0",
+ "port-version": 0
+ }
+ ]
+}
diff --git a/azure-pipelines/end-to-end-tests-dir/patterns.ps1 b/azure-pipelines/end-to-end-tests-dir/patterns.ps1
new file mode 100644
index 0000000000..8cc837ee29
--- /dev/null
+++ b/azure-pipelines/end-to-end-tests-dir/patterns.ps1
@@ -0,0 +1,84 @@
+. $PSScriptRoot/../end-to-end-tests-prelude.ps1
+
+###
+# Creates a git registry to run the e2e tests on
+$e2eProjects = "$PSScriptRoot/../e2e_projects"
+$manifestRoot = "$e2eProjects/registries-package-patterns"
+$e2eRegistryPath = "$PSScriptRoot/../e2e_registry".Replace('\', '\\')
+Push-Location $e2eRegistryPath
+try
+{
+ Write-Host "Initializing test registry"
+ if (Test-Path "$e2eRegistryPath/.git")
+ {
+ Remove-Item -Recurse -Force "$e2eRegistryPath/.git"
+ }
+
+
+ $gitConfig = @(
+ '-c', 'user.name=Nobody',
+ '-c', 'user.email=nobody@example.com',
+ '-c', 'core.autocrlf=false'
+ )
+
+ git @gitConfig init . | Out-Null
+ Throw-IfFailed
+ git @gitConfig add -A | Out-Null
+ Throw-IfFailed
+ git @gitConfig commit -m "initial commit" | Out-Null
+ Throw-IfFailed
+ $e2eRegistryBaseline = git rev-parse HEAD
+ Throw-IfFailed
+}
+finally
+{
+ Pop-Location
+}
+###
+
+$commonArgs += @("--x-manifest-root=$manifestRoot")
+
+# [patterns] No patterns (no default)
+Write-Host "[patterns] No patterns (no default)"
+$inFile = "$manifestRoot/no-patterns.json.in"
+(Get-Content -Path "$inFile").Replace("`$E2ERegistryPath", $e2eRegistryPath).Replace("`$E2ERegistryBaseline", $e2eRegistryBaseline) `
+| Out-File "$manifestRoot/vcpkg.json"
+
+Run-Vcpkg -EndToEndTestSilent @commonArgs install | Out-Null
+Throw-IfFailed
+Refresh-TestRoot
+
+# [patterns] Patterns only (no default)
+Write-Host "[patterns] Patterns only (no default)"
+$inFile = "$manifestRoot/only-patterns.json.in"
+(Get-Content -Path "$inFile").Replace("`$E2ERegistryPath", $e2eRegistryPath).Replace("`$E2ERegistryBaseline", $e2eRegistryBaseline) `
+| Out-File "$manifestRoot/vcpkg.json"
+
+Run-Vcpkg -EndToEndTestSilent @commonArgs install | Out-Null
+Throw-IfFailed
+Refresh-TestRoot
+
+# [patterns] Patterns with default
+Write-Host "[patterns] Patterns with default"
+$inFile = "$manifestRoot/with-default.json.in"
+(Get-Content -Path "$inFile").Replace("`$E2ERegistryPath", $e2eRegistryPath).Replace("`$E2ERegistryBaseline", $e2eRegistryBaseline) `
+| Out-File "$manifestRoot/vcpkg.json"
+
+Run-Vcpkg -EndToEndTestSilent @commonArgs install | Out-Null
+Throw-IfFailed
+Refresh-TestRoot
+
+# [patterns] Repeated patterns
+Write-Host "[patterns] Repeated patterns"
+$inFile = "$manifestRoot/with-redeclaration.json.in"
+(Get-Content -Path "$inFile").Replace("`$E2ERegistryPath", $e2eRegistryPath).Replace("`$E2ERegistryBaseline", $e2eRegistryBaseline) `
+| Out-File "$manifestRoot/vcpkg.json"
+
+$out = Run-VcpkgAndCaptureOutput -EndToEndTestSilent @commonArgs install
+Throw-IfFailed
+if ($out -notmatch "redeclarations will be ignored")
+{
+ $out
+ throw "Expected warning about redeclaration"
+}
+Refresh-TestRoot
\ No newline at end of file
diff --git a/include/vcpkg/base/jsonreader.h b/include/vcpkg/base/jsonreader.h
index 526b889746..afa4e1867b 100644
--- a/include/vcpkg/base/jsonreader.h
+++ b/include/vcpkg/base/jsonreader.h
@@ -378,10 +378,29 @@ namespace vcpkg::Json
{
virtual StringView type_name() const override { return "a package name"; }
- static bool is_package_name(StringView sv);
-
virtual Optional visit_string(Json::Reader&, StringView sv) override;
static PackageNameDeserializer instance;
};
+
+ ///
+ /// A registry package pattern (e.g.: boost*) and the in-file location where it was declared.
+ ///
+ struct PackagePatternDeclaration
+ {
+ std::string pattern;
+ std::string location;
+ };
+
+ ///
+ /// Deserializes a list of package names and patterns along with their respective in-file declaration locations.
+ ///
+ struct PackagePatternDeserializer final : Json::IDeserializer
+ {
+ virtual StringView type_name() const override { return "a package pattern"; }
+
+ static bool is_package_pattern(StringView sv);
+
+ virtual Optional visit_string(Json::Reader&, StringView sv) override;
+ };
}
diff --git a/include/vcpkg/base/messages.h b/include/vcpkg/base/messages.h
index a62f5d13c7..6b9758e616 100644
--- a/include/vcpkg/base/messages.h
+++ b/include/vcpkg/base/messages.h
@@ -947,6 +947,13 @@ namespace vcpkg
"Comparing Utf8Decoders with different provenance; this is always an error");
DECLARE_MESSAGE(CompressFolderFailed, (msg::path), "", "Failed to compress folder \"{path}\":");
DECLARE_MESSAGE(ComputingInstallPlan, (), "", "Computing installation plan...");
+ DECLARE_MESSAGE(ConfigurationErrorRegistriesWithoutBaseline,
+ (msg::path, msg::url),
+ "",
+ "The configuration defined in {path} is invalid.\n\n"
+ "Using registries requires that a baseline is set for the default registry or that the default "
+ "registry is null.\n\n"
+ "See {url} for more details.");
DECLARE_MESSAGE(ConflictingFiles,
(msg::path, msg::spec),
"",
@@ -1101,6 +1108,11 @@ namespace vcpkg
(msg::value),
"'{value}' is a command line option.",
"'--{value}' specified multiple times.");
+ DECLARE_MESSAGE(DuplicatePackagePattern, (msg::package_name), "", "Package \"{package_name}\" is duplicated.");
+ DECLARE_MESSAGE(DuplicatePackagePatternFirstOcurrence, (), "", "First declared in:");
+ DECLARE_MESSAGE(DuplicatePackagePatternIgnoredLocations, (), "", "The following redeclarations will be ignored:");
+ DECLARE_MESSAGE(DuplicatePackagePatternLocation, (msg::path), "", "location: {path}");
+ DECLARE_MESSAGE(DuplicatePackagePatternRegistry, (msg::url), "", "registry: {url}");
DECLARE_MESSAGE(ElapsedInstallTime, (msg::count), "", "Total elapsed time: {count}");
DECLARE_MESSAGE(ElapsedTimeForChecks, (msg::elapsed), "", "Time to determine pass/fail: {elapsed}");
DECLARE_MESSAGE(EmailVcpkgTeam, (msg::url), "", "Send an email to {url} with any feedback.");
@@ -1965,6 +1977,24 @@ namespace vcpkg
"Error messages are is printed after this.",
"while loading {path}:");
DECLARE_MESSAGE(ParseControlErrorInfoWrongTypeFields, (), "", "The following fields had the wrong types:");
+ DECLARE_MESSAGE(
+ ParseIdentifierError,
+ (msg::value, msg::url),
+ "{value} is a lowercase identifier like 'boost'",
+ "\"{value}\" is not a valid identifier. "
+ "Identifiers must be lowercase alphanumeric+hypens and not reserved (see {url} for more information)");
+ DECLARE_MESSAGE(
+ ParsePackageNameError,
+ (msg::package_name, msg::url),
+ "",
+ "\"{package_name}\" is not a valid package name. "
+ "Package names must be lowercase alphanumeric+hypens and not reserved (see {url} for more information)");
+ DECLARE_MESSAGE(ParsePackagePatternError,
+ (msg::package_name, msg::url),
+ "",
+ "\"{package_name}\" is not a valid package pattern. "
+ "Package patterns must use only one wildcard character (*) and it must be the last character in "
+ "the pattern (see {url} for more information)");
DECLARE_MESSAGE(PathMustBeAbsolute,
(msg::path),
"",
@@ -2544,6 +2574,7 @@ namespace vcpkg
"The message named {value} starts with warning:, it must be changed to prepend "
"WarningMessage in code instead.");
DECLARE_MESSAGE(WarningsTreatedAsErrors, (), "", "previous warnings being interpreted as errors");
+ DECLARE_MESSAGE(WarnOnParseConfig, (msg::path), "", "Found the following warnings in configuration {path}:");
DECLARE_MESSAGE(WhileLookingForSpec, (msg::spec), "", "while looking for {spec}:");
DECLARE_MESSAGE(WindowsOnlyCommand, (), "", "This command only supports Windows.");
DECLARE_MESSAGE(WroteNuGetPkgConfInfo, (msg::path), "", "Wrote NuGet package config information to {path}");
diff --git a/include/vcpkg/configuration.h b/include/vcpkg/configuration.h
index 23232cb6d5..9e2313980e 100644
--- a/include/vcpkg/configuration.h
+++ b/include/vcpkg/configuration.h
@@ -21,6 +21,7 @@ namespace vcpkg
Optional reference;
Optional repo;
Optional> packages;
+ Optional> package_declarations;
Json::Value serialize() const;
@@ -73,5 +74,14 @@ namespace vcpkg
};
Json::IDeserializer& get_configuration_deserializer();
+ // Parse configuration from a file containing a valid vcpkg-configuration.json file
+ Optional parse_configuration(StringView contents,
+ StringView origin,
+ vcpkg::MessageSink& messageSink);
+ // Parse a configuration JSON object
+ Optional parse_configuration(const Json::Object& object,
+ StringView origin,
+ vcpkg::MessageSink& messageSink);
+
std::vector find_unknown_fields(const Configuration& config);
}
diff --git a/include/vcpkg/registries.h b/include/vcpkg/registries.h
index abae5ae06d..8d7ab99808 100644
--- a/include/vcpkg/registries.h
+++ b/include/vcpkg/registries.h
@@ -119,6 +119,11 @@ namespace vcpkg
// finds the correct registry for the port name
// Returns the null pointer if there is no registry set up for that name
const RegistryImplementation* registry_for_port(StringView port_name) const;
+
+ // Returns a list of registries that can resolve a given port name
+ // the returned list is sorted by priority.
+ std::vector registries_for_port(StringView name) const;
+
ExpectedL baseline_for_port(StringView port_name) const;
View registries() const { return registries_; }
@@ -196,4 +201,6 @@ namespace vcpkg
private:
VersionDbEntryDeserializer underlying;
};
+
+ size_t package_match_prefix(StringView name, StringView pattern);
}
diff --git a/include/vcpkg/sourceparagraph.h b/include/vcpkg/sourceparagraph.h
index fe760a90cd..aabff8fb45 100644
--- a/include/vcpkg/sourceparagraph.h
+++ b/include/vcpkg/sourceparagraph.h
@@ -137,7 +137,9 @@ namespace vcpkg
Json::Object serialize_manifest(const SourceControlFile& scf);
- ExpectedS parse_manifest_configuration(StringView origin, const Json::Object& manifest);
+ ExpectedL parse_manifest_configuration(const Json::Object& manifest,
+ StringView origin,
+ MessageSink& warningsSink);
///
/// Named pair of a SourceControlFile and the location of this file
diff --git a/locales/messages.json b/locales/messages.json
index 80108fb925..4a8046853f 100644
--- a/locales/messages.json
+++ b/locales/messages.json
@@ -265,6 +265,8 @@
"CompressFolderFailed": "Failed to compress folder \"{path}\":",
"_CompressFolderFailed.comment": "An example of {path} is /foo/bar.",
"ComputingInstallPlan": "Computing installation plan...",
+ "ConfigurationErrorRegistriesWithoutBaseline": "The configuration defined in {path} is invalid.\n\nUsing registries requires that a baseline is set for the default registry or that the default registry is null.\n\nSee {url} for more details.",
+ "_ConfigurationErrorRegistriesWithoutBaseline.comment": "An example of {path} is /foo/bar. An example of {url} is https://github.com/microsoft/vcpkg.",
"ConflictingFiles": "The following files are already installed in {path} and are in conflict with {spec}",
"_ConflictingFiles.comment": "An example of {path} is /foo/bar. An example of {spec} is zlib:x64-windows.",
"ConflictingValuesForOption": "conflicting values specified for '--{option}'.",
@@ -357,6 +359,14 @@
"_DuplicateCommandOption.comment": "An example of {option} is editable.",
"DuplicateOptions": "'--{value}' specified multiple times.",
"_DuplicateOptions.comment": "'{value}' is a command line option.",
+ "DuplicatePackagePattern": "Package \"{package_name}\" is duplicated.",
+ "_DuplicatePackagePattern.comment": "An example of {package_name} is zlib.",
+ "DuplicatePackagePatternFirstOcurrence": "First declared in:",
+ "DuplicatePackagePatternIgnoredLocations": "The following redeclarations will be ignored:",
+ "DuplicatePackagePatternLocation": "location: {path}",
+ "_DuplicatePackagePatternLocation.comment": "An example of {path} is /foo/bar.",
+ "DuplicatePackagePatternRegistry": "registry: {url}",
+ "_DuplicatePackagePatternRegistry.comment": "An example of {url} is https://github.com/microsoft/vcpkg.",
"DuplicatedKeyInObj": "Duplicated key \"{value}\" in an object",
"_DuplicatedKeyInObj.comment": "{value} is a json property/object",
"ElapsedForPackage": "Elapsed time to handle {spec}: {elapsed}",
@@ -884,6 +894,12 @@
"ParseControlErrorInfoWhileLoading": "while loading {path}:",
"_ParseControlErrorInfoWhileLoading.comment": "Error messages are is printed after this. An example of {path} is /foo/bar.",
"ParseControlErrorInfoWrongTypeFields": "The following fields had the wrong types:",
+ "ParseIdentifierError": "\"{value}\" is not a valid identifier. Identifiers must be lowercase alphanumeric+hypens and not reserved (see {url} for more information)",
+ "_ParseIdentifierError.comment": "{value} is a lowercase identifier like 'boost' An example of {url} is https://github.com/microsoft/vcpkg.",
+ "ParsePackageNameError": "\"{package_name}\" is not a valid package name. Package names must be lowercase alphanumeric+hypens and not reserved (see {url} for more information)",
+ "_ParsePackageNameError.comment": "An example of {package_name} is zlib. An example of {url} is https://github.com/microsoft/vcpkg.",
+ "ParsePackagePatternError": "\"{package_name}\" is not a valid package pattern. Package patterns must use only one wildcard character (*) and it must be the last character in the pattern (see {url} for more information)",
+ "_ParsePackagePatternError.comment": "An example of {package_name} is zlib. An example of {url} is https://github.com/microsoft/vcpkg.",
"PathMustBeAbsolute": "Value of environment variable X_VCPKG_REGISTRIES_CACHE is not absolute: {path}",
"_PathMustBeAbsolute.comment": "An example of {path} is /foo/bar.",
"PerformingPostBuildValidation": "-- Performing post-build validation",
@@ -1172,6 +1188,8 @@
"WaitingForChildrenToExit": "Waiting for child processes to exit...",
"WaitingToTakeFilesystemLock": "waiting to take filesystem lock on {path}...",
"_WaitingToTakeFilesystemLock.comment": "An example of {path} is /foo/bar.",
+ "WarnOnParseConfig": "Found the following warnings in configuration {path}:",
+ "_WarnOnParseConfig.comment": "An example of {path} is /foo/bar.",
"WarningMessage": "warning: ",
"WarningMessageMustUsePrintWarning": "The message named {value} starts with warning:, it must be changed to prepend WarningMessage in code instead.",
"_WarningMessageMustUsePrintWarning.comment": "{value} is is a localized message name like WarningMessageMustUsePrintWarning",
diff --git a/src/vcpkg-test/configmetadata.cpp b/src/vcpkg-test/configmetadata.cpp
index 4d21d904e3..0ed5e07872 100644
--- a/src/vcpkg-test/configmetadata.cpp
+++ b/src/vcpkg-test/configmetadata.cpp
@@ -197,7 +197,7 @@ TEST_CASE ("config registries only", "[ce-metadata]")
})json";
check_errors(raw_bad_fs_registry, R"(
$.registries[0] (a filesystem registry): unexpected field 'reference', did you mean 'baseline'?
-$.registries[0] (a registry): missing required field 'packages' (an array of package names)
+$.registries[0] (a registry): missing required field 'packages' (an array of package patterns)
)");
std::string raw_bad_git_registry = R"json({
@@ -214,7 +214,7 @@ TEST_CASE ("config registries only", "[ce-metadata]")
$.registries[0] (a git registry): missing required field 'repository' (a git repository URL)
$.registries[0].reference: mismatched type: expected a git reference (for example, a branch)
$.registries[0] (a git registry): unexpected field 'no-repository', did you mean 'repository'?
-$.registries[0].packages: mismatched type: expected an array of package names
+$.registries[0].packages: mismatched type: expected an array of package patterns
)");
std::string raw_bad_artifact_registry = R"json({
diff --git a/src/vcpkg-test/manifests.cpp b/src/vcpkg-test/manifests.cpp
index eb625ba1ba..972ba08650 100644
--- a/src/vcpkg-test/manifests.cpp
+++ b/src/vcpkg-test/manifests.cpp
@@ -840,7 +840,7 @@ TEST_CASE ("manifest construct maximum", "[manifests]")
"dependencies": [
"firebending",
{
- "name": "order.white-lotus",
+ "name": "order-white-lotus",
"features": [ "the-ancient-ways" ],
"platform": "!(windows & arm)"
},
@@ -898,7 +898,7 @@ TEST_CASE ("manifest construct maximum", "[manifests]")
REQUIRE(pgh.feature_paragraphs[0]->dependencies.size() == 3);
REQUIRE(pgh.feature_paragraphs[0]->dependencies[0].name == "firebending");
- REQUIRE(pgh.feature_paragraphs[0]->dependencies[1].name == "order.white-lotus");
+ REQUIRE(pgh.feature_paragraphs[0]->dependencies[1].name == "order-white-lotus");
REQUIRE(pgh.feature_paragraphs[0]->dependencies[1].features.size() == 1);
REQUIRE(pgh.feature_paragraphs[0]->dependencies[1].features[0] == "the-ancient-ways");
REQUIRE_FALSE(pgh.feature_paragraphs[0]->dependencies[1].platform.evaluate(
diff --git a/src/vcpkg-test/registries.cpp b/src/vcpkg-test/registries.cpp
index 2394ec4bd0..8c34ab1473 100644
--- a/src/vcpkg-test/registries.cpp
+++ b/src/vcpkg-test/registries.cpp
@@ -83,6 +83,155 @@ TEST_CASE ("registry_set_selects_registry", "[registries]")
}
}
+TEST_CASE ("check valid package patterns", "[registries]")
+{
+ using ID = Json::IdentifierDeserializer;
+ using PD = Json::PackagePatternDeserializer;
+
+ // test identifiers
+ CHECK(ID::is_ident("co"));
+ CHECK(ID::is_ident("rapidjson"));
+ CHECK(ID::is_ident("boost-tuple"));
+ CHECK(ID::is_ident("vcpkg-boost-helper"));
+
+ // reject invalid characters
+ CHECK(!ID::is_ident(""));
+ CHECK(!ID::is_ident("boost_tuple"));
+ CHECK(!ID::is_ident("boost.tuple"));
+ CHECK(!ID::is_ident("boost@1"));
+ CHECK(!ID::is_ident("boost#1"));
+ CHECK(!ID::is_ident("boost:x64-windows"));
+
+ // accept legacy
+ CHECK(ID::is_ident("all_modules"));
+
+ // reject reserved keywords
+ CHECK(!ID::is_ident("prn"));
+ CHECK(!ID::is_ident("aux"));
+ CHECK(!ID::is_ident("nul"));
+ CHECK(!ID::is_ident("con"));
+ CHECK(!ID::is_ident("core"));
+ CHECK(!ID::is_ident("default"));
+ CHECK(!ID::is_ident("lpt1"));
+ CHECK(!ID::is_ident("com1"));
+
+ // reject incomplete segments
+ CHECK(!ID::is_ident("-a"));
+ CHECK(!ID::is_ident("a-"));
+ CHECK(!ID::is_ident("a--"));
+ CHECK(!ID::is_ident("---"));
+
+ // accept prefixes
+ CHECK(PD::is_package_pattern("*"));
+ CHECK(PD::is_package_pattern("b*"));
+ CHECK(PD::is_package_pattern("boost*"));
+ CHECK(PD::is_package_pattern("boost-*"));
+
+ // reject invalid patterns
+ CHECK(!PD::is_package_pattern("*a"));
+ CHECK(!PD::is_package_pattern("a*a"));
+ CHECK(!PD::is_package_pattern("a**"));
+ CHECK(!PD::is_package_pattern("a+"));
+ CHECK(!PD::is_package_pattern("a?"));
+}
+
+TEST_CASE ("calculate prefix priority", "[registries]")
+{
+ CHECK(package_match_prefix("boost", "*") == 1);
+ CHECK(package_match_prefix("boost", "b*") == 2);
+ CHECK(package_match_prefix("boost", "boost*") == 6);
+ CHECK(package_match_prefix("boost", "boost") == SIZE_MAX);
+
+ CHECK(package_match_prefix("", "") == SIZE_MAX);
+ CHECK(package_match_prefix("", "*") == 1);
+ CHECK(package_match_prefix("", "a") == 0);
+ CHECK(package_match_prefix("boost", "") == 0);
+ CHECK(package_match_prefix("boost", "c*") == 0);
+ CHECK(package_match_prefix("boost", "*c") == 0);
+ CHECK(package_match_prefix("boost", "c**") == 0);
+ CHECK(package_match_prefix("boost", "c*a") == 0);
+}
+
+TEST_CASE ("select highest priority registry", "[registries]")
+{
+ std::vector rs;
+ rs.push_back(make_registry(1, {"b*"}));
+ rs.push_back(make_registry(2, {"boost*"}));
+ rs.push_back(make_registry(3, {"boost", "boost-tuple"}));
+ rs.push_back(make_registry(4, {"boost-*"}));
+ rs.push_back(make_registry(5, {"boo*"}));
+ rs.push_back(make_registry(6, {"boost", "boost-tuple"}));
+ RegistrySet set(std::make_unique(0), std::move(rs));
+
+ auto reg = set.registry_for_port("boost");
+ REQUIRE(reg);
+ CHECK(get_tri_num(*reg) == 3);
+
+ reg = set.registry_for_port("boost-algorithm");
+ REQUIRE(reg);
+ CHECK(get_tri_num(*reg) == 4);
+
+ reg = set.registry_for_port("boost-tuple");
+ REQUIRE(reg);
+ CHECK(get_tri_num(*reg) == 3);
+
+ reg = set.registry_for_port("boomerang");
+ REQUIRE(reg);
+ CHECK(get_tri_num(*reg) == 5);
+
+ reg = set.registry_for_port("bang");
+ REQUIRE(reg);
+ CHECK(get_tri_num(*reg) == 1);
+
+ reg = set.registry_for_port("cpprestsdk");
+ REQUIRE(reg);
+ CHECK(get_tri_num(*reg) == 0);
+}
+
+TEST_CASE ("sort candidate registries by priority", "[registries]")
+{
+ {
+ std::vector rs;
+ rs.push_back(make_registry(1, {"bo*"}));
+ rs.push_back(make_registry(2, {"b*"}));
+ rs.push_back(make_registry(3, {"boost*"}));
+ rs.push_back(make_registry(4, {"boost"}));
+ RegistrySet set(nullptr, std::move(rs));
+
+ auto candidates = set.registries_for_port("boost");
+ REQUIRE(candidates.size() == 4);
+ size_t idx = 0;
+
+ auto reg = candidates[idx++];
+ REQUIRE(reg);
+ CHECK(get_tri_num(*reg) == 4);
+
+ reg = candidates[idx++];
+ REQUIRE(reg);
+ CHECK(get_tri_num(*reg) == 3);
+
+ reg = candidates[idx++];
+ REQUIRE(reg);
+ CHECK(get_tri_num(*reg) == 1);
+
+ reg = candidates[idx++];
+ REQUIRE(reg);
+ CHECK(get_tri_num(*reg) == 2);
+ }
+
+ {
+ std::vector rs;
+ rs.push_back(make_registry(1, {"bo*"}));
+ rs.push_back(make_registry(2, {"b*"}));
+ rs.push_back(make_registry(3, {"boost*"}));
+ rs.push_back(make_registry(4, {"boost"}));
+ RegistrySet set(nullptr, std::move(rs));
+
+ auto candidates = set.registries_for_port("cpprestsdk");
+ REQUIRE(candidates.empty());
+ }
+}
+
static vcpkg::Optional visit_default_registry(Json::Reader& r, Json::Value&& reg)
{
Json::Object config;
@@ -225,6 +374,138 @@ TEST_CASE ("registry_parsing", "[registries]")
CHECK(r.errors().empty());
}
+TEST_CASE ("registries report pattern errors", "[registries]")
+{
+ auto test_json = parse_json(R"json({
+ "registries": [
+ {
+ "kind": "git",
+ "repository": "https://github.com/Microsoft/vcpkg",
+ "baseline": "ffff0000",
+ "packages": [ "*", "", "a*a", "*a" ]
+ }
+ ]
+})json");
+
+ Json::Reader r;
+ auto maybe_conf = r.visit(test_json, get_configuration_deserializer());
+ const auto& errors = r.errors();
+ CHECK(!errors.empty());
+ REQUIRE(errors.size() == 3);
+ CHECK(errors[0] ==
+ "$.registries[0].packages[1] (a package pattern): \"\" is not a valid package pattern. Package patterns must "
+ "use only one wildcard character (*) and it must be the last character in the pattern (see "
+ "https://github.com/Microsoft/vcpkg/tree/master/docs/users/registries.md for more information)");
+ CHECK(errors[1] ==
+ "$.registries[0].packages[2] (a package pattern): \"a*a\" is not a valid package pattern. Package patterns "
+ "must use only one wildcard character (*) and it must be the last character in the pattern (see "
+ "https://github.com/Microsoft/vcpkg/tree/master/docs/users/registries.md for more information)");
+ CHECK(errors[2] ==
+ "$.registries[0].packages[3] (a package pattern): \"*a\" is not a valid package pattern. Package patterns "
+ "must use only one wildcard character (*) and it must be the last character in the pattern (see "
+ "https://github.com/Microsoft/vcpkg/tree/master/docs/users/registries.md for more information)");
+}
+
+TEST_CASE ("registries ignored patterns warning", "[registries]")
+{
+ auto test_json = parse_json(R"json({
+ "registries": [
+ {
+ "kind": "git",
+ "repository": "https://github.com/Microsoft/vcpkg",
+ "baseline": "ffff0000",
+ "packages": [ "*", "rapidjson", "zlib" ]
+ },
+ {
+ "kind": "git",
+ "repository": "https://github.com/northwindtraders/vcpkg-registry",
+ "baseline": "aaaa0000",
+ "packages": [ "bei*", "zlib" ]
+ },
+ {
+ "kind": "git",
+ "repository": "https://github.com/another-remote/another-vcpkg-registry",
+ "baseline": "bbbb0000",
+ "packages": [ "*", "bei*", "zlib" ]
+ }
+ ]
+})json");
+
+ Json::Reader r;
+ auto maybe_conf = r.visit(test_json, get_configuration_deserializer());
+
+ auto conf = maybe_conf.get();
+ REQUIRE(conf);
+
+ auto& regs = conf->registries;
+ REQUIRE(regs.size() == 3);
+
+ auto reg = regs[0];
+ CHECK(reg.kind == "git");
+ CHECK(reg.repo == "https://github.com/Microsoft/vcpkg");
+ CHECK(reg.baseline == "ffff0000");
+ auto pkgs = reg.packages.get();
+ REQUIRE(pkgs);
+ REQUIRE(pkgs->size() == 3);
+ CHECK((*pkgs)[0] == "*");
+ CHECK((*pkgs)[1] == "rapidjson");
+ CHECK((*pkgs)[2] == "zlib");
+
+ reg = regs[1];
+ CHECK(reg.kind == "git");
+ CHECK(reg.repo == "https://github.com/northwindtraders/vcpkg-registry");
+ CHECK(reg.baseline == "aaaa0000");
+ pkgs = reg.packages.get();
+ REQUIRE(pkgs);
+ REQUIRE(pkgs->size() == 2);
+ CHECK((*pkgs)[0] == "bei*");
+ CHECK((*pkgs)[1] == "zlib");
+
+ reg = regs[2];
+ CHECK(reg.kind == "git");
+ CHECK(reg.repo == "https://github.com/another-remote/another-vcpkg-registry");
+ CHECK(reg.baseline == "bbbb0000");
+ pkgs = reg.packages.get();
+ REQUIRE(pkgs);
+ REQUIRE(pkgs->size() == 3);
+ CHECK((*pkgs)[0] == "*");
+ CHECK((*pkgs)[1] == "bei*");
+ CHECK((*pkgs)[2] == "zlib");
+
+ const auto& warnings = r.warnings();
+ REQUIRE(warnings.size() == 3);
+ CHECK(warnings[0] == R"($ (a configuration object): warning: Package "*" is duplicated.
+ First declared in:
+ location: $.registries[0].packages[0]
+ registry: https://github.com/Microsoft/vcpkg
+
+ The following redeclarations will be ignored:
+ location: $.registries[2].packages[0]
+ registry: https://github.com/another-remote/another-vcpkg-registry
+)");
+ CHECK(warnings[1] == R"($ (a configuration object): warning: Package "bei*" is duplicated.
+ First declared in:
+ location: $.registries[1].packages[0]
+ registry: https://github.com/northwindtraders/vcpkg-registry
+
+ The following redeclarations will be ignored:
+ location: $.registries[2].packages[1]
+ registry: https://github.com/another-remote/another-vcpkg-registry
+)");
+ CHECK(warnings[2] == R"($ (a configuration object): warning: Package "zlib" is duplicated.
+ First declared in:
+ location: $.registries[0].packages[2]
+ registry: https://github.com/Microsoft/vcpkg
+
+ The following redeclarations will be ignored:
+ location: $.registries[1].packages[1]
+ registry: https://github.com/northwindtraders/vcpkg-registry
+
+ location: $.registries[2].packages[2]
+ registry: https://github.com/another-remote/another-vcpkg-registry
+)");
+}
+
TEST_CASE ("git_version_db_parsing", "[registries]")
{
VersionDbEntryArrayDeserializer filesystem_version_db{VersionDbType::Git, "a/b"};
diff --git a/src/vcpkg/base/json.cpp b/src/vcpkg/base/json.cpp
index 4c5a869925..526d27b475 100644
--- a/src/vcpkg/base/json.cpp
+++ b/src/vcpkg/base/json.cpp
@@ -1351,6 +1351,8 @@ namespace vcpkg::Json
}
// } auto stringify()
+ uint64_t get_json_parsing_stats() { return g_json_parsing_stats.load(); }
+
static std::vector invalid_json_fields(const Json::Object& obj,
Span known_fields) noexcept
{
@@ -1462,9 +1464,7 @@ namespace vcpkg::Json
if (!is_ident(sv))
{
r.add_generic_error(type_name(),
- Strings::concat("must be lowercase alphanumeric+hyphens and not reserved (see ",
- vcpkg::docs::manifests_url,
- " for more information)"));
+ msg::format(msgParseIdentifierError, msg::value = sv, msg::url = docs::manifests_url));
}
return sv.to_string();
}
@@ -1474,32 +1474,86 @@ namespace vcpkg::Json
return r.array_elements(arr, IdentifierDeserializer::instance);
}
- bool PackageNameDeserializer::is_package_name(StringView sv)
+ Optional PackageNameDeserializer::visit_string(Json::Reader& r, StringView sv)
{
- if (sv.size() == 0)
+ if (!IdentifierDeserializer::is_ident(sv))
{
- return false;
+ r.add_generic_error(
+ type_name(),
+ msg::format(msgParsePackageNameError, msg::package_name = sv, msg::url = docs::manifests_url));
+ }
+ return sv.to_string();
+ }
+
+ bool PackagePatternDeserializer::is_package_pattern(StringView sv)
+ {
+ if (IdentifierDeserializer::is_ident(sv))
+ {
+ return true;
}
- for (const auto& ident : Strings::split(sv, '.'))
+ /*if (sv == "*")
{
- if (!IdentifierDeserializer::is_ident(ident))
+ return true;
+ }*/
+
+ // ([a-z0-9]+(-[a-z0-9]+)*)(\*?)
+ auto cur = sv.begin();
+ const auto last = sv.end();
+ for (;;)
+ {
+ // [a-z0-9]+
+ if (cur == last)
{
return false;
}
- }
- return true;
- }
+ if (!is_lower_digit(*cur))
+ {
+ if (*cur != '*')
+ {
+ return false;
+ }
- uint64_t get_json_parsing_stats() { return g_json_parsing_stats.load(); }
+ return ++cur == last;
+ }
+
+ do
+ {
+ ++cur;
+ if (cur == last)
+ {
+ return true;
+ }
+ } while (is_lower_digit(*cur));
- Optional PackageNameDeserializer::visit_string(Json::Reader&, StringView sv)
+ switch (*cur)
+ {
+ case '-':
+ // repeat outer [a-z0-9]+ again to match -[a-z0-9]+
+ ++cur;
+ continue;
+ case '*':
+ // match last optional *
+ ++cur;
+ return cur == last;
+ default: return false;
+ }
+ }
+ }
+
+ Optional PackagePatternDeserializer::visit_string(Json::Reader& r, StringView sv)
{
- if (!is_package_name(sv))
+ if (!is_package_pattern(sv))
{
- return nullopt;
+ r.add_generic_error(
+ type_name(),
+ msg::format(msgParsePackagePatternError, msg::package_name = sv, msg::url = docs::registries_url));
}
- return sv.to_string();
+
+ return PackagePatternDeclaration{
+ sv.to_string(),
+ r.path(),
+ };
}
}
diff --git a/src/vcpkg/base/messages.cpp b/src/vcpkg/base/messages.cpp
index c786dcd20e..947d102a8f 100644
--- a/src/vcpkg/base/messages.cpp
+++ b/src/vcpkg/base/messages.cpp
@@ -499,6 +499,7 @@ namespace vcpkg
REGISTER_MESSAGE(CommandFailed);
REGISTER_MESSAGE(CompressFolderFailed);
REGISTER_MESSAGE(ComputingInstallPlan);
+ REGISTER_MESSAGE(ConfigurationErrorRegistriesWithoutBaseline);
REGISTER_MESSAGE(ConflictingFiles);
REGISTER_MESSAGE(CMakeUsingExportedLibs);
REGISTER_MESSAGE(CommunityTriplets);
@@ -555,6 +556,11 @@ namespace vcpkg
REGISTER_MESSAGE(DuplicateCommandOption);
REGISTER_MESSAGE(DuplicatedKeyInObj);
REGISTER_MESSAGE(DuplicateOptions);
+ REGISTER_MESSAGE(DuplicatePackagePattern);
+ REGISTER_MESSAGE(DuplicatePackagePatternFirstOcurrence);
+ REGISTER_MESSAGE(DuplicatePackagePatternIgnoredLocations);
+ REGISTER_MESSAGE(DuplicatePackagePatternLocation);
+ REGISTER_MESSAGE(DuplicatePackagePatternRegistry);
REGISTER_MESSAGE(ElapsedInstallTime);
REGISTER_MESSAGE(ElapsedTimeForChecks);
REGISTER_MESSAGE(EmailVcpkgTeam);
@@ -860,6 +866,9 @@ namespace vcpkg
REGISTER_MESSAGE(ParseControlErrorInfoTypesEntry);
REGISTER_MESSAGE(ParseControlErrorInfoWhileLoading);
REGISTER_MESSAGE(ParseControlErrorInfoWrongTypeFields);
+ REGISTER_MESSAGE(ParseIdentifierError);
+ REGISTER_MESSAGE(ParsePackageNameError);
+ REGISTER_MESSAGE(ParsePackagePatternError);
REGISTER_MESSAGE(PathMustBeAbsolute);
REGISTER_MESSAGE(PECoffHeaderTooShort);
REGISTER_MESSAGE(PEConfigCrossesSectionBoundary);
@@ -1007,6 +1016,7 @@ namespace vcpkg
REGISTER_MESSAGE(WaitingToTakeFilesystemLock);
REGISTER_MESSAGE(WarningMessageMustUsePrintWarning);
REGISTER_MESSAGE(WarningsTreatedAsErrors);
+ REGISTER_MESSAGE(WarnOnParseConfig);
REGISTER_MESSAGE(WhileLookingForSpec);
REGISTER_MESSAGE(WindowsOnlyCommand);
REGISTER_MESSAGE(WroteNuGetPkgConfInfo);
diff --git a/src/vcpkg/configuration.cpp b/src/vcpkg/configuration.cpp
index 7fc5d4a198..9756ad8110 100644
--- a/src/vcpkg/configuration.cpp
+++ b/src/vcpkg/configuration.cpp
@@ -2,6 +2,7 @@
#include
#include
+#include
#include
#include
#include
@@ -196,13 +197,14 @@ namespace
if (auto config = impl.get())
{
- static Json::ArrayDeserializer package_names_deserializer{
- "an array of package names"};
+ static Json::ArrayDeserializer package_names_deserializer{
+ "an array of package patterns"};
if (config->kind && *config->kind.get() != RegistryConfigDeserializer::KIND_ARTIFACT)
{
- r.required_object_field(
- type_name(), obj, PACKAGES, config->packages.emplace(), package_names_deserializer);
+ auto& declarations = config->package_declarations.emplace();
+ r.required_object_field(type_name(), obj, PACKAGES, declarations, package_names_deserializer);
+ config->packages.emplace(Util::fmap(declarations, [](auto&& decl) { return decl.pattern; }));
}
}
return impl;
@@ -380,6 +382,82 @@ namespace
return ret;
}
+ LocalizedString& append_declaration_warning(LocalizedString& msg,
+ StringView location,
+ StringView registry,
+ size_t indent_level)
+ {
+ return msg.append_indent(indent_level)
+ .append(msgDuplicatePackagePatternLocation, msg::path = location)
+ .append_raw("\n")
+ .append_indent(indent_level)
+ .append(msgDuplicatePackagePatternRegistry, msg::url = registry)
+ .append_raw("\n");
+ }
+
+ std::vector collect_package_pattern_warnings(const std::vector& registries)
+ {
+ struct LocationAndRegistry
+ {
+ StringView location;
+ StringView registry;
+
+ LocationAndRegistry() = default;
+ };
+
+ // handle warnings from package pattern declarations
+ std::map> patterns;
+ for (auto&& reg : registries)
+ {
+ if (auto packages = reg.package_declarations.get())
+ {
+ for (auto&& pkg : *packages)
+ {
+ auto it = patterns.find(pkg.pattern);
+ if (it == patterns.end())
+ {
+ it = patterns.emplace(pkg.pattern, std::vector{}).first;
+ }
+ it->second.emplace_back(LocationAndRegistry{
+ pkg.location,
+ reg.pretty_location(),
+ });
+ }
+ }
+ }
+
+ std::vector warnings;
+ for (auto&& key_value_pair : patterns)
+ {
+ const auto& pattern = key_value_pair.first;
+ const auto& locations = key_value_pair.second;
+ if (locations.size() > 1)
+ {
+ auto first = locations.begin();
+ const auto last = locations.end();
+ auto warning = msg::format_warning(msgDuplicatePackagePattern, msg::package_name = pattern)
+ .append_raw("\n")
+ .append_indent()
+ .append(msgDuplicatePackagePatternFirstOcurrence)
+ .append_raw("\n");
+ append_declaration_warning(warning, first->location, first->registry, 2)
+ .append_raw("\n")
+ .append_indent()
+ .append(msgDuplicatePackagePatternIgnoredLocations)
+ .append_raw("\n");
+ ++first;
+ append_declaration_warning(warning, first->location, first->registry, 2);
+ while (++first != last)
+ {
+ warning.append_raw("\n");
+ append_declaration_warning(warning, first->location, first->registry, 2);
+ }
+ warnings.emplace_back(warning);
+ }
+ }
+ return warnings;
+ }
+
Optional ConfigurationDeserializer::visit_object(Json::Reader& r, const Json::Object& obj)
{
Configuration ret;
@@ -420,6 +498,11 @@ namespace
static Json::ArrayDeserializer regs_des("an array of registries");
r.optional_object_field(obj, REGISTRIES, ret.registries, regs_des);
+ for (auto&& warning : collect_package_pattern_warnings(ret.registries))
+ {
+ r.add_warning(type_name(), warning);
+ }
+
Json::Object& ce_metadata_obj = ret.ce_metadata;
auto maybe_ce_metadata = r.visit(obj, CeMetadataDeserializer::instance);
if (maybe_ce_metadata.has_value())
@@ -679,6 +762,62 @@ namespace vcpkg
Json::IDeserializer& get_configuration_deserializer() { return ConfigurationDeserializer::instance; }
+ Optional parse_configuration(StringView contents, StringView origin, MessageSink& messageSink)
+ {
+ if (contents.empty()) return nullopt;
+
+ auto conf = Json::parse(contents, origin);
+ if (!conf)
+ {
+ messageSink.println(msgFailedToParseConfig, msg::path = origin);
+ messageSink.println(Color::error, LocalizedString::from_raw(conf.error()->to_string()));
+ return nullopt;
+ }
+
+ auto conf_value = std::move(conf.value_or_exit(VCPKG_LINE_INFO));
+ if (!conf_value.first.is_object())
+ {
+ messageSink.println(msgFailedToParseNoTopLevelObj, msg::path = origin);
+ return nullopt;
+ }
+
+ return parse_configuration(std::move(conf_value.first.object(VCPKG_LINE_INFO)), origin, messageSink);
+ }
+
+ Optional parse_configuration(const Json::Object& obj, StringView origin, MessageSink& messageSink)
+ {
+ Json::Reader reader;
+ auto maybe_configuration = reader.visit(obj, get_configuration_deserializer());
+ bool has_warnings = !reader.warnings().empty();
+ bool has_errors = !reader.errors().empty();
+ if (has_warnings || has_errors)
+ {
+ if (has_errors)
+ {
+ messageSink.println(Color::error, msgFailedToParseConfig, msg::path = origin);
+ }
+ else
+ {
+ messageSink.println(Color::warning, msgWarnOnParseConfig, msg::path = origin);
+ }
+
+ for (auto&& msg : reader.errors())
+ {
+ messageSink.println(Color::error, LocalizedString().append_indent().append_raw(msg));
+ }
+
+ for (auto&& msg : reader.warnings())
+ {
+ messageSink.println(Color::warning, LocalizedString().append_indent().append(msg));
+ }
+
+ msg::println(msgExtendedDocumentationAtUrl, msg::url = docs::registries_url);
+
+ if (has_errors) return nullopt;
+ }
+ return maybe_configuration;
+ }
+
static std::unique_ptr instantiate_rconfig(const VcpkgPaths& paths,
const RegistryConfig& config,
const Path& config_dir)
diff --git a/src/vcpkg/registries.cpp b/src/vcpkg/registries.cpp
index c353962981..c1d34c282b 100644
--- a/src/vcpkg/registries.cpp
+++ b/src/vcpkg/registries.cpp
@@ -357,7 +357,7 @@ namespace
}
auto port_name = filename.substr(0, filename.size() - 5);
- if (!Json::PackageNameDeserializer::is_package_name(port_name))
+ if (!Json::IdentifierDeserializer::is_ident(port_name))
{
Checks::msg_exit_maybe_upgrade(VCPKG_LINE_INFO, msgInvalidPortVersonName, msg::path = file);
}
@@ -1147,15 +1147,72 @@ namespace vcpkg
const RegistryImplementation* RegistrySet::registry_for_port(StringView name) const
{
- for (const auto& registry : registries())
+ auto candidates = registries_for_port(name);
+ if (candidates.empty())
{
- const auto& packages = registry.packages();
- if (std::find(packages.begin(), packages.end(), name) != packages.end())
+ return default_registry();
+ }
+
+ return candidates[0];
+ }
+
+ size_t package_match_prefix(StringView name, StringView prefix)
+ {
+ if (name == prefix)
+ {
+ // exact match is like matching "infinity" prefix
+ return SIZE_MAX;
+ }
+
+ // Note that the * is included in the match so that 0 means no match
+ const auto prefix_size = prefix.size();
+ if (prefix_size != 0)
+ {
+ const auto star_index = prefix_size - 1;
+ if (prefix[star_index] == '*' && name.size() >= star_index &&
+ name.substr(0, star_index) == prefix.substr(0, star_index))
{
- return ®istry.implementation();
+ return prefix_size;
}
}
- return default_registry();
+
+ return 0;
+ }
+
+ std::vector RegistrySet::registries_for_port(StringView name) const
+ {
+ struct RegistryCandidate
+ {
+ const RegistryImplementation* impl;
+ std::size_t matched_prefix;
+ };
+
+ std::vector candidates;
+ for (auto&& registry : registries())
+ {
+ std::size_t longest_prefix = 0;
+ for (auto&& package : registry.packages())
+ {
+ longest_prefix = std::max(longest_prefix, package_match_prefix(name, package));
+ }
+
+ if (longest_prefix != 0)
+ {
+ candidates.push_back({®istry.implementation(), longest_prefix});
+ }
+ }
+
+ if (candidates.empty())
+ {
+ return std::vector();
+ }
+
+ std::stable_sort(
+ candidates.begin(), candidates.end(), [](const RegistryCandidate& lhs, const RegistryCandidate& rhs) {
+ return lhs.matched_prefix > rhs.matched_prefix;
+ });
+
+ return Util::fmap(std::move(candidates), [](const RegistryCandidate& target) { return target.impl; });
}
ExpectedL RegistrySet::baseline_for_port(StringView port_name) const
diff --git a/src/vcpkg/sourceparagraph.cpp b/src/vcpkg/sourceparagraph.cpp
index c0fb580b17..bb19873103 100644
--- a/src/vcpkg/sourceparagraph.cpp
+++ b/src/vcpkg/sourceparagraph.cpp
@@ -452,10 +452,9 @@ namespace vcpkg
virtual Optional visit_string(Json::Reader& r, StringView sv) override
{
- if (!Json::PackageNameDeserializer::is_package_name(sv))
+ if (!Json::IdentifierDeserializer::is_ident(sv))
{
- r.add_generic_error(type_name(),
- "must be lowercase alphanumeric+hyphens, split with periods, and not reserved");
+ r.add_generic_error(type_name(), "must be lowercase alphanumeric+hyphens and not reserved");
}
Dependency dep;
@@ -1189,29 +1188,42 @@ namespace vcpkg
};
ManifestConfigurationDeserializer ManifestConfigurationDeserializer::instance;
- ExpectedS parse_manifest_configuration(StringView origin,
- const Json::Object& manifest)
+ ExpectedL parse_manifest_configuration(const Json::Object& manifest,
+ StringView origin,
+ MessageSink& warningsSink)
{
Json::Reader reader;
-
auto res = reader.visit(manifest, ManifestConfigurationDeserializer::instance);
+ if (!reader.warnings().empty())
+ {
+ warningsSink.println(Color::warning, msgWarnOnParseConfig, msg::path = origin);
+ for (auto&& warning : reader.warnings())
+ {
+ warningsSink.println(Color::warning, LocalizedString::from_raw(warning));
+ }
+ warningsSink.println(Color::warning, msgExtendedDocumentationAtUrl, msg::url = docs::registries_url);
+ warningsSink.println(Color::warning, msgExtendedDocumentationAtUrl, msg::url = docs::manifests_url);
+ }
+
if (!reader.errors().empty())
{
- std::string ret = "Error: in the manifest ";
- Strings::append(ret, origin, "\nwhile obtaining configuration information from the manifest:\n");
+ LocalizedString ret;
+ ret.append(msgFailedToParseConfig, msg::path = origin);
+ ret.append_raw("\n");
for (auto&& err : reader.errors())
{
- Strings::append(ret, " ", err, "\n");
+ ret.append_indent();
+ ret.append_fmt_raw("{}\n", err);
}
- msg::println(msgExtendedDocumentationAtUrl, msg::url = docs::registries_url);
- msg::println(msgExtendedDocumentationAtUrl, msg::url = docs::manifests_url);
+ ret.append(msgExtendedDocumentationAtUrl, msg::url = docs::registries_url);
+ ret.append_raw("\n");
+ ret.append(msgExtendedDocumentationAtUrl, msg::url = docs::manifests_url);
+ ret.append_raw("\n");
return std::move(ret);
}
- else
- {
- return std::move(res).value_or_exit(VCPKG_LINE_INFO);
- }
+
+ return std::move(res).value_or_exit(VCPKG_LINE_INFO);
}
SourceControlFile SourceControlFile::clone() const
@@ -1599,18 +1611,8 @@ namespace vcpkg
if (auto configuration = scf.core_paragraph->vcpkg_configuration.get())
{
- Json::Reader reader;
- auto maybe_configuration = reader.visit(*configuration, get_configuration_deserializer());
- if (!reader.errors().empty())
- {
- msg::println_error(msgErrorWhileParsing, msg::path = ManifestDeserializer::VCPKG_CONFIGURATION);
- for (auto&& msg : reader.errors())
- {
- msg::println_error(LocalizedString().append_indent().append_raw(msg));
- }
- msg::println(msgExtendedDocumentationAtUrl, msg::url = docs::registries_url);
- Checks::exit_fail(VCPKG_LINE_INFO);
- }
+ auto maybe_configuration =
+ parse_configuration(*configuration, ManifestDeserializer::VCPKG_CONFIGURATION, stdout_sink);
obj.insert(ManifestDeserializer::VCPKG_CONFIGURATION,
maybe_configuration.value_or_exit(VCPKG_LINE_INFO).serialize());
}
diff --git a/src/vcpkg/vcpkgpaths.cpp b/src/vcpkg/vcpkgpaths.cpp
index 4a03d85504..1431b54b7b 100644
--- a/src/vcpkg/vcpkgpaths.cpp
+++ b/src/vcpkg/vcpkgpaths.cpp
@@ -88,53 +88,19 @@ namespace
return {std::move(manifest_value.first.object(VCPKG_LINE_INFO)), std::move(manifest_path)};
}
- Optional config_from_manifest(const Path& manifest_path,
- const Optional& manifest_doc)
+ static Optional config_from_manifest(const Optional& manifest_doc)
{
if (auto manifest = manifest_doc.get())
{
- return parse_manifest_configuration(manifest_path, manifest->manifest).value_or_exit(VCPKG_LINE_INFO);
+ return parse_manifest_configuration(manifest->manifest, manifest->path, stdout_sink)
+ .value_or_exit(VCPKG_LINE_INFO);
}
return nullopt;
}
- Optional config_from_json(const Path& config_path, const Filesystem& fs)
- {
- if (!fs.exists(config_path, VCPKG_LINE_INFO))
- {
- return nullopt;
- }
-
- auto parsed_config = Json::parse_file(VCPKG_LINE_INFO, fs, config_path);
- if (!parsed_config.first.is_object())
- {
- msg::println_error(msgFailedToParseNoTopLevelObj, msg::path = config_path);
- msg::println(Color::error, msg::msgSeeURL, msg::url = docs::registries_url);
- Checks::exit_fail(VCPKG_LINE_INFO);
- }
- const auto& obj = parsed_config.first.object(VCPKG_LINE_INFO);
-
- Json::Reader reader;
- auto parsed_config_opt = reader.visit(obj, get_configuration_deserializer());
- if (!reader.errors().empty())
- {
- msg::println_error(msgFailedToParseConfig, msg::path = config_path);
-
- for (auto&& msg : reader.errors())
- {
- msg::write_unlocalized_text_to_stdout(Color::none, fmt::format(" {}\n", msg));
- }
-
- msg::println(msgExtendedDocumentationAtUrl, msg::url = docs::registries_url);
- Checks::exit_fail(VCPKG_LINE_INFO);
- }
-
- return parsed_config_opt;
- }
-
- std::vector merge_overlays(const std::vector& cli_overlays,
- const std::vector& manifest_overlays,
- const std::vector& env_overlays)
+ static std::vector merge_overlays(const std::vector& cli_overlays,
+ const std::vector& manifest_overlays,
+ const std::vector& env_overlays)
{
std::vector ret = cli_overlays;
@@ -213,6 +179,26 @@ namespace
}
}
+ const auto& final_config = ret.config;
+ const bool has_ports_registries =
+ Util::any_of(final_config.registries, [](auto&& reg) { return reg.kind != "artifact"; });
+ if (has_ports_registries)
+ {
+ const auto default_registry = final_config.default_reg.get();
+ const bool is_null_default = (default_registry) ? !default_registry->kind.has_value() : false;
+ const bool has_baseline = (default_registry) ? default_registry->baseline.has_value() : false;
+ if (!is_null_default && !has_baseline)
+ {
+ auto origin =
+ ret.directory /
+ ((ret.source == ConfigurationSource::ManifestFile) ? "vcpkg.json" : "vcpkg-configuration.json");
+ msg::println_error(msgConfigurationErrorRegistriesWithoutBaseline,
+ msg::path = origin,
+ msg::url = vcpkg::docs::registries_url);
+ Checks::exit_fail(VCPKG_LINE_INFO);
+ }
+ }
+
return ret;
}
@@ -693,12 +679,17 @@ namespace vcpkg
Debug::print("Using downloads-root: ", downloads, '\n');
{
- auto maybe_manifest_config = config_from_manifest(m_pimpl->m_manifest_path, m_pimpl->m_manifest_doc);
- auto maybe_config_json = config_from_json(m_pimpl->m_config_dir / "vcpkg-configuration.json", filesystem);
+ const auto config_path = m_pimpl->m_config_dir / "vcpkg-configuration.json";
+ auto maybe_manifest_config = config_from_manifest(m_pimpl->m_manifest_doc);
+ auto maybe_json_config = !filesystem.exists(config_path, IgnoreErrors{})
+ ? nullopt
+ : parse_configuration(filesystem.read_contents(config_path, IgnoreErrors{}),
+ config_path,
+ stdout_sink);
m_pimpl->m_config = merge_validate_configs(std::move(maybe_manifest_config),
m_pimpl->m_manifest_dir,
- std::move(maybe_config_json),
+ std::move(maybe_json_config),
m_pimpl->m_config_dir,
*this);