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);