diff options
37 files changed, 1231 insertions, 759 deletions
diff --git a/scripts/azure-pipelines/end-to-end-tests-dir/create.ps1 b/scripts/azure-pipelines/end-to-end-tests-dir/create.ps1 index 53686fe50..9d59da539 100644 --- a/scripts/azure-pipelines/end-to-end-tests-dir/create.ps1 +++ b/scripts/azure-pipelines/end-to-end-tests-dir/create.ps1 @@ -1,8 +1,8 @@ . $PSScriptRoot/../end-to-end-tests-prelude.ps1
# Test vcpkg create
-$CurrentTest = "create zlib"
-Write-Host $CurrentTest
+$Script:CurrentTest = "create zlib"
+Write-Host $Script:CurrentTest
./vcpkg --x-builtin-ports-root=$TestingRoot/ports create zlib https://github.com/madler/zlib/archive/v1.2.11.tar.gz zlib-1.2.11.tar.gz
Throw-IfFailed
diff --git a/scripts/azure-pipelines/end-to-end-tests-dir/integrate-install.ps1 b/scripts/azure-pipelines/end-to-end-tests-dir/integrate-install.ps1 index 387697751..38362ba8c 100644 --- a/scripts/azure-pipelines/end-to-end-tests-dir/integrate-install.ps1 +++ b/scripts/azure-pipelines/end-to-end-tests-dir/integrate-install.ps1 @@ -2,24 +2,24 @@ if (-not $IsLinux -and -not $IsMacOS) { . $PSScriptRoot/../end-to-end-tests-prelude.ps1
# Test msbuild props and targets
- $CurrentTest = "zlib:x86-windows-static msbuild scripts\testing\integrate-install\..."
- Write-Host $CurrentTest
+ $Script:CurrentTest = "zlib:x86-windows-static msbuild scripts\testing\integrate-install\..."
+ Write-Host $Script:CurrentTest
./vcpkg $commonArgs install zlib:x86-windows-static --x-binarysource=clear
Throw-IfFailed
foreach ($project in @("VcpkgTriplet", "VcpkgTriplet2", "VcpkgUseStatic", "VcpkgUseStatic2")) {
- $CurrentTest = "msbuild scripts\testing\integrate-install\$project.vcxproj"
+ $Script:CurrentTest = "msbuild scripts\testing\integrate-install\$project.vcxproj"
./vcpkg $commonArgs env "msbuild scripts\testing\integrate-install\$project.vcxproj /p:VcpkgRoot=$TestingRoot /p:IntDir=$TestingRoot\int\ /p:OutDir=$TestingRoot\out\ "
Throw-IfFailed
Remove-Item -Recurse -Force $TestingRoot\int
Remove-Item -Recurse -Force $TestingRoot\out
}
- $CurrentTest = "zlib:x86-windows msbuild scripts\testing\integrate-install\..."
- Write-Host $CurrentTest
+ $Script:CurrentTest = "zlib:x86-windows msbuild scripts\testing\integrate-install\..."
+ Write-Host $Script:CurrentTest
./vcpkg $commonArgs install zlib:x86-windows --x-binarysource=clear
Throw-IfFailed
foreach ($project in @("Project1", "NoProps")) {
- $CurrentTest = "msbuild scripts\testing\integrate-install\$project.vcxproj"
- Write-Host $CurrentTest
+ $Script:CurrentTest = "msbuild scripts\testing\integrate-install\$project.vcxproj"
+ Write-Host $Script:CurrentTest
./vcpkg $commonArgs env "msbuild scripts\testing\integrate-install\$project.vcxproj /p:VcpkgRoot=$TestingRoot /p:IntDir=$TestingRoot\int\ /p:OutDir=$TestingRoot\out\ "
Throw-IfFailed
Remove-Item -Recurse -Force $TestingRoot\int
diff --git a/scripts/azure-pipelines/end-to-end-tests-dir/registries.ps1 b/scripts/azure-pipelines/end-to-end-tests-dir/registries.ps1 new file mode 100644 index 000000000..b985a61f5 --- /dev/null +++ b/scripts/azure-pipelines/end-to-end-tests-dir/registries.ps1 @@ -0,0 +1,13 @@ +. "$PSScriptRoot/../end-to-end-tests-prelude.ps1" + + +$commonArgs += @("--x-builtin-port-versions-dir=$PSScriptRoot/../../e2e_ports/port_versions") + +Run-Vcpkg install @commonArgs 'vcpkg-internal-e2e-test-port' +Throw-IfNotFailed + +Run-Vcpkg install @commonArgs --feature-flags=registries 'vcpkg-internal-e2e-test-port' +Throw-IfFailed + +Run-Vcpkg install @commonArgs --feature-flags=registries 'zlib' +Throw-IfFailed diff --git a/scripts/azure-pipelines/end-to-end-tests-dir/spaces.ps1 b/scripts/azure-pipelines/end-to-end-tests-dir/spaces.ps1 index 49796c1b0..b7aaf6462 100644 --- a/scripts/azure-pipelines/end-to-end-tests-dir/spaces.ps1 +++ b/scripts/azure-pipelines/end-to-end-tests-dir/spaces.ps1 @@ -1,8 +1,8 @@ . $PSScriptRoot/../end-to-end-tests-prelude.ps1
##### Test spaces in the path
-$CurrentTest = "zlib with spaces in path"
-Write-Host $CurrentTest
+$Script:CurrentTest = "zlib with spaces in path"
+Write-Host $Script:CurrentTest
./vcpkg install zlib "--triplet" $Triplet `
"--no-binarycaching" `
"--x-buildtrees-root=$TestingRoot/build Trees" `
diff --git a/scripts/azure-pipelines/end-to-end-tests-prelude.ps1 b/scripts/azure-pipelines/end-to-end-tests-prelude.ps1 index 057df9fa6..0c675e9bf 100644 --- a/scripts/azure-pipelines/end-to-end-tests-prelude.ps1 +++ b/scripts/azure-pipelines/end-to-end-tests-prelude.ps1 @@ -12,9 +12,9 @@ $commonArgs = @( "--x-buildtrees-root=$buildtreesRoot",
"--x-install-root=$installRoot",
"--x-packages-root=$packagesRoot",
- "--overlay-ports=scripts/e2e_ports"
+ "--overlay-ports=scripts/e2e_ports/overlays"
)
-$CurrentTest = 'unassigned'
+$Script:CurrentTest = 'unassigned'
function Refresh-TestRoot {
Remove-Item -Recurse -Force $TestingRoot -ErrorAction SilentlyContinue
@@ -28,7 +28,7 @@ function Require-FileExists { [string]$File
)
if (-Not (Test-Path $File)) {
- throw "'$CurrentTest' failed to create file '$File'"
+ throw "'$Script:CurrentTest' failed to create file '$File'"
}
}
@@ -38,26 +38,29 @@ function Require-FileNotExists { [string]$File
)
if (Test-Path $File) {
- throw "'$CurrentTest' should not have created file '$File'"
+ throw "'$Script:CurrentTest' should not have created file '$File'"
}
}
function Throw-IfFailed {
if ($LASTEXITCODE -ne 0) {
- throw "'$CurrentTest' had a step with a nonzero exit code"
+ throw "'$Script:CurrentTest' had a step with a nonzero exit code"
}
}
function Throw-IfNotFailed {
if ($LASTEXITCODE -eq 0) {
- throw "'$CurrentTest' had a step with an unexpectedly zero exit code"
+ throw "'$Script:CurrentTest' had a step with an unexpectedly zero exit code"
}
}
function Run-Vcpkg {
- param([string[]]$TestArgs)
- $CurrentTest = "./vcpkg $($testArgs -join ' ')"
- Write-Host $CurrentTest
+ Param(
+ [Parameter(ValueFromRemainingArguments)]
+ [string[]]$TestArgs
+ )
+ $Script:CurrentTest = "./vcpkg $($testArgs -join ' ')"
+ Write-Host $Script:CurrentTest
./vcpkg @testArgs
}
diff --git a/scripts/azure-pipelines/end-to-end-tests.ps1 b/scripts/azure-pipelines/end-to-end-tests.ps1 index f6b5c2e6b..029120d54 100644 --- a/scripts/azure-pipelines/end-to-end-tests.ps1 +++ b/scripts/azure-pipelines/end-to-end-tests.ps1 @@ -34,7 +34,7 @@ $ErrorActionPreference = "Stop" $AllTests = Get-ChildItem $PSScriptRoot/end-to-end-tests-dir/*.ps1
if ($Filter -ne $Null) {
- $AllTests = $AllTests | ? { $_ -match $Filter }
+ $AllTests = $AllTests | ? { $_.Name -match $Filter }
}
$n = 1
$m = $AllTests.Count
diff --git a/scripts/e2e_ports/vcpkg-uses-test-cmake/portfile.cmake b/scripts/e2e_ports/overlays/vcpkg-uses-test-cmake/portfile.cmake index 571ae3b7a..571ae3b7a 100644 --- a/scripts/e2e_ports/vcpkg-uses-test-cmake/portfile.cmake +++ b/scripts/e2e_ports/overlays/vcpkg-uses-test-cmake/portfile.cmake diff --git a/scripts/e2e_ports/vcpkg-uses-test-cmake/vcpkg.json b/scripts/e2e_ports/overlays/vcpkg-uses-test-cmake/vcpkg.json index 9f34e058d..9f34e058d 100644 --- a/scripts/e2e_ports/vcpkg-uses-test-cmake/vcpkg.json +++ b/scripts/e2e_ports/overlays/vcpkg-uses-test-cmake/vcpkg.json diff --git a/scripts/e2e_ports/vcpkg-uses-vcpkg-common-functions/portfile.cmake b/scripts/e2e_ports/overlays/vcpkg-uses-vcpkg-common-functions/portfile.cmake index 9207d30b1..9207d30b1 100644 --- a/scripts/e2e_ports/vcpkg-uses-vcpkg-common-functions/portfile.cmake +++ b/scripts/e2e_ports/overlays/vcpkg-uses-vcpkg-common-functions/portfile.cmake diff --git a/scripts/e2e_ports/vcpkg-uses-vcpkg-common-functions/vcpkg.json b/scripts/e2e_ports/overlays/vcpkg-uses-vcpkg-common-functions/vcpkg.json index 7c16ffe82..7c16ffe82 100644 --- a/scripts/e2e_ports/vcpkg-uses-vcpkg-common-functions/vcpkg.json +++ b/scripts/e2e_ports/overlays/vcpkg-uses-vcpkg-common-functions/vcpkg.json diff --git a/scripts/e2e_ports/port_versions/baseline.json b/scripts/e2e_ports/port_versions/baseline.json new file mode 100644 index 000000000..953e8752d --- /dev/null +++ b/scripts/e2e_ports/port_versions/baseline.json @@ -0,0 +1,3 @@ +{ + "vcpkg-internal-e2e-test-port": { "version-string": "1.0.0" } +} diff --git a/scripts/e2e_ports/port_versions/v-/vcpkg-internal-e2e-test-port.json b/scripts/e2e_ports/port_versions/v-/vcpkg-internal-e2e-test-port.json new file mode 100644 index 000000000..ce7698ebb --- /dev/null +++ b/scripts/e2e_ports/port_versions/v-/vcpkg-internal-e2e-test-port.json @@ -0,0 +1,8 @@ +{ + "versions": [ + { + "version-string": "1.0.0", + "git-tree": "1dc3e42a3c0cafe2884d379af4399273238b986e" + } + ] +} diff --git a/scripts/e2e_ports/vcpkg-internal-e2e-test-port/portfile.cmake b/scripts/e2e_ports/vcpkg-internal-e2e-test-port/portfile.cmake new file mode 100644 index 000000000..065116c27 --- /dev/null +++ b/scripts/e2e_ports/vcpkg-internal-e2e-test-port/portfile.cmake @@ -0,0 +1 @@ +set(VCPKG_POLICY_EMPTY_PACKAGE enabled) diff --git a/scripts/e2e_ports/vcpkg-internal-e2e-test-port/vcpkg.json b/scripts/e2e_ports/vcpkg-internal-e2e-test-port/vcpkg.json new file mode 100644 index 000000000..a25da6d23 --- /dev/null +++ b/scripts/e2e_ports/vcpkg-internal-e2e-test-port/vcpkg.json @@ -0,0 +1,4 @@ +{ + "name": "vcpkg-internal-e2e-test-port", + "version-string": "1.0.0" +} diff --git a/toolsrc/include/vcpkg/base/files.h b/toolsrc/include/vcpkg/base/files.h index a6cc06c19..69ec41fe3 100644 --- a/toolsrc/include/vcpkg/base/files.h +++ b/toolsrc/include/vcpkg/base/files.h @@ -239,6 +239,12 @@ namespace vcpkg::Files virtual void current_path(const fs::path& path, std::error_code&) = 0; void current_path(const fs::path& path, LineInfo li); + // if the path does not exist, then (try_|)take_exclusive_file_lock attempts to create the file + // (but not any path members above the file itself) + // in other words, if `/a/b` is a directory, and you're attempting to lock `/a/b/c`, + // then these lock functions create `/a/b/c` if it doesn't exist; + // however, if `/a/b` doesn't exist, then the functions will fail. + // waits forever for the file lock virtual fs::SystemHandle take_exclusive_file_lock(const fs::path& path, std::error_code&) = 0; // waits, at most, 1.5 seconds, for the file lock diff --git a/toolsrc/include/vcpkg/base/jsonreader.h b/toolsrc/include/vcpkg/base/jsonreader.h index bea60e7ce..da086fa02 100644 --- a/toolsrc/include/vcpkg/base/jsonreader.h +++ b/toolsrc/include/vcpkg/base/jsonreader.h @@ -75,13 +75,13 @@ namespace vcpkg::Json }; std::vector<Path> m_path; + public: // checks that an object doesn't contain any fields which both: // * don't start with a `$` // * are not in `valid_fields` // if known_fields.empty(), then it's treated as if all field names are valid void check_for_unexpected_fields(const Object& obj, View<StringView> valid_fields, StringView type_name); - public: template<class Type> void required_object_field( StringView type, const Object& obj, StringView key, Type& place, IDeserializer<Type>& visitor) diff --git a/toolsrc/include/vcpkg/configuration.h b/toolsrc/include/vcpkg/configuration.h index 4cba88fe5..6d6a9b1f4 100644 --- a/toolsrc/include/vcpkg/configuration.h +++ b/toolsrc/include/vcpkg/configuration.h @@ -1,6 +1,10 @@ #pragma once #include <vcpkg/fwd/configuration.h> +#include <vcpkg/fwd/vcpkgcmdarguments.h> + +#include <vcpkg/base/files.h> +#include <vcpkg/base/json.h> #include <vcpkg/registries.h> @@ -12,5 +16,10 @@ namespace vcpkg // `registries` and `default_registry`. The fall back logic is // taken care of in RegistrySet. RegistrySet registry_set; + + void validate_feature_flags(const FeatureFlagSettings& flags); }; + + std::unique_ptr<Json::IDeserializer<Configuration>> make_configuration_deserializer( + const fs::path& config_directory); } diff --git a/toolsrc/include/vcpkg/configurationdeserializer.h b/toolsrc/include/vcpkg/configurationdeserializer.h deleted file mode 100644 index d6b239356..000000000 --- a/toolsrc/include/vcpkg/configurationdeserializer.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include <vcpkg/base/fwd/json.h> - -#include <vcpkg/fwd/vcpkgcmdarguments.h> - -#include <vcpkg/base/json.h> -#include <vcpkg/base/optional.h> -#include <vcpkg/base/stringliteral.h> -#include <vcpkg/base/stringview.h> -#include <vcpkg/base/view.h> - -#include <vcpkg/configuration.h> -#include <vcpkg/registries.h> - -namespace vcpkg -{ - struct RegistryImplDeserializer : Json::IDeserializer<std::unique_ptr<RegistryImpl>> - { - constexpr static StringLiteral KIND = "kind"; - constexpr static StringLiteral PATH = "path"; - - constexpr static StringLiteral KIND_BUILTIN = "builtin"; - constexpr static StringLiteral KIND_FILESYSTEM = "filesystem"; - - virtual StringView type_name() const override; - virtual View<StringView> valid_fields() const override; - - virtual Optional<std::unique_ptr<RegistryImpl>> visit_null(Json::Reader&) override; - virtual Optional<std::unique_ptr<RegistryImpl>> visit_object(Json::Reader&, const Json::Object&) override; - - static RegistryImplDeserializer instance; - }; - - struct RegistryDeserializer final : Json::IDeserializer<Registry> - { - constexpr static StringLiteral PACKAGES = "packages"; - - virtual StringView type_name() const override; - virtual View<StringView> valid_fields() const override; - - virtual Optional<Registry> visit_object(Json::Reader&, const Json::Object&) override; - }; - - struct ConfigurationDeserializer final : Json::IDeserializer<Configuration> - { - virtual StringView type_name() const override { return "a configuration object"; } - - constexpr static StringLiteral DEFAULT_REGISTRY = "default-registry"; - constexpr static StringLiteral REGISTRIES = "registries"; - virtual View<StringView> valid_fields() const override - { - constexpr static StringView t[] = {DEFAULT_REGISTRY, REGISTRIES}; - return t; - } - - virtual Optional<Configuration> visit_object(Json::Reader& r, const Json::Object& obj) override; - - ConfigurationDeserializer(const VcpkgCmdArguments& args); - - private: - bool print_json; - - bool registries_enabled; - }; -} diff --git a/toolsrc/include/vcpkg/fwd/registries.h b/toolsrc/include/vcpkg/fwd/registries.h index 7352c429d..73783cc6b 100644 --- a/toolsrc/include/vcpkg/fwd/registries.h +++ b/toolsrc/include/vcpkg/fwd/registries.h @@ -2,7 +2,8 @@ namespace vcpkg { - struct RegistryImpl; + struct RegistryEntry; + struct RegistryImplementation; struct Registry; struct RegistrySet; } diff --git a/toolsrc/include/vcpkg/portfileprovider.h b/toolsrc/include/vcpkg/portfileprovider.h index b93c58e8c..fde28b0df 100644 --- a/toolsrc/include/vcpkg/portfileprovider.h +++ b/toolsrc/include/vcpkg/portfileprovider.h @@ -5,6 +5,7 @@ #include <vcpkg/base/expected.h> #include <vcpkg/base/util.h> +#include <vcpkg/registries.h> #include <vcpkg/sourceparagraph.h> #include <vcpkg/versions.h> @@ -41,21 +42,19 @@ namespace vcpkg::PortFileProvider struct IVersionedPortfileProvider { - virtual const std::vector<vcpkg::Versions::VersionSpec>& get_port_versions(StringView port_name) const = 0; + virtual View<VersionT> get_port_versions(StringView port_name) const = 0; virtual ~IVersionedPortfileProvider() = default; virtual ExpectedS<const SourceControlFileLocation&> get_control_file( - const vcpkg::Versions::VersionSpec& version_spec) const = 0; + const Versions::VersionSpec& version_spec) const = 0; }; struct IBaselineProvider { - virtual ~IBaselineProvider() = default; - virtual Optional<VersionT> get_baseline_version(StringView port_name) const = 0; + virtual ~IBaselineProvider() = default; }; - std::unique_ptr<IBaselineProvider> make_baseline_provider(const vcpkg::VcpkgPaths& paths); - std::unique_ptr<IBaselineProvider> make_baseline_provider(const vcpkg::VcpkgPaths& paths, StringView baseline); - std::unique_ptr<IVersionedPortfileProvider> make_versioned_portfile_provider(const vcpkg::VcpkgPaths& paths); + std::unique_ptr<IBaselineProvider> make_baseline_provider(const VcpkgPaths&); + std::unique_ptr<IVersionedPortfileProvider> make_versioned_portfile_provider(const VcpkgPaths&); } diff --git a/toolsrc/include/vcpkg/registries.h b/toolsrc/include/vcpkg/registries.h index 1f8c0f393..006bc789d 100644 --- a/toolsrc/include/vcpkg/registries.h +++ b/toolsrc/include/vcpkg/registries.h @@ -1,8 +1,11 @@ #pragma once +#include <vcpkg/fwd/configuration.h> +#include <vcpkg/fwd/registries.h> #include <vcpkg/fwd/vcpkgpaths.h> #include <vcpkg/base/files.h> +#include <vcpkg/base/jsonreader.h> #include <vcpkg/base/stringview.h> #include <vcpkg/base/view.h> @@ -17,40 +20,45 @@ namespace vcpkg { struct RegistryEntry { - // returns fs::path() if version doesn't exist - virtual fs::path get_port_directory(const VcpkgPaths& paths, const VersionT& version) const = 0; + virtual View<VersionT> get_port_versions() const = 0; + + virtual ExpectedS<fs::path> get_path_to_version(const VcpkgPaths& paths, const VersionT& version) const = 0; virtual ~RegistryEntry() = default; }; - struct RegistryImpl + struct RegistryImplementation { // returns nullptr if the port doesn't exist virtual std::unique_ptr<RegistryEntry> get_port_entry(const VcpkgPaths& paths, StringView port_name) const = 0; + // appends the names of the ports to the out parameter + // may result in duplicated port names; make sure to Util::sort_unique_erase at the end virtual void get_all_port_names(std::vector<std::string>& port_names, const VcpkgPaths& paths) const = 0; virtual Optional<VersionT> get_baseline_version(const VcpkgPaths& paths, StringView port_name) const = 0; - virtual ~RegistryImpl() = default; + virtual ~RegistryImplementation() = default; }; struct Registry { // requires: static_cast<bool>(implementation) - Registry(std::vector<std::string>&& packages, std::unique_ptr<RegistryImpl>&& implementation); + Registry(std::vector<std::string>&& packages, std::unique_ptr<RegistryImplementation>&& implementation); Registry(std::vector<std::string>&&, std::nullptr_t) = delete; // always ordered lexicographically View<std::string> packages() const { return packages_; } - const RegistryImpl& implementation() const { return *implementation_; } + const RegistryImplementation& implementation() const { return *implementation_; } + + static std::unique_ptr<RegistryImplementation> builtin_registry(std::string&& baseline = {}); - static std::unique_ptr<RegistryImpl> builtin_registry(); + friend RegistrySet; // for experimental_set_builtin_registry_baseline private: std::vector<std::string> packages_; - std::unique_ptr<RegistryImpl> implementation_; + std::unique_ptr<RegistryImplementation> implementation_; }; // this type implements the registry fall back logic from the registries RFC: @@ -65,20 +73,34 @@ 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 RegistryImpl* registry_for_port(StringView port_name) const; + const RegistryImplementation* registry_for_port(StringView port_name) const; + Optional<VersionT> baseline_for_port(const VcpkgPaths& paths, StringView port_name) const; View<Registry> registries() const { return registries_; } - const RegistryImpl* default_registry() const { return default_registry_.get(); } + const RegistryImplementation* default_registry() const { return default_registry_.get(); } // TODO: figure out how to get this to return an error (or maybe it should be a warning?) void add_registry(Registry&& r); - void set_default_registry(std::unique_ptr<RegistryImpl>&& r); + void set_default_registry(std::unique_ptr<RegistryImplementation>&& r); void set_default_registry(std::nullptr_t r); + // this exists in order to allow versioning and registries to be developed and tested separately + void experimental_set_builtin_registry_baseline(StringView baseline) const; + + // returns whether the registry set has any modifications to the default + // (i.e., whether `default_registry` was set, or `registries` had any entries) + // for checking against the registry feature flag. + bool has_modifications() const; + private: - std::unique_ptr<RegistryImpl> default_registry_; + std::unique_ptr<RegistryImplementation> default_registry_; std::vector<Registry> registries_; }; + std::unique_ptr<Json::IDeserializer<std::unique_ptr<RegistryImplementation>>> + get_registry_implementation_deserializer(const fs::path& configuration_directory); + + std::unique_ptr<Json::IDeserializer<std::vector<Registry>>> get_registry_array_deserializer( + const fs::path& configuration_directory); } diff --git a/toolsrc/include/vcpkg/sourceparagraph.h b/toolsrc/include/vcpkg/sourceparagraph.h index ee07826bb..50417b8ad 100644 --- a/toolsrc/include/vcpkg/sourceparagraph.h +++ b/toolsrc/include/vcpkg/sourceparagraph.h @@ -116,8 +116,8 @@ namespace vcpkg Json::Object serialize_debug_manifest(const SourceControlFile& scf); /// <summary> - /// Full metadata of a package: core and other features. As well as the location the SourceControlFile was - /// loaded from. + /// Full metadata of a package: core and other features, + /// as well as the port directory the SourceControlFile was loaded from /// </summary> struct SourceControlFileLocation { diff --git a/toolsrc/include/vcpkg/vcpkgcmdarguments.h b/toolsrc/include/vcpkg/vcpkgcmdarguments.h index f18f4843c..2677e6811 100644 --- a/toolsrc/include/vcpkg/vcpkgcmdarguments.h +++ b/toolsrc/include/vcpkg/vcpkgcmdarguments.h @@ -134,6 +134,8 @@ namespace vcpkg std::unique_ptr<std::string> scripts_root_dir; constexpr static StringLiteral BUILTIN_PORTS_ROOT_DIR_ARG = "x-builtin-ports-root"; std::unique_ptr<std::string> builtin_ports_root_dir; + constexpr static StringLiteral BUILTIN_PORT_VERSIONS_DIR_ARG = "x-builtin-port-versions-dir"; + std::unique_ptr<std::string> builtin_port_versions_dir; constexpr static StringLiteral DEFAULT_VISUAL_STUDIO_PATH_ENV = "VCPKG_VISUAL_STUDIO_PATH"; std::unique_ptr<std::string> default_visual_studio_path; diff --git a/toolsrc/include/vcpkg/vcpkgpaths.h b/toolsrc/include/vcpkg/vcpkgpaths.h index e71f28b0a..1f562c151 100644 --- a/toolsrc/include/vcpkg/vcpkgpaths.h +++ b/toolsrc/include/vcpkg/vcpkgpaths.h @@ -92,6 +92,7 @@ namespace vcpkg fs::path scripts; fs::path prefab; fs::path builtin_ports; + fs::path builtin_port_versions; fs::path tools; fs::path buildsystems; @@ -116,7 +117,7 @@ namespace vcpkg const fs::path& get_tool_exe(const std::string& tool) const; const std::string& get_tool_version(const std::string& tool) const; - // Git manipulation + // Git manipulation in the vcpkg directory fs::path git_checkout_baseline(Files::Filesystem& filesystem, StringView commit_sha) const; fs::path git_checkout_port(Files::Filesystem& filesystem, StringView port_name, StringView git_tree) const; ExpectedS<std::string> git_show(const std::string& treeish, const fs::path& dot_git_dir) const; diff --git a/toolsrc/include/vcpkg/versiondeserializers.h b/toolsrc/include/vcpkg/versiondeserializers.h index 2efe340d7..02696d39b 100644 --- a/toolsrc/include/vcpkg/versiondeserializers.h +++ b/toolsrc/include/vcpkg/versiondeserializers.h @@ -10,13 +10,6 @@ namespace vcpkg { - struct VersionDbEntry - { - VersionT version; - Versions::Scheme scheme = Versions::Scheme::String; - std::string git_tree; - }; - Json::IDeserializer<VersionT>& get_versiont_deserializer_instance(); std::unique_ptr<Json::IDeserializer<std::string>> make_version_deserializer(StringLiteral type_name); @@ -44,12 +37,4 @@ namespace vcpkg const std::string& version, int port_version, bool always_emit_port_version = false); - - ExpectedS<std::map<std::string, VersionT, std::less<>>> parse_baseline_file(Files::Filesystem& fs, - StringView baseline_name, - const fs::path& baseline_file_path); - - ExpectedS<std::vector<VersionDbEntry>> parse_versions_file(Files::Filesystem& fs, - StringView port_name, - const fs::path& versions_file_path); } diff --git a/toolsrc/src/vcpkg-test/dependencies.cpp b/toolsrc/src/vcpkg-test/dependencies.cpp index d1834b13d..07f9d0852 100644 --- a/toolsrc/src/vcpkg-test/dependencies.cpp +++ b/toolsrc/src/vcpkg-test/dependencies.cpp @@ -54,10 +54,7 @@ struct MockVersionedPortfileProvider : PortFileProvider::IVersionedPortfileProvi return it2->second; } - virtual const std::vector<vcpkg::Versions::VersionSpec>& get_port_versions(StringView) const override - { - Checks::unreachable(VCPKG_LINE_INFO); - } + virtual View<vcpkg::VersionT> get_port_versions(StringView) const override { Checks::unreachable(VCPKG_LINE_INFO); } SourceControlFileLocation& emplace(std::string&& name, Versions::Version&& version, diff --git a/toolsrc/src/vcpkg-test/registries.cpp b/toolsrc/src/vcpkg-test/registries.cpp new file mode 100644 index 000000000..1c839409d --- /dev/null +++ b/toolsrc/src/vcpkg-test/registries.cpp @@ -0,0 +1,132 @@ +#include <catch2/catch.hpp> + +#include <vcpkg/base/jsonreader.h> + +#include <vcpkg/registries.h> + +using namespace vcpkg; + +namespace +{ + struct TestRegistryImplementation final : RegistryImplementation + { + std::unique_ptr<RegistryEntry> get_port_entry(const VcpkgPaths&, StringView) const override { return nullptr; } + + void get_all_port_names(std::vector<std::string>&, const VcpkgPaths&) const override { } + + Optional<VersionT> get_baseline_version(const VcpkgPaths&, StringView) const override { return nullopt; } + + int number; + + TestRegistryImplementation(int n) : number(n) { } + }; + + Registry make_registry(int n, std::vector<std::string>&& port_names) + { + return {std::move(port_names), std::make_unique<TestRegistryImplementation>(n)}; + } + + int get_tri_num(const RegistryImplementation& r) + { + if (auto tri = dynamic_cast<const TestRegistryImplementation*>(&r)) + { + return tri->number; + } + else + { + return -1; + } + } + + // test functions which parse string literals, so no concerns about failure + Json::Value parse_json(StringView sv) { return Json::parse(sv).value_or_exit(VCPKG_LINE_INFO).first; } +} + +TEST_CASE ("registry_set_selects_registry", "[registries]") +{ + RegistrySet set; + set.set_default_registry(std::make_unique<TestRegistryImplementation>(0)); + + set.add_registry(make_registry(1, {"p1", "q1", "r1"})); + set.add_registry(make_registry(2, {"p2", "q2", "r2"})); + + auto reg = set.registry_for_port("p1"); + REQUIRE(reg); + CHECK(get_tri_num(*reg) == 1); + reg = set.registry_for_port("r2"); + REQUIRE(reg); + CHECK(get_tri_num(*reg) == 2); + reg = set.registry_for_port("a"); + REQUIRE(reg); + CHECK(get_tri_num(*reg) == 0); + + set.set_default_registry(nullptr); + + reg = set.registry_for_port("q1"); + REQUIRE(reg); + CHECK(get_tri_num(*reg) == 1); + reg = set.registry_for_port("p2"); + REQUIRE(reg); + CHECK(get_tri_num(*reg) == 2); + reg = set.registry_for_port("a"); + CHECK_FALSE(reg); +} + +TEST_CASE ("registry_parsing", "[registries]") +{ + Json::Reader r; + auto registry_impl_des = get_registry_implementation_deserializer({}); + + auto test_json = parse_json(R"json( +{ + "kind": "builtin" +} + )json"); + auto registry_impl = r.visit(test_json, *registry_impl_des); + REQUIRE(registry_impl); + CHECK(*registry_impl.get()); + CHECK(r.errors().empty()); + + test_json = parse_json(R"json( +{ + "kind": "builtin", + "baseline": "hi" +} + )json"); + registry_impl = r.visit(test_json, *registry_impl_des); + REQUIRE(registry_impl); + CHECK(*registry_impl.get()); + CHECK(r.errors().empty()); + + test_json = parse_json(R"json( +{ + "kind": "builtin", + "path": "a/b" +} + )json"); + registry_impl = r.visit(test_json, *registry_impl_des); + CHECK_FALSE(r.errors().empty()); + r.errors().clear(); + + test_json = parse_json(R"json( +{ + "kind": "filesystem", + "path": "a/b/c" +} + )json"); + registry_impl = r.visit(test_json, *registry_impl_des); + REQUIRE(registry_impl); + CHECK(*registry_impl.get()); + CHECK(r.errors().empty()); + + test_json = parse_json(R"json( +{ + "kind": "filesystem", + "path": "/a/b/c" +} + )json"); + registry_impl = r.visit(test_json, *registry_impl_des); + REQUIRE(registry_impl); + CHECK(*registry_impl.get()); + CHECK(r.errors().empty()); +} diff --git a/toolsrc/src/vcpkg/base/files.cpp b/toolsrc/src/vcpkg/base/files.cpp index b0717f2d6..ce68741d5 100644 --- a/toolsrc/src/vcpkg/base/files.cpp +++ b/toolsrc/src/vcpkg/base/files.cpp @@ -1216,7 +1216,7 @@ namespace vcpkg::Files { } -#if defined(WIN32) +#if defined(_WIN32) void assign_busy_error(std::error_code& ec) { ec.assign(ERROR_BUSY, std::system_category()); } bool operator()(std::error_code& ec) @@ -1242,7 +1242,7 @@ namespace vcpkg::Files res.system_handle = reinterpret_cast<intptr_t>(handle); return true; } -#else // ^^^ WIN32 / !WIN32 vvv +#else // ^^^ _WIN32 / !_WIN32 vvv int fd = -1; void assign_busy_error(std::error_code& ec) { ec.assign(EBUSY, std::generic_category()); } @@ -1252,7 +1252,7 @@ namespace vcpkg::Files ec.clear(); if (fd == -1) { - fd = ::open(native.c_str(), 0); + fd = ::open(native.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd < 0) { ec.assign(errno, std::generic_category()); @@ -1335,7 +1335,7 @@ namespace vcpkg::Files virtual void unlock_file_lock(fs::SystemHandle handle, std::error_code& ec) override { -#if defined(WIN32) +#if defined(_WIN32) if (CloseHandle(reinterpret_cast<HANDLE>(handle.system_handle)) == 0) { ec.assign(GetLastError(), std::system_category()); diff --git a/toolsrc/src/vcpkg/configuration.cpp b/toolsrc/src/vcpkg/configuration.cpp index f062af51d..3fa02876f 100644 --- a/toolsrc/src/vcpkg/configuration.cpp +++ b/toolsrc/src/vcpkg/configuration.cpp @@ -2,66 +2,83 @@ #include <vcpkg/base/system.print.h> #include <vcpkg/configuration.h> -#include <vcpkg/configurationdeserializer.h> #include <vcpkg/vcpkgcmdarguments.h> -namespace vcpkg +namespace { - Optional<Configuration> ConfigurationDeserializer::visit_object(Json::Reader& r, const Json::Object& obj) - { - RegistrySet registries; + using namespace vcpkg; - bool registries_feature_flags_warning = false; + struct ConfigurationDeserializer final : Json::IDeserializer<Configuration> + { + virtual StringView type_name() const override { return "a configuration object"; } + constexpr static StringLiteral DEFAULT_REGISTRY = "default-registry"; + constexpr static StringLiteral REGISTRIES = "registries"; + virtual View<StringView> valid_fields() const override { - std::unique_ptr<RegistryImpl> default_registry; - if (r.optional_object_field(obj, DEFAULT_REGISTRY, default_registry, RegistryImplDeserializer::instance)) - { - if (!registries_enabled) - { - registries_feature_flags_warning = true; - } - else - { - registries.set_default_registry(std::move(default_registry)); - } - } + constexpr static StringView t[] = {DEFAULT_REGISTRY, REGISTRIES}; + return t; } - static Json::ArrayDeserializer<RegistryDeserializer> array_of_registries{"an array of registries"}; + virtual Optional<Configuration> visit_object(Json::Reader& r, const Json::Object& obj) override; - std::vector<Registry> regs; - r.optional_object_field(obj, REGISTRIES, regs, array_of_registries); + ConfigurationDeserializer(const fs::path& configuration_directory); + + private: + fs::path configuration_directory; + }; + + constexpr StringLiteral ConfigurationDeserializer::DEFAULT_REGISTRY; + constexpr StringLiteral ConfigurationDeserializer::REGISTRIES; - if (!regs.empty() && !registries_enabled) + Optional<Configuration> ConfigurationDeserializer::visit_object(Json::Reader& r, const Json::Object& obj) + { + RegistrySet registries; + + auto impl_des = get_registry_implementation_deserializer(configuration_directory); + + std::unique_ptr<RegistryImplementation> default_registry; + if (r.optional_object_field(obj, DEFAULT_REGISTRY, default_registry, *impl_des)) { - registries_feature_flags_warning = true; - regs.clear(); + registries.set_default_registry(std::move(default_registry)); } + auto reg_des = get_registry_array_deserializer(configuration_directory); + std::vector<Registry> regs; + r.optional_object_field(obj, REGISTRIES, regs, *reg_des); + for (Registry& reg : regs) { registries.add_registry(std::move(reg)); } - if (registries_feature_flags_warning && !print_json) + return Configuration{std::move(registries)}; + } + + ConfigurationDeserializer::ConfigurationDeserializer(const fs::path& configuration_directory) + : configuration_directory(configuration_directory) + { + } + +} + +std::unique_ptr<Json::IDeserializer<Configuration>> vcpkg::make_configuration_deserializer( + const fs::path& config_directory) +{ + return std::make_unique<ConfigurationDeserializer>(config_directory); +} + +namespace vcpkg +{ + void Configuration::validate_feature_flags(const FeatureFlagSettings& flags) + { + if (!flags.registries && registry_set.has_modifications()) { System::printf(System::Color::warning, "Warning: configuration specified the \"registries\" or \"default-registries\" field, but " "the %s feature flag was not enabled.\n", VcpkgCmdArguments::REGISTRIES_FEATURE); + registry_set = RegistrySet(); } - - return Configuration{std::move(registries)}; - } - - constexpr StringLiteral ConfigurationDeserializer::DEFAULT_REGISTRY; - constexpr StringLiteral ConfigurationDeserializer::REGISTRIES; - - ConfigurationDeserializer::ConfigurationDeserializer(const VcpkgCmdArguments& args) - { - registries_enabled = args.registries_enabled(); - print_json = args.output_json(); } - } diff --git a/toolsrc/src/vcpkg/install.cpp b/toolsrc/src/vcpkg/install.cpp index a07fb431c..50f07c732 100644 --- a/toolsrc/src/vcpkg/install.cpp +++ b/toolsrc/src/vcpkg/install.cpp @@ -7,6 +7,7 @@ #include <vcpkg/build.h> #include <vcpkg/cmakevars.h> #include <vcpkg/commands.setinstalled.h> +#include <vcpkg/configuration.h> #include <vcpkg/dependencies.h> #include <vcpkg/globalstate.h> #include <vcpkg/help.h> @@ -16,6 +17,7 @@ #include <vcpkg/paragraphs.h> #include <vcpkg/remove.h> #include <vcpkg/vcpkglib.h> +#include <vcpkg/vcpkgpaths.h> namespace vcpkg::Install { @@ -841,16 +843,12 @@ namespace vcpkg::Install if (args.versions_enabled()) { auto verprovider = PortFileProvider::make_versioned_portfile_provider(paths); - auto baseprovider = [&]() -> std::unique_ptr<PortFileProvider::IBaselineProvider> { - if (auto p_baseline = manifest_scf.core_paragraph->extra_info.get("$x-default-baseline")) - { - return PortFileProvider::make_baseline_provider(paths, p_baseline->string().to_string()); - } - else - { - return PortFileProvider::make_baseline_provider(paths); - } - }(); + auto baseprovider = PortFileProvider::make_baseline_provider(paths); + if (auto p_baseline = manifest_scf.core_paragraph->extra_info.get("$x-default-baseline")) + { + paths.get_configuration().registry_set.experimental_set_builtin_registry_baseline( + p_baseline->string()); + } auto install_plan = Dependencies::create_versioned_install_plan(*verprovider, diff --git a/toolsrc/src/vcpkg/paragraphs.cpp b/toolsrc/src/vcpkg/paragraphs.cpp index 421f28b00..32103c76b 100644 --- a/toolsrc/src/vcpkg/paragraphs.cpp +++ b/toolsrc/src/vcpkg/paragraphs.cpp @@ -357,14 +357,23 @@ namespace vcpkg::Paragraphs return res; } - ExpectedS<std::vector<Paragraph>> pghs = get_paragraphs(fs, path_to_control); - if (auto vector_pghs = pghs.get()) + + if (fs.exists(path_to_control)) { - return SourceControlFile::parse_control_file(fs::u8string(path_to_control), std::move(*vector_pghs)); + ExpectedS<std::vector<Paragraph>> pghs = get_paragraphs(fs, path_to_control); + if (auto vector_pghs = pghs.get()) + { + return SourceControlFile::parse_control_file(fs::u8string(path_to_control), std::move(*vector_pghs)); + } + auto error_info = std::make_unique<ParseControlErrorInfo>(); + error_info->name = fs::u8string(path.filename()); + error_info->error = pghs.error(); + return error_info; } + auto error_info = std::make_unique<ParseControlErrorInfo>(); error_info->name = fs::u8string(path.filename()); - error_info->error = pghs.error(); + error_info->error = "Failed to find either a CONTROL file or vcpkg.json file."; return error_info; } @@ -423,15 +432,8 @@ namespace vcpkg::Paragraphs auto baseline_version = impl->get_baseline_version(paths, port_name); if (port_entry && baseline_version) { - auto port_path = port_entry->get_port_directory(paths, *baseline_version.get()); - if (port_path.empty()) - { - Debug::print("Registry for port `", - port_name, - "` is incorrect - baseline port version `", - baseline_version.get()->to_string(), - "` not found."); - } + auto port_path = + port_entry->get_path_to_version(paths, *baseline_version.get()).value_or_exit(VCPKG_LINE_INFO); auto maybe_spgh = try_load_port(fs, port_path); if (const auto spgh = maybe_spgh.get()) { diff --git a/toolsrc/src/vcpkg/portfileprovider.cpp b/toolsrc/src/vcpkg/portfileprovider.cpp index edc527c24..c956dfe9b 100644 --- a/toolsrc/src/vcpkg/portfileprovider.cpp +++ b/toolsrc/src/vcpkg/portfileprovider.cpp @@ -17,29 +17,25 @@ using namespace Versions; namespace { - ExpectedS<fs::path> get_versions_json_path(const VcpkgPaths& paths, StringView port_name) - { - auto json_path = paths.root / fs::u8path("port_versions") / - fs::u8path(Strings::concat(port_name.substr(0, 1), "-")) / - fs::u8path(Strings::concat(port_name, ".json")); - if (paths.get_filesystem().exists(json_path)) - { - return std::move(json_path); - } - return {Strings::concat("Error: Versions database file does not exist: ", fs::u8string(json_path)), - expected_right_tag}; - } + using namespace vcpkg; - ExpectedS<fs::path> get_baseline_json_path(const VcpkgPaths& paths, StringView baseline_commit_sha) + struct OverlayRegistryEntry final : RegistryEntry { - auto baseline_path = paths.git_checkout_baseline(paths.get_filesystem(), baseline_commit_sha); - if (paths.get_filesystem().exists(baseline_path)) + OverlayRegistryEntry(fs::path&& p, VersionT&& v) : path(p), version(v) { } + + View<VersionT> get_port_versions() const override { return {&version, 1}; } + ExpectedS<fs::path> get_path_to_version(const VcpkgPaths&, const VersionT& v) const override { - return std::move(baseline_path); + if (v == version) + { + return path; + } + return Strings::format("Version %s not found; only %s is available.", v.to_string(), version.to_string()); } - return {Strings::concat("Error: Baseline database file does not exist: ", fs::u8string(baseline_path)), - expected_right_tag}; - } + + fs::path path; + VersionT version; + }; } namespace vcpkg::PortFileProvider @@ -95,9 +91,9 @@ namespace vcpkg::PortFileProvider } } - static Optional<SourceControlFileLocation> try_load_overlay_port(const Files::Filesystem& fs, - View<fs::path> overlay_ports, - const std::string& spec) + static std::unique_ptr<OverlayRegistryEntry> try_load_overlay_port(const Files::Filesystem& fs, + View<fs::path> overlay_ports, + const std::string& spec) { for (auto&& ports_dir : overlay_ports) { @@ -105,11 +101,12 @@ namespace vcpkg::PortFileProvider if (Paragraphs::is_port_directory(fs, ports_dir)) { auto maybe_scf = Paragraphs::try_load_port(fs, ports_dir); - if (auto scf = maybe_scf.get()) + if (auto scfp = maybe_scf.get()) { - if (scf->get()->core_paragraph->name == spec) + auto& scf = *scfp; + if (scf->core_paragraph->name == spec) { - return SourceControlFileLocation{std::move(*scf), ports_dir}; + return std::make_unique<OverlayRegistryEntry>(fs::path(ports_dir), scf->to_versiont()); } } else @@ -126,17 +123,18 @@ namespace vcpkg::PortFileProvider if (Paragraphs::is_port_directory(fs, ports_spec)) { auto found_scf = Paragraphs::try_load_port(fs, ports_spec); - if (auto scf = found_scf.get()) + if (auto scfp = found_scf.get()) { - if (scf->get()->core_paragraph->name == spec) + auto& scf = *scfp; + if (scf->core_paragraph->name == spec) { - return SourceControlFileLocation{std::move(*scf), std::move(ports_spec)}; + return std::make_unique<OverlayRegistryEntry>(std::move(ports_spec), scf->to_versiont()); } Checks::exit_with_message(VCPKG_LINE_INFO, "Error: Failed to load port from %s: names did not match: '%s' != '%s'", fs::u8string(ports_spec), spec, - scf->get()->core_paragraph->name); + scf->core_paragraph->name); } else { @@ -146,61 +144,38 @@ namespace vcpkg::PortFileProvider } } } - return nullopt; + return nullptr; } - static Optional<SourceControlFileLocation> try_load_registry_port(const VcpkgPaths& paths, const std::string& spec) + static std::pair<std::unique_ptr<RegistryEntry>, Optional<VersionT>> try_load_registry_port_and_baseline( + const VcpkgPaths& paths, const std::string& spec) { - const auto& fs = paths.get_filesystem(); if (auto registry = paths.get_configuration().registry_set.registry_for_port(spec)) { - auto baseline_version = registry->get_baseline_version(paths, spec); auto entry = registry->get_port_entry(paths, spec); - if (entry && baseline_version) + auto maybe_baseline = registry->get_baseline_version(paths, spec); + if (entry) { - auto port_directory = entry->get_port_directory(paths, *baseline_version.get()); - if (port_directory.empty()) + if (!maybe_baseline) { - Checks::exit_with_message(VCPKG_LINE_INFO, - "Error: registry is incorrect. Baseline version for port `%s` is `%s`, " - "but that version is not in the registry.\n", - spec, - baseline_version.get()->to_string()); - } - auto found_scf = Paragraphs::try_load_port(fs, port_directory); - if (auto scf = found_scf.get()) - { - if (scf->get()->core_paragraph->name == spec) + if (entry->get_port_versions().size() == 1) { - return SourceControlFileLocation{std::move(*scf), std::move(port_directory)}; + maybe_baseline = entry->get_port_versions()[0]; } - Checks::exit_with_message(VCPKG_LINE_INFO, - "Error: Failed to load port from %s: names did not match: '%s' != '%s'", - fs::u8string(port_directory), - spec, - scf->get()->core_paragraph->name); - } - else - { - print_error_message(found_scf.error()); - Checks::exit_with_message( - VCPKG_LINE_INFO, "Error: Failed to load port %s from %s", spec, fs::u8string(port_directory)); } + return {std::move(entry), std::move(maybe_baseline)}; } else { - Debug::print("Failed to find port `", - spec, - "` in registry:", - entry ? " entry found;" : " no entry found;", - baseline_version ? " baseline version found\n" : " no baseline version found\n"); + Debug::print("Failed to find port `", spec, "` in registry: no entry found.\n"); } } else { Debug::print("Failed to find registry for port: `", spec, "`.\n"); } - return nullopt; + + return {nullptr, nullopt}; } ExpectedS<const SourceControlFileLocation&> PathsPortFileProvider::get_control_file(const std::string& spec) const @@ -209,18 +184,49 @@ namespace vcpkg::PortFileProvider if (cache_it == cache.end()) { const auto& fs = paths.get_filesystem(); - auto maybe_port = try_load_overlay_port(fs, overlay_ports, spec); - if (!maybe_port) + + std::unique_ptr<RegistryEntry> port; + VersionT port_version; + + auto maybe_overlay_port = try_load_overlay_port(fs, overlay_ports, spec); + if (maybe_overlay_port) + { + port_version = maybe_overlay_port->version; + port = std::move(maybe_overlay_port); + } + else { - maybe_port = try_load_registry_port(paths, spec); + auto maybe_registry_port = try_load_registry_port_and_baseline(paths, spec); + port = std::move(maybe_registry_port.first); + if (auto version = maybe_registry_port.second.get()) + { + port_version = std::move(*version); + } + else if (port) + { + return std::string("No baseline version available."); + } } - if (auto p = maybe_port.get()) + + if (port) { - auto maybe_error = - p->source_control_file->check_against_feature_flags(p->source_location, paths.get_feature_flags()); - if (maybe_error) return std::move(*maybe_error.get()); + auto port_path = port->get_path_to_version(paths, port_version).value_or_exit(VCPKG_LINE_INFO); + auto maybe_scfl = Paragraphs::try_load_port(fs, port_path); + if (auto p = maybe_scfl.get()) + { + auto maybe_error = (*p)->check_against_feature_flags(port_path, paths.get_feature_flags()); + if (maybe_error) return std::move(*maybe_error.get()); - cache_it = cache.emplace(spec, std::move(*p)).first; + cache_it = + cache.emplace(spec, SourceControlFileLocation{std::move(*p), std::move(port_path)}).first; + } + else + { + return Strings::format("Error: when loading port `%s` from directory `%s`:\n%s\n", + spec, + fs::u8string(port_path), + maybe_scfl.error()->error); + } } } @@ -296,179 +302,86 @@ namespace vcpkg::PortFileProvider { struct BaselineProviderImpl : IBaselineProvider, Util::ResourceBase { - BaselineProviderImpl(const VcpkgPaths& paths) : paths(paths) { } - BaselineProviderImpl(const VcpkgPaths& paths, StringView baseline) - : paths(paths), m_baseline(baseline.to_string()) - { - } - - const Optional<std::map<std::string, VersionT, std::less<>>>& get_baseline_cache() const - { - return baseline_cache.get_lazy([&]() -> Optional<std::map<std::string, VersionT, std::less<>>> { - if (auto baseline = m_baseline.get()) - { - auto baseline_file = get_baseline_json_path(paths, *baseline).value_or_exit(VCPKG_LINE_INFO); - - auto maybe_baselines_map = - parse_baseline_file(paths.get_filesystem(), "default", baseline_file); - Checks::check_exit(VCPKG_LINE_INFO, - maybe_baselines_map.has_value(), - "Error: Couldn't parse baseline `%s` from `%s`", - "default", - fs::u8string(baseline_file)); - auto baselines_map = *maybe_baselines_map.get(); - return std::move(baselines_map); - } - else - { - // No baseline was provided, so use current repo - const auto& fs = paths.get_filesystem(); - auto baseline_file = paths.root / fs::u8path("port_versions") / fs::u8path("baseline.json"); - if (fs.exists(baseline_file)) - { - auto maybe_baselines_map = - parse_baseline_file(paths.get_filesystem(), "default", baseline_file); - Checks::check_exit(VCPKG_LINE_INFO, - maybe_baselines_map.has_value(), - "Error: Couldn't parse baseline `%s` from `%s`", - "default", - fs::u8string(baseline_file)); - auto baselines_map = *maybe_baselines_map.get(); - return std::move(baselines_map); - } - else - { - // No baseline file in current repo -- use current port versions. - m_portfile_provider = - std::make_unique<PathsPortFileProvider>(paths, std::vector<std::string>{}); - return nullopt; - } - } - }); - } + BaselineProviderImpl(const VcpkgPaths& paths_) : paths(paths_) { } virtual Optional<VersionT> get_baseline_version(StringView port_name) const override { - const auto& cache = get_baseline_cache(); - if (auto p_cache = cache.get()) + auto it = m_baseline_cache.find(port_name); + if (it != m_baseline_cache.end()) { - auto it = p_cache->find(port_name.to_string()); - if (it != p_cache->end()) - { - return it->second; - } - return nullopt; + return it->second; } else { - auto maybe_scfl = m_portfile_provider->get_control_file(port_name.to_string()); - if (auto p_scfl = maybe_scfl.get()) - { - auto cpgh = p_scfl->source_control_file->core_paragraph.get(); - return VersionT{cpgh->version, cpgh->port_version}; - } - else - { - return nullopt; - } + auto version = paths.get_configuration().registry_set.baseline_for_port(paths, port_name); + m_baseline_cache.emplace(port_name.to_string(), version); + return version; } } private: - const VcpkgPaths& paths; - const Optional<std::string> m_baseline; - Lazy<Optional<std::map<std::string, VersionT, std::less<>>>> baseline_cache; - mutable std::unique_ptr<PathsPortFileProvider> m_portfile_provider; + const VcpkgPaths& paths; // TODO: remove this data member + mutable std::map<std::string, Optional<VersionT>, std::less<>> m_baseline_cache; }; struct VersionedPortfileProviderImpl : IVersionedPortfileProvider, Util::ResourceBase { - VersionedPortfileProviderImpl(const VcpkgPaths& paths) : paths(paths) { } + VersionedPortfileProviderImpl(const VcpkgPaths& paths_) : paths(paths_) { } - virtual const std::vector<VersionSpec>& get_port_versions(StringView port_name) const override + virtual View<VersionT> get_port_versions(StringView port_name) const override { - auto cache_it = versions_cache.find(port_name.to_string()); - if (cache_it != versions_cache.end()) + auto entry_it = m_entry_cache.find(port_name.to_string()); + if (entry_it != m_entry_cache.end()) { - return cache_it->second; + return entry_it->second->get_port_versions(); } - auto maybe_versions_file_path = get_versions_json_path(get_paths(), port_name); - if (auto versions_file_path = maybe_versions_file_path.get()) + auto entry = try_load_registry_port_and_baseline(paths, port_name.to_string()); + if (!entry.first) { - auto maybe_version_entries = parse_versions_file(get_filesystem(), port_name, *versions_file_path); - Checks::check_exit(VCPKG_LINE_INFO, - maybe_version_entries.has_value(), - "Error: Couldn't parse versions from file: %s", - fs::u8string(*versions_file_path)); - auto version_entries = maybe_version_entries.value_or_exit(VCPKG_LINE_INFO); - - auto port = port_name.to_string(); - for (auto&& version_entry : version_entries) - { - VersionSpec spec(port, version_entry.version); - versions_cache[port].push_back(spec); - git_tree_cache.emplace(std::move(spec), std::move(version_entry.git_tree)); - } - return versions_cache.at(port); - } - else - { - // Fall back to current available version - auto maybe_port = try_load_registry_port(paths, port_name.to_string()); - if (auto p = maybe_port.get()) - { - auto maybe_error = p->source_control_file->check_against_feature_flags( - p->source_location, paths.get_feature_flags()); - - if (auto error = maybe_error.get()) - { - Checks::exit_with_message(VCPKG_LINE_INFO, "Error: %s", *error); - } - - VersionSpec vspec(port_name.to_string(), - VersionT(p->source_control_file->core_paragraph->version, - p->source_control_file->core_paragraph->port_version)); - control_cache.emplace(vspec, std::move(*p)); - return versions_cache.emplace(port_name.to_string(), std::vector<VersionSpec>{vspec}) - .first->second; - } Checks::exit_with_message( VCPKG_LINE_INFO, "Error: Could not find a definition for port %s", port_name); } + auto it = m_entry_cache.emplace(port_name.to_string(), std::move(entry.first)); + return it.first->second->get_port_versions(); } - virtual ExpectedS<const SourceControlFileLocation&> get_control_file( - const VersionSpec& version_spec) const override + ExpectedS<const SourceControlFileLocation&> get_control_file(const VersionSpec& version_spec) const override { - // Pre-populate versions cache. - get_port_versions(version_spec.port_name); - - auto cache_it = control_cache.find(version_spec); - if (cache_it != control_cache.end()) + auto cache_it = m_control_cache.find(version_spec); + if (cache_it != m_control_cache.end()) { return cache_it->second; } - auto git_tree_cache_it = git_tree_cache.find(version_spec); - if (git_tree_cache_it == git_tree_cache.end()) + auto entry_it = m_entry_cache.find(version_spec.port_name); + if (entry_it == m_entry_cache.end()) { - return Strings::concat("Error: No git object SHA for entry ", - version_spec.port_name, - " at version ", - version_spec.version, - "."); + auto reg_for_port = + paths.get_configuration().registry_set.registry_for_port(version_spec.port_name); + + if (!reg_for_port) + { + return Strings::format("Error: no registry set up for port %s", version_spec.port_name); + } + + auto entry = reg_for_port->get_port_entry(paths, version_spec.port_name); + entry_it = m_entry_cache.emplace(version_spec.port_name, std::move(entry)).first; } - const std::string git_tree = git_tree_cache_it->second; - auto port_directory = get_paths().git_checkout_port(get_filesystem(), version_spec.port_name, git_tree); + auto maybe_path = entry_it->second->get_path_to_version(paths, version_spec.version); + if (!maybe_path.has_value()) + { + return std::move(maybe_path).error(); + } + auto& port_directory = *maybe_path.get(); - auto maybe_control_file = Paragraphs::try_load_port(get_filesystem(), port_directory); + auto maybe_control_file = Paragraphs::try_load_port(paths.get_filesystem(), port_directory); if (auto scf = maybe_control_file.get()) { if (scf->get()->core_paragraph->name == version_spec.port_name) { - return control_cache + return m_control_cache .emplace(version_spec, SourceControlFileLocation{std::move(*scf), std::move(port_directory)}) .first->second; @@ -484,14 +397,10 @@ namespace vcpkg::PortFileProvider "Error: Failed to load port %s from %s", version_spec.port_name, fs::u8string(port_directory)); } - const VcpkgPaths& get_paths() const { return paths; } - Files::Filesystem& get_filesystem() const { return paths.get_filesystem(); } - private: - const VcpkgPaths& paths; - mutable std::map<std::string, std::vector<VersionSpec>> versions_cache; - mutable std::unordered_map<VersionSpec, std::string, VersionSpecHasher> git_tree_cache; - mutable std::unordered_map<VersionSpec, SourceControlFileLocation, VersionSpecHasher> control_cache; + const VcpkgPaths& paths; // TODO: remove this data member + mutable std::unordered_map<VersionSpec, SourceControlFileLocation, VersionSpecHasher> m_control_cache; + mutable std::map<std::string, std::unique_ptr<RegistryEntry>, std::less<>> m_entry_cache; }; } @@ -500,11 +409,6 @@ namespace vcpkg::PortFileProvider return std::make_unique<BaselineProviderImpl>(paths); } - std::unique_ptr<IBaselineProvider> make_baseline_provider(const vcpkg::VcpkgPaths& paths, StringView baseline) - { - return std::make_unique<BaselineProviderImpl>(paths, baseline); - } - std::unique_ptr<IVersionedPortfileProvider> make_versioned_portfile_provider(const vcpkg::VcpkgPaths& paths) { return std::make_unique<VersionedPortfileProviderImpl>(paths); diff --git a/toolsrc/src/vcpkg/registries.cpp b/toolsrc/src/vcpkg/registries.cpp index 51c8849c9..22ac26fd9 100644 --- a/toolsrc/src/vcpkg/registries.cpp +++ b/toolsrc/src/vcpkg/registries.cpp @@ -3,8 +3,9 @@ #include <vcpkg/base/jsonreader.h> #include <vcpkg/base/system.debug.h> -#include <vcpkg/configurationdeserializer.h> +#include <vcpkg/paragraphs.h> #include <vcpkg/registries.h> +#include <vcpkg/sourceparagraph.h> #include <vcpkg/vcpkgcmdarguments.h> #include <vcpkg/vcpkgpaths.h> #include <vcpkg/versiondeserializers.h> @@ -16,341 +17,695 @@ namespace { using namespace vcpkg; - struct BuiltinEntry final : RegistryEntry + using Baseline = std::map<std::string, VersionT, std::less<>>; + + static fs::path port_versions_dir = fs::u8path("port_versions"); + + // this class is an implementation detail of `BuiltinRegistryEntry`; + // when `BuiltinRegistryEntry` is using a port versions file for a port, + // it uses this as it's underlying type; + // when `BuiltinRegistryEntry` is using a port tree, it uses the scfl + struct GitRegistryEntry { - fs::path port_directory; + explicit GitRegistryEntry(std::string&& port_name) : port_name(port_name) { } - BuiltinEntry(fs::path&& p) : port_directory(std::move(p)) { } + std::string port_name; - fs::path get_port_directory(const VcpkgPaths&, const VersionT&) const override { return port_directory; } + // these two map port versions to git trees + // these shall have the same size, and git_trees[i] shall be the git tree for port_versions[i] + std::vector<VersionT> port_versions; + std::vector<std::string> git_trees; }; - struct BuiltinRegistry final : RegistryImpl + struct BuiltinRegistryEntry final : RegistryEntry { - std::unique_ptr<RegistryEntry> get_port_entry(const VcpkgPaths& paths, StringView port_name) const override + explicit BuiltinRegistryEntry(std::unique_ptr<GitRegistryEntry>&& entry) + : git_entry(std::move(entry)), scfl(nullptr) + { + } + explicit BuiltinRegistryEntry(std::unique_ptr<SourceControlFileLocation>&& scfl_) + : git_entry(nullptr), scfl(std::move(scfl_)), scfl_version(scfl->to_versiont()) { - auto p = paths.builtin_ports_directory() / fs::u8path(port_name); - if (paths.get_filesystem().exists(p)) + } + + View<VersionT> get_port_versions() const override + { + if (git_entry) { - return std::make_unique<BuiltinEntry>(std::move(p)); + return git_entry->port_versions; } else { - return nullptr; + return {&scfl_version, 1}; } } + ExpectedS<fs::path> get_path_to_version(const VcpkgPaths&, const VersionT& version) const override; - void get_all_port_names(std::vector<std::string>& names, const VcpkgPaths& paths) const override - { - const auto& fs = paths.get_filesystem(); - auto port_dirs = fs.get_files_non_recursive(paths.builtin_ports_directory()); - Util::sort(port_dirs); + // exactly one of these two shall be null - Util::erase_remove_if(port_dirs, - [&](auto&& port_dir_entry) { return port_dir_entry.filename() == ".DS_Store"; }); + // if we find a versions.json, this shall be non-null and BuiltinRegistryEntry uses git_entry's implementation + std::unique_ptr<GitRegistryEntry> git_entry; + // otherwise, if we don't find a versions.json, + // we fall back to just using the version in the ports directory, and this is the non-null one + std::unique_ptr<SourceControlFileLocation> scfl; + VersionT scfl_version; // this exists so that we can return a pointer to it + }; - std::transform(port_dirs.begin(), port_dirs.end(), std::back_inserter(names), [](const fs::path& p) { - return fs::u8string(p.filename()); - }); - } + struct FilesystemRegistryEntry final : RegistryEntry + { + explicit FilesystemRegistryEntry(std::string&& port_name) : port_name(port_name) { } - Optional<VersionT> get_baseline_version(const VcpkgPaths&, StringView) const override { return VersionT{}; } + View<VersionT> get_port_versions() const override { return port_versions; } + + ExpectedS<fs::path> get_path_to_version(const VcpkgPaths& paths, const VersionT& version) const override; + + std::string port_name; + + // these two map port versions to paths + // these shall have the same size, and paths[i] shall be the path for port_versions[i] + std::vector<VersionT> port_versions; + std::vector<fs::path> version_paths; }; - struct FilesystemEntry final : RegistryEntry + struct BuiltinRegistry final : RegistryImplementation { - std::map<VersionT, fs::path, VersionTMapLess> versions; + BuiltinRegistry(std::string&& baseline) : m_baseline_identifier(std::move(baseline)) { } - fs::path get_port_directory(const VcpkgPaths&, const VersionT& version) const override - { - auto it = versions.find(version); - if (it != versions.end()) - { - return it->second; - } - return {}; - } + std::unique_ptr<RegistryEntry> get_port_entry(const VcpkgPaths& paths, StringView port_name) const override; + + void get_all_port_names(std::vector<std::string>&, const VcpkgPaths&) const override; + + Optional<VersionT> get_baseline_version(const VcpkgPaths& paths, StringView port_name) const override; + + ~BuiltinRegistry() = default; + + std::string m_baseline_identifier; + DelayedInit<Baseline> m_baseline; }; - struct FilesystemVersionEntryDeserializer final : Json::IDeserializer<std::pair<VersionT, fs::path>> + struct FilesystemRegistry final : RegistryImplementation { - StringView type_name() const override { return "a version entry object"; } - View<StringView> valid_fields() const override + FilesystemRegistry(fs::path&& path, std::string&& baseline) + : m_path(std::move(path)), m_baseline_identifier(baseline) { - static const StringView t[] = {"version-string", "port-version", "registry-path"}; - return t; } - Optional<std::pair<VersionT, fs::path>> visit_object(Json::Reader& r, const Json::Object& obj) override - { - fs::path registry_path; + std::unique_ptr<RegistryEntry> get_port_entry(const VcpkgPaths&, StringView) const override; - auto version = get_versiont_deserializer_instance().visit_object(r, obj); + void get_all_port_names(std::vector<std::string>&, const VcpkgPaths&) const override; - r.required_object_field( - "version entry", obj, "registry-path", registry_path, Json::PathDeserializer::instance); - // registry_path should look like `/blah/foo` - if (registry_path.has_root_name() || !registry_path.has_root_directory()) - { - r.add_generic_error(type_name(), "must be an absolute path without a drive name"); - registry_path.clear(); - } + Optional<VersionT> get_baseline_version(const VcpkgPaths&, StringView) const override; + + private: + fs::path m_path; + std::string m_baseline_identifier; + DelayedInit<Baseline> m_baseline; + }; - return std::pair<VersionT, fs::path>{std::move(version).value_or(VersionT{}), std::move(registry_path)}; + ExpectedS<fs::path> get_git_baseline_json_path(const VcpkgPaths& paths, StringView baseline_commit_sha) + { + auto baseline_path = paths.git_checkout_baseline(paths.get_filesystem(), baseline_commit_sha); + if (paths.get_filesystem().exists(baseline_path)) + { + return std::move(baseline_path); } + return {Strings::concat("Error: Baseline database file does not exist: ", fs::u8string(baseline_path)), + expected_right_tag}; + } + + struct VersionDbEntry + { + VersionT version; + Versions::Scheme scheme = Versions::Scheme::String; - static FilesystemVersionEntryDeserializer instance; + // only one of these may be non-empty + std::string git_tree; + fs::path path; }; - FilesystemVersionEntryDeserializer FilesystemVersionEntryDeserializer::instance; - struct FilesystemEntryDeserializer final : Json::IDeserializer<FilesystemEntry> + // VersionDbType::Git => VersionDbEntry.git_tree is filled + // VersionDbType::Filesystem => VersionDbEntry.path is filled + enum class VersionDbType { - StringView type_name() const override { return "a registry entry object"; } + Git, + Filesystem, + }; - Optional<FilesystemEntry> visit_array(Json::Reader& r, const Json::Array& arr) override + fs::path relative_path_to_versions(StringView port_name); + ExpectedS<std::vector<VersionDbEntry>> load_versions_file(Files::Filesystem& fs, + VersionDbType vdb, + const fs::path& port_versions, + StringView port_name, + const fs::path& registry_root = {}); + + // returns nullopt if the baseline is valid, but doesn't contain the specified baseline, + // or (equivalently) if the baseline does not exist. + ExpectedS<Optional<Baseline>> parse_baseline_versions(StringView contents, StringView baseline); + ExpectedS<Optional<Baseline>> load_baseline_versions(const VcpkgPaths& paths, + const fs::path& path_to_baseline, + StringView identifier = {}); + + void load_all_port_names_from_port_versions(std::vector<std::string>& out, + const VcpkgPaths& paths, + const fs::path& port_versions_path) + { + for (auto super_directory : fs::directory_iterator(port_versions_path)) { - FilesystemEntry res; + if (!fs::is_directory(paths.get_filesystem().status(VCPKG_LINE_INFO, super_directory))) continue; - std::pair<VersionT, fs::path> buffer; - for (std::size_t idx = 0; idx < arr.size(); ++idx) + for (auto file : fs::directory_iterator(super_directory)) { - r.visit_at_index( - arr[idx], static_cast<int64_t>(idx), buffer, FilesystemVersionEntryDeserializer::instance); + auto filename = fs::u8string(file.path().filename()); + if (!Strings::ends_with(filename, ".json")) continue; - auto it = res.versions.lower_bound(buffer.first); - if (it == res.versions.end() || it->first != buffer.first) - { - buffer.second = registry_root / fs::lexically_normal(buffer.second).relative_path(); - res.versions.insert(it, std::move(buffer)); - } - else if (buffer.first != VersionT{}) + auto port_name = filename.substr(0, filename.size() - 5); + if (!Json::PackageNameDeserializer::is_package_name(port_name)) { - r.add_generic_error( - type_name(), "Gave multiple definitions for version: ", buffer.first.to_string()); + Checks::exit_with_message( + VCPKG_LINE_INFO, "Error: found invalid port version file name: `%s`.", fs::u8string(file)); } + out.push_back(std::move(port_name)); } - - return res; } + } - FilesystemEntryDeserializer(const fs::path& p) : registry_root(p) { } - - const fs::path& registry_root; - }; + // { RegistryImplementation - struct FilesystemRegistry final : RegistryImpl + // { BuiltinRegistry::RegistryImplementation + std::unique_ptr<RegistryEntry> BuiltinRegistry::get_port_entry(const VcpkgPaths& paths, StringView port_name) const { - std::unique_ptr<RegistryEntry> get_port_entry(const VcpkgPaths& paths, StringView port_name) const override + auto versions_path = paths.builtin_port_versions / relative_path_to_versions(port_name); + if (paths.get_feature_flags().registries && paths.get_filesystem().exists(versions_path)) { - const auto& fs = paths.get_filesystem(); - auto entry_path = this->path_to_port_entry(paths, port_name); - if (!fs.exists(entry_path)) + auto maybe_version_entries = + load_versions_file(paths.get_filesystem(), VersionDbType::Git, paths.builtin_port_versions, port_name); + Checks::check_exit( + VCPKG_LINE_INFO, maybe_version_entries.has_value(), "Error: %s", maybe_version_entries.error()); + auto version_entries = std::move(maybe_version_entries).value_or_exit(VCPKG_LINE_INFO); + + auto res = + std::make_unique<BuiltinRegistryEntry>(std::make_unique<GitRegistryEntry>(port_name.to_string())); + auto gre = res->git_entry.get(); + for (auto&& version_entry : version_entries) { - Debug::print( - "Failed to find entry for port `", port_name, "` in file: ", fs::u8string(entry_path), "\n"); - return nullptr; + gre->port_versions.push_back(version_entry.version); + gre->git_trees.push_back(version_entry.git_tree); } - std::error_code ec; - auto json_document = Json::parse_file(fs, entry_path, ec); + return res; + } - if (auto p = json_document.get()) + // Fall back to current available version + auto port_directory = paths.builtin_ports_directory() / fs::u8path(port_name); + if (paths.get_filesystem().exists(port_directory)) + { + auto found_scf = Paragraphs::try_load_port(paths.get_filesystem(), port_directory); + if (auto scfp = found_scf.get()) { - Json::Reader r; - auto real_path = paths.config_root_dir / path; - FilesystemEntryDeserializer deserializer{real_path}; - auto entry = r.visit(p->first, deserializer); - auto pentry = entry.get(); - if (pentry && r.errors().empty()) + auto& scf = *scfp; + auto maybe_error = scf->check_against_feature_flags(port_directory, paths.get_feature_flags()); + if (maybe_error) { - return std::make_unique<FilesystemEntry>(std::move(*pentry)); + Checks::exit_with_message(VCPKG_LINE_INFO, "Parsing manifest failed: %s", *maybe_error.get()); } - else + + if (scf->core_paragraph->name == port_name) { - vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, - "Failed to parse the port entry for port `%s` at `%s`.\n%s", - port_name, - fs::u8string(entry_path), - Strings::join("\n", r.errors())); + return std::make_unique<BuiltinRegistryEntry>( + std::make_unique<SourceControlFileLocation>(std::move(scf), std::move(port_directory))); } + Checks::exit_with_message(VCPKG_LINE_INFO, + "Error: Failed to load port from %s: names did not match: '%s' != '%s'", + fs::u8string(port_directory), + port_name, + scf->core_paragraph->name); } - else - { - Debug::print("Failed to parse json document: ", json_document.error()->format(), "\n"); - } - - return nullptr; } - void get_all_port_names(std::vector<std::string>& port_names, const VcpkgPaths& paths) const override + return nullptr; + } + + Baseline parse_builtin_baseline(const VcpkgPaths& paths, StringView baseline_identifier) + { + auto path_to_baseline = paths.builtin_port_versions / fs::u8path("baseline.json"); + auto res_baseline = load_baseline_versions(paths, path_to_baseline, baseline_identifier); + + if (!res_baseline.has_value()) { - std::error_code ec; - for (const auto& super_dir : fs::directory_iterator(path)) - { - if (!fs::is_directory(paths.get_filesystem().status(super_dir, ec))) - { - continue; - } + Checks::exit_with_message(VCPKG_LINE_INFO, res_baseline.error()); + } + auto opt_baseline = res_baseline.get(); + if (auto p = opt_baseline->get()) + { + return std::move(*p); + } - auto super_dir_filename = fs::u8string(super_dir.path().filename()); - if (!Strings::ends_with(super_dir_filename, "-")) - { - continue; - } + if (baseline_identifier.size() == 0) + { + return {}; + } - super_dir_filename.pop_back(); - for (const auto& database_entry : fs::directory_iterator(super_dir)) - { - auto database_entry_filename = database_entry.path().filename(); - auto database_entry_filename_str = fs::u8string(database_entry_filename); + if (baseline_identifier == "default") + { + Checks::exit_with_message(VCPKG_LINE_INFO, + "Couldn't find explicitly specified baseline `\"default\"` in the baseline file.", + baseline_identifier); + } - if (!Strings::starts_with(database_entry_filename_str, super_dir_filename) || - !Strings::ends_with(database_entry_filename_str, ".json")) - { - Debug::print("Unexpected file in database (this is not an error): ", - fs::u8string(database_entry.path()), - "\n"); - continue; - } + // attempt to check out the baseline: + auto maybe_path = get_git_baseline_json_path(paths, baseline_identifier); + if (!maybe_path.has_value()) + { + Checks::exit_with_message(VCPKG_LINE_INFO, + "Couldn't find explicitly specified baseline `\"%s\"` in the baseline file, " + "and there was no baseline at that commit or the commit didn't exist.\n%s", + baseline_identifier, + maybe_path.error()); + } - port_names.push_back(fs::u8string(database_entry_filename.replace_extension())); - } - } + res_baseline = load_baseline_versions(paths, *maybe_path.get()); + if (!res_baseline.has_value()) + { + Checks::exit_with_message(VCPKG_LINE_INFO, res_baseline.error()); + } + opt_baseline = res_baseline.get(); + if (auto p = opt_baseline->get()) + { + return std::move(*p); } - Optional<VersionT> get_baseline_version(const VcpkgPaths& paths, StringView port_name) const override + Checks::exit_with_message(VCPKG_LINE_INFO, + "Couldn't find explicitly specified baseline `\"%s\"` in the baseline " + "file, and the `\"default\"` baseline does not exist at that commit.", + baseline_identifier); + } + Optional<VersionT> BuiltinRegistry::get_baseline_version(const VcpkgPaths& paths, StringView port_name) const + { + if (paths.get_feature_flags().registries) { - if (!paths.get_feature_flags().versions) + const auto& baseline = m_baseline.get( + [this, &paths]() -> Baseline { return parse_builtin_baseline(paths, m_baseline_identifier); }); + + auto it = baseline.find(port_name); + if (it != baseline.end()) { - Checks::check_exit(VCPKG_LINE_INFO, - "This invocation failed because the `versions` feature flag is not enabled."); + return it->second; } + } - const auto& baseline_cache = baseline.get([this, &paths] { return load_baseline_versions(paths); }); - auto it = baseline_cache.find(port_name); - if (it != baseline_cache.end()) + // fall back to using the ports directory version + auto maybe_scf = + Paragraphs::try_load_port(paths.get_filesystem(), paths.builtin_ports_directory() / fs::u8path(port_name)); + if (auto pscf = maybe_scf.get()) + { + auto& scf = *pscf; + return scf->to_versiont(); + } + Debug::print("Failed to load port `", port_name, "` from the ports tree: ", maybe_scf.error()->error, "\n"); + return nullopt; + } + + void BuiltinRegistry::get_all_port_names(std::vector<std::string>& out, const VcpkgPaths& paths) const + { + if (paths.get_feature_flags().registries && paths.get_filesystem().exists(paths.builtin_port_versions)) + { + load_all_port_names_from_port_versions(out, paths, paths.builtin_port_versions); + } + + for (auto port_directory : fs::directory_iterator(paths.builtin_ports_directory())) + { + if (!fs::is_directory(paths.get_filesystem().status(VCPKG_LINE_INFO, port_directory))) continue; + auto filename = fs::u8string(port_directory.path().filename()); + if (filename == ".DS_Store") continue; + out.push_back(filename); + } + } + // } BuiltinRegistry::RegistryImplementation + + // { FilesystemRegistry::RegistryImplementation + Baseline parse_filesystem_baseline(const VcpkgPaths& paths, const fs::path& root, StringView baseline_identifier) + { + auto path_to_baseline = root / port_versions_dir / fs::u8path("baseline.json"); + auto res_baseline = load_baseline_versions(paths, path_to_baseline, baseline_identifier); + if (auto opt_baseline = res_baseline.get()) + { + if (auto p = opt_baseline->get()) { - return it->second; + return std::move(*p); } - else + + if (baseline_identifier.size() == 0) { - return nullopt; + return {}; } + + Checks::exit_with_message( + VCPKG_LINE_INFO, + "Error: could not find explicitly specified baseline `\"%s\"` in baseline file `%s`.", + baseline_identifier, + fs::u8string(path_to_baseline)); } - FilesystemRegistry(fs::path&& path_) : path(path_) { } + Checks::exit_with_message(VCPKG_LINE_INFO, res_baseline.error()); + } + Optional<VersionT> FilesystemRegistry::get_baseline_version(const VcpkgPaths& paths, StringView port_name) const + { + const auto& baseline = m_baseline.get( + [this, &paths]() -> Baseline { return parse_filesystem_baseline(paths, m_path, m_baseline_identifier); }); - private: - fs::path path_to_registry_database(const VcpkgPaths& paths) const + auto it = baseline.find(port_name); + if (it != baseline.end()) { - fs::path path_to_db = paths.config_root_dir / path; - path_to_db /= fs::u8path( - {'\xF0', '\x9F', '\x98', '\x87'}); // utf-8 for https://emojipedia.org/smiling-face-with-halo/ - return path_to_db; + return it->second; } - - fs::path path_to_port_entry(const VcpkgPaths& paths, StringView port_name) const + else { - Checks::check_exit(VCPKG_LINE_INFO, port_name.size() != 0); - fs::path path_to_entry = path_to_registry_database(paths); - path_to_entry /= fs::u8path({port_name.byte_at_index(0), '-'}); - path_to_entry /= fs::u8path(port_name); - path_to_entry += fs::u8path(".json"); - - return path_to_entry; + return nullopt; } + } - std::map<std::string, VersionT, std::less<>> load_baseline_versions(const VcpkgPaths& paths) const + std::unique_ptr<RegistryEntry> FilesystemRegistry::get_port_entry(const VcpkgPaths& paths, + StringView port_name) const + { + auto maybe_version_entries = load_versions_file( + paths.get_filesystem(), VersionDbType::Filesystem, m_path / port_versions_dir, port_name, m_path); + Checks::check_exit( + VCPKG_LINE_INFO, maybe_version_entries.has_value(), "Error: %s", maybe_version_entries.error()); + auto version_entries = std::move(maybe_version_entries).value_or_exit(VCPKG_LINE_INFO); + + auto res = std::make_unique<FilesystemRegistryEntry>(port_name.to_string()); + for (auto&& version_entry : version_entries) { - auto baseline_file = path_to_registry_database(paths) / fs::u8path("baseline.json"); + res->port_versions.push_back(std::move(version_entry.version)); + res->version_paths.push_back(std::move(version_entry.path)); + } + return res; + } + + void FilesystemRegistry::get_all_port_names(std::vector<std::string>& out, const VcpkgPaths& paths) const + { + load_all_port_names_from_port_versions(out, paths, m_path / port_versions_dir); + } + // } FilesystemRegistry::RegistryImplementation + + // } RegistryImplementation + + // { RegistryEntry - auto value = Json::parse_file(VCPKG_LINE_INFO, paths.get_filesystem(), baseline_file); - if (!value.first.is_object()) + // { BuiltinRegistryEntry::RegistryEntry + ExpectedS<fs::path> BuiltinRegistryEntry::get_path_to_version(const VcpkgPaths& paths, + const VersionT& version) const + { + if (git_entry) + { + auto it = std::find(git_entry->port_versions.begin(), git_entry->port_versions.end(), version); + if (it == git_entry->port_versions.end()) { - Checks::exit_with_message(VCPKG_LINE_INFO, "Error: `baseline.json` does not have a top-level object."); + return Strings::concat( + "Error: No version entry for ", git_entry->port_name, " at version ", version, "."); } - auto maybe_baseline_versions = parse_baseline_file(paths.get_filesystem(), "default", baseline_file); - if (auto baseline_versions = maybe_baseline_versions.get()) + const auto& git_tree = git_entry->git_trees[it - git_entry->port_versions.begin()]; + return paths.git_checkout_port(paths.get_filesystem(), git_entry->port_name, git_tree); + } + + if (scfl_version == version) + { + return scfl->source_location; + } + + auto& name = scfl->source_control_file->core_paragraph->name; + return Strings::format( + "Error: no version entry for %s at version %s.\n" + "We are currently using the version in the ports tree, since no %s.json was found in port_versions.", + name, + scfl->to_versiont().to_string(), + name); + } + // } BuiltinRegistryEntry::RegistryEntry + + // { FilesystemRegistryEntry::RegistryEntry + ExpectedS<fs::path> FilesystemRegistryEntry::get_path_to_version(const VcpkgPaths&, const VersionT& version) const + { + auto it = std::find(port_versions.begin(), port_versions.end(), version); + if (it == port_versions.end()) + { + return Strings::concat("Error: No version entry for ", port_name, " at version ", version, "."); + } + return version_paths[it - port_versions.begin()]; + } + // } FilesystemRegistryEntry::RegistryEntry + + // } RegistryEntry +} + +// deserializers +namespace +{ + using namespace vcpkg; + + struct BaselineDeserializer final : Json::IDeserializer<std::map<std::string, VersionT, std::less<>>> + { + StringView type_name() const override { return "a baseline object"; } + + Optional<type> visit_object(Json::Reader& r, const Json::Object& obj) override + { + std::map<std::string, VersionT, std::less<>> result; + + for (auto pr : obj) { - return std::move(*baseline_versions); + const auto& version_value = pr.second; + VersionT version; + r.visit_in_key(version_value, pr.first, version, get_versiont_deserializer_instance()); + + result.emplace(pr.first.to_string(), std::move(version)); } - else + + return std::move(result); + } + + static BaselineDeserializer instance; + }; + BaselineDeserializer BaselineDeserializer::instance; + + struct VersionDbEntryDeserializer final : Json::IDeserializer<VersionDbEntry> + { + static constexpr StringLiteral GIT_TREE = "git-tree"; + static constexpr StringLiteral PATH = "path"; + + StringView type_name() const override { return "a version database entry"; } + View<StringView> valid_fields() const override + { + static const StringView u_git[] = {GIT_TREE}; + static const StringView u_path[] = {PATH}; + static const auto t_git = vcpkg::Util::Vectors::concat<StringView>(schemed_deserializer_fields(), u_git); + static const auto t_path = vcpkg::Util::Vectors::concat<StringView>(schemed_deserializer_fields(), u_path); + + return type == VersionDbType::Git ? t_git : t_path; + } + + Optional<VersionDbEntry> visit_object(Json::Reader& r, const Json::Object& obj) override + { + VersionDbEntry ret; + + auto schemed_version = visit_required_schemed_deserializer(type_name(), r, obj); + ret.scheme = schemed_version.scheme; + ret.version = std::move(schemed_version.versiont); + + static Json::StringDeserializer git_tree_deserializer("a git object SHA"); + static Json::StringDeserializer path_deserializer("a registry path"); + + switch (type) { - Checks::exit_with_message(VCPKG_LINE_INFO, - "Error: failed to parse `%s`:\n%s", - fs::u8string(baseline_file), - maybe_baseline_versions.error()); + case VersionDbType::Git: + { + r.required_object_field(type_name(), obj, GIT_TREE, ret.git_tree, git_tree_deserializer); + break; + } + case VersionDbType::Filesystem: + { + std::string path_res; + r.required_object_field(type_name(), obj, PATH, path_res, path_deserializer); + fs::path p = fs::u8path(path_res); + if (p.is_absolute()) + { + r.add_generic_error("a registry path", + "A registry path may not be absolute, and must start with a `$` to mean " + "the registry root; e.g., `$/foo/bar`."); + return ret; + } + else if (p.empty()) + { + r.add_generic_error("a registry path", "A registry path must not be empty."); + return ret; + } + + auto it = p.begin(); + if (*it != "$") + { + r.add_generic_error( + "a registry path", + "A registry path must start with `$` to mean the registry root; e.g., `$/foo/bar`"); + } + + ret.path = registry_root; + ++it; + std::for_each(it, p.end(), [&r, &ret](const fs::path& p) { + if (p == "..") + { + r.add_generic_error("a registry path", "A registry path must not contain `..`."); + } + else + { + ret.path /= p; + } + }); + + break; + } } + + return ret; } - fs::path path; - DelayedInit<std::map<std::string, VersionT, std::less<>>> baseline; + VersionDbEntryDeserializer(VersionDbType type, const fs::path& root) : type(type), registry_root(root) { } + + VersionDbType type; + fs::path registry_root; }; -} -namespace vcpkg -{ - std::unique_ptr<RegistryImpl> Registry::builtin_registry() { return std::make_unique<BuiltinRegistry>(); } + struct VersionDbEntryArrayDeserializer final : Json::IDeserializer<std::vector<VersionDbEntry>> + { + virtual StringView type_name() const override { return "an array of versions"; } - Registry::Registry(std::vector<std::string>&& packages, std::unique_ptr<RegistryImpl>&& impl) - : packages_(std::move(packages)), implementation_(std::move(impl)) + virtual Optional<std::vector<VersionDbEntry>> visit_array(Json::Reader& r, const Json::Array& arr) override + { + return r.array_elements(arr, underlying); + } + + VersionDbEntryArrayDeserializer(VersionDbType type, const fs::path& root) : underlying{type, root} { } + + VersionDbEntryDeserializer underlying; + }; + + struct RegistryImplDeserializer : Json::IDeserializer<std::unique_ptr<RegistryImplementation>> { - Checks::check_exit(VCPKG_LINE_INFO, implementation_ != nullptr); - } + constexpr static StringLiteral KIND = "kind"; + constexpr static StringLiteral BASELINE = "baseline"; + constexpr static StringLiteral PATH = "path"; + + constexpr static StringLiteral KIND_BUILTIN = "builtin"; + constexpr static StringLiteral KIND_FILESYSTEM = "filesystem"; + + virtual StringView type_name() const override { return "a registry"; } + virtual View<StringView> valid_fields() const override; - RegistryImplDeserializer RegistryImplDeserializer::instance; + virtual Optional<std::unique_ptr<RegistryImplementation>> visit_null(Json::Reader&) override; + virtual Optional<std::unique_ptr<RegistryImplementation>> visit_object(Json::Reader&, + const Json::Object&) override; - StringView RegistryImplDeserializer::type_name() const { return "a registry"; } + RegistryImplDeserializer(const fs::path& configuration_directory) + : config_directory(configuration_directory) { } + fs::path config_directory; + }; constexpr StringLiteral RegistryImplDeserializer::KIND; + constexpr StringLiteral RegistryImplDeserializer::BASELINE; constexpr StringLiteral RegistryImplDeserializer::PATH; constexpr StringLiteral RegistryImplDeserializer::KIND_BUILTIN; constexpr StringLiteral RegistryImplDeserializer::KIND_FILESYSTEM; + struct RegistryDeserializer final : Json::IDeserializer<Registry> + { + constexpr static StringLiteral PACKAGES = "packages"; + + virtual StringView type_name() const override { return "a registry"; } + virtual View<StringView> valid_fields() const override; + + virtual Optional<Registry> visit_object(Json::Reader&, const Json::Object&) override; + + explicit RegistryDeserializer(const fs::path& configuration_directory) : impl_des(configuration_directory) { } + + RegistryImplDeserializer impl_des; + }; + constexpr StringLiteral RegistryDeserializer::PACKAGES; + View<StringView> RegistryImplDeserializer::valid_fields() const { - static const StringView t[] = {KIND, PATH}; + static const StringView t[] = {KIND, BASELINE, PATH}; + return t; + } + View<StringView> valid_builtin_fields() + { + static const StringView t[] = { + RegistryImplDeserializer::KIND, + RegistryImplDeserializer::BASELINE, + RegistryDeserializer::PACKAGES, + }; + return t; + } + View<StringView> valid_filesystem_fields() + { + static const StringView t[] = { + RegistryImplDeserializer::KIND, + RegistryImplDeserializer::BASELINE, + RegistryImplDeserializer::PATH, + RegistryDeserializer::PACKAGES, + }; return t; } - Optional<std::unique_ptr<RegistryImpl>> RegistryImplDeserializer::visit_null(Json::Reader&) { return nullptr; } + Optional<std::unique_ptr<RegistryImplementation>> RegistryImplDeserializer::visit_null(Json::Reader&) + { + return nullptr; + } - Optional<std::unique_ptr<RegistryImpl>> RegistryImplDeserializer::visit_object(Json::Reader& r, - const Json::Object& obj) + Optional<std::unique_ptr<RegistryImplementation>> RegistryImplDeserializer::visit_object(Json::Reader& r, + const Json::Object& obj) { static Json::StringDeserializer kind_deserializer{"a registry implementation kind"}; + static Json::StringDeserializer baseline_deserializer{"a baseline"}; std::string kind; + std::string baseline; + r.required_object_field(type_name(), obj, KIND, kind, kind_deserializer); + r.optional_object_field(obj, BASELINE, baseline, baseline_deserializer); + + std::unique_ptr<RegistryImplementation> res; if (kind == KIND_BUILTIN) { - if (obj.contains(PATH)) - { - r.add_extra_field_error("a builtin registry", PATH); - } - return static_cast<std::unique_ptr<RegistryImpl>>(std::make_unique<BuiltinRegistry>()); + r.check_for_unexpected_fields(obj, valid_builtin_fields(), "a builtin registry"); + res = std::make_unique<BuiltinRegistry>(std::move(baseline)); } else if (kind == KIND_FILESYSTEM) { + r.check_for_unexpected_fields(obj, valid_filesystem_fields(), "a filesystem registry"); + fs::path path; r.required_object_field("a filesystem registry", obj, PATH, path, Json::PathDeserializer::instance); - return static_cast<std::unique_ptr<RegistryImpl>>(std::make_unique<FilesystemRegistry>(std::move(path))); + res = std::make_unique<FilesystemRegistry>(config_directory / path, std::move(baseline)); } else { + StringLiteral valid_kinds[] = {KIND_BUILTIN, KIND_FILESYSTEM}; + r.add_generic_error(type_name(), + "Field \"kind\" did not have an expected value (expected one of: \"", + Strings::join("\", \"", valid_kinds), + "\", found \"", + kind, + "\")."); return nullopt; } - } - StringView RegistryDeserializer::type_name() const { return "a registry"; } - - constexpr StringLiteral RegistryDeserializer::PACKAGES; + return std::move(res); + } View<StringView> RegistryDeserializer::valid_fields() const { static const StringView t[] = { RegistryImplDeserializer::KIND, + RegistryImplDeserializer::BASELINE, RegistryImplDeserializer::PATH, PACKAGES, }; @@ -359,7 +714,7 @@ namespace vcpkg Optional<Registry> RegistryDeserializer::visit_object(Json::Reader& r, const Json::Object& obj) { - auto impl = RegistryImplDeserializer::instance.visit_object(r, obj); + auto impl = impl_des.visit_object(r, obj); if (!impl.has_value()) { @@ -375,9 +730,159 @@ namespace vcpkg return Registry{std::move(packages), std::move(impl).value_or_exit(VCPKG_LINE_INFO)}; } + fs::path relative_path_to_versions(StringView port_name) + { + auto port_filename = fs::u8path(port_name.to_string() + ".json"); + return fs::u8path({port_name.byte_at_index(0), '-'}) / port_filename; + } + + ExpectedS<std::vector<VersionDbEntry>> load_versions_file(Files::Filesystem& fs, + VersionDbType type, + const fs::path& port_versions, + StringView port_name, + const fs::path& registry_root) + { + Checks::check_exit(VCPKG_LINE_INFO, + !(type == VersionDbType::Filesystem && registry_root.empty()), + "Bug in vcpkg; type should never = Filesystem when registry_root is empty."); + + auto versions_file_path = port_versions / relative_path_to_versions(port_name); + + if (!fs.exists(versions_file_path)) + { + return Strings::format("Couldn't find the versions database file: %s", fs::u8string(versions_file_path)); + } + + auto maybe_contents = fs.read_contents(versions_file_path); + if (!maybe_contents.has_value()) + { + return Strings::format("Failed to load the versions database file %s: %s", + fs::u8string(versions_file_path), + maybe_contents.error().message()); + } + + auto maybe_versions_json = Json::parse(*maybe_contents.get()); + if (!maybe_versions_json.has_value()) + { + return Strings::format( + "Error: failed to parse versions file for `%s`: %s", port_name, maybe_versions_json.error()->format()); + } + if (!maybe_versions_json.get()->first.is_object()) + { + return Strings::format("Error: versions file for `%s` does not have a top level object.", port_name); + } + + const auto& versions_object = maybe_versions_json.get()->first.object(); + auto maybe_versions_array = versions_object.get("versions"); + if (!maybe_versions_array || !maybe_versions_array->is_array()) + { + return Strings::format("Error: versions file for `%s` does not contain a versions array.", port_name); + } + + std::vector<VersionDbEntry> db_entries; + VersionDbEntryArrayDeserializer deserializer{type, registry_root}; + // Avoid warning treated as error. + if (maybe_versions_array != nullptr) + { + Json::Reader r; + r.visit_in_key(*maybe_versions_array, "versions", db_entries, deserializer); + if (!r.errors().empty()) + { + return Strings::format( + "Error: failed to parse versions file for `%s`:\n%s", port_name, Strings::join("\n", r.errors())); + } + } + return db_entries; + } + + ExpectedS<Optional<Baseline>> parse_baseline_versions(StringView contents, StringView baseline) + { + auto maybe_value = Json::parse(contents); + if (!maybe_value.has_value()) + { + return Strings::format("Error: failed to parse baseline file: %s", maybe_value.error()->format()); + } + + auto& value = *maybe_value.get(); + + if (!value.first.is_object()) + { + return std::string("Error: baseline does not have a top-level object."); + } + + auto real_baseline = baseline.size() == 0 ? "default" : baseline; + + const auto& obj = value.first.object(); + auto baseline_value = obj.get(real_baseline); + if (!baseline_value) + { + return {nullopt, expected_left_tag}; + } + + Json::Reader r; + std::map<std::string, VersionT, std::less<>> result; + r.visit_in_key(*baseline_value, real_baseline, result, BaselineDeserializer::instance); + if (r.errors().empty()) + { + return {std::move(result), expected_left_tag}; + } + else + { + Checks::exit_with_message( + VCPKG_LINE_INFO, "Error: failed to parse baseline:\n%s", Strings::join("\n", r.errors())); + } + } + + ExpectedS<Optional<Baseline>> load_baseline_versions(const VcpkgPaths& paths, + const fs::path& path_to_baseline, + StringView baseline) + { + auto maybe_contents = paths.get_filesystem().read_contents(path_to_baseline); + if (auto contents = maybe_contents.get()) + { + return parse_baseline_versions(*contents, baseline); + } + else if (maybe_contents.error() == std::errc::no_such_file_or_directory) + { + return {nullopt, expected_left_tag}; + } + else + { + return Strings::format("Error: failed to read file `%s`: %s", + fs::u8string(path_to_baseline), + maybe_contents.error().message()); + } + } +} + +namespace vcpkg +{ + std::unique_ptr<Json::IDeserializer<std::unique_ptr<RegistryImplementation>>> + get_registry_implementation_deserializer(const fs::path& configuration_directory) + { + return std::make_unique<RegistryImplDeserializer>(configuration_directory); + } + std::unique_ptr<Json::IDeserializer<std::vector<Registry>>> get_registry_array_deserializer( + const fs::path& configuration_directory) + { + return std::make_unique<Json::ArrayDeserializer<RegistryDeserializer>>( + "an array of registries", RegistryDeserializer(configuration_directory)); + } + + std::unique_ptr<RegistryImplementation> Registry::builtin_registry(std::string&& baseline) + { + return std::make_unique<BuiltinRegistry>(std::move(baseline)); + } + + Registry::Registry(std::vector<std::string>&& packages, std::unique_ptr<RegistryImplementation>&& impl) + : packages_(std::move(packages)), implementation_(std::move(impl)) + { + Checks::check_exit(VCPKG_LINE_INFO, implementation_ != nullptr); + } + RegistrySet::RegistrySet() : default_registry_(Registry::builtin_registry()), registries_() { } - const RegistryImpl* RegistrySet::registry_for_port(StringView name) const + const RegistryImplementation* RegistrySet::registry_for_port(StringView name) const { for (const auto& registry : registries()) { @@ -390,8 +895,63 @@ namespace vcpkg return default_registry(); } + Optional<VersionT> RegistrySet::baseline_for_port(const VcpkgPaths& paths, StringView port_name) const + { + auto impl = registry_for_port(port_name); + if (!impl) return nullopt; + return impl->get_baseline_version(paths, port_name); + } + void RegistrySet::add_registry(Registry&& r) { registries_.push_back(std::move(r)); } - void RegistrySet::set_default_registry(std::unique_ptr<RegistryImpl>&& r) { default_registry_ = std::move(r); } + void RegistrySet::set_default_registry(std::unique_ptr<RegistryImplementation>&& r) + { + default_registry_ = std::move(r); + } void RegistrySet::set_default_registry(std::nullptr_t) { default_registry_.reset(); } + + void RegistrySet::experimental_set_builtin_registry_baseline(StringView baseline) const + { + // to check if we should warn + bool default_registry_is_builtin = false; + if (auto builtin_registry = dynamic_cast<BuiltinRegistry*>(default_registry_.get())) + { + default_registry_is_builtin = true; + builtin_registry->m_baseline_identifier.assign(baseline.begin(), baseline.end()); + } + + if (!default_registry_is_builtin || registries_.size() != 0) + { + System::print2(System::Color::warning, + "Warning: when using the registries feature, one should not use `\"$x-default-baseline\"` " + "to set the baseline.\n", + " Instead, use the \"baseline\" field of the registry."); + } + + for (auto& reg : registries_) + { + if (auto builtin_registry = dynamic_cast<BuiltinRegistry*>(reg.implementation_.get())) + { + builtin_registry->m_baseline_identifier.assign(baseline.begin(), baseline.end()); + } + } + } + + bool RegistrySet::has_modifications() const + { + if (!registries_.empty()) + { + return true; + } + if (auto builtin_reg = dynamic_cast<const BuiltinRegistry*>(default_registry_.get())) + { + if (builtin_reg->m_baseline_identifier.empty()) + { + return false; + } + return true; + } + // default_registry_ is not a BuiltinRegistry + return true; + } } diff --git a/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp index 5b25b32d8..0310a01ae 100644 --- a/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp +++ b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp @@ -277,6 +277,7 @@ namespace vcpkg {PACKAGES_ROOT_DIR_ARG, &VcpkgCmdArguments::packages_root_dir}, {SCRIPTS_ROOT_DIR_ARG, &VcpkgCmdArguments::scripts_root_dir}, {BUILTIN_PORTS_ROOT_DIR_ARG, &VcpkgCmdArguments::builtin_ports_root_dir}, + {BUILTIN_PORT_VERSIONS_DIR_ARG, &VcpkgCmdArguments::builtin_port_versions_dir}, }; constexpr static std::pair<StringView, std::vector<std::string> VcpkgCmdArguments::*> diff --git a/toolsrc/src/vcpkg/vcpkgpaths.cpp b/toolsrc/src/vcpkg/vcpkgpaths.cpp index 5c7e6a77e..a41258761 100644 --- a/toolsrc/src/vcpkg/vcpkgpaths.cpp +++ b/toolsrc/src/vcpkg/vcpkgpaths.cpp @@ -10,7 +10,6 @@ #include <vcpkg/build.h> #include <vcpkg/commands.h> #include <vcpkg/configuration.h> -#include <vcpkg/configurationdeserializer.h> #include <vcpkg/globalstate.h> #include <vcpkg/metrics.h> #include <vcpkg/packagespec.h> @@ -89,9 +88,9 @@ namespace vcpkg const fs::path& filepath) { Json::Reader reader; - ConfigurationDeserializer deserializer(args); + auto deserializer = make_configuration_deserializer(filepath.parent_path()); - auto parsed_config_opt = reader.visit(obj, deserializer); + auto parsed_config_opt = reader.visit(obj, *deserializer); if (!reader.errors().empty()) { System::print2(System::Color::error, "Errors occurred while parsing ", fs::u8string(filepath), "\n"); @@ -103,6 +102,8 @@ namespace vcpkg Checks::exit_fail(VCPKG_LINE_INFO); } + parsed_config_opt.get()->validate_feature_flags(args.feature_flag_settings()); + return std::move(parsed_config_opt).value_or_exit(VCPKG_LINE_INFO); } @@ -342,6 +343,8 @@ If you wish to silence this error and use classic mode, you can: scripts = process_input_directory(filesystem, root, args.scripts_root_dir.get(), "scripts", VCPKG_LINE_INFO); builtin_ports = process_output_directory(filesystem, root, args.builtin_ports_root_dir.get(), "ports", VCPKG_LINE_INFO); + builtin_port_versions = process_output_directory( + filesystem, root, args.builtin_port_versions_dir.get(), "port_versions", VCPKG_LINE_INFO); prefab = root / fs::u8path("prefab"); if (args.default_visual_studio_path) @@ -493,11 +496,13 @@ If you wish to silence this error and use classic mode, you can: fs.remove_all(dot_git_dir, VCPKG_LINE_INFO); // All git commands are run with: --git-dir={dot_git_dir} --work-tree={work_tree_temp} - // git clone --no-checkout --local {vcpkg_root} {dot_git_dir} + // git clone --no-checkout --local --no-hardlinks {vcpkg_root} {dot_git_dir} + // note that `--no-hardlinks` is added because otherwise, git fails to clone in some cases System::CmdLineBuilder clone_cmd_builder = git_cmd_builder(paths, dot_git_dir, work_tree) .string_arg("clone") .string_arg("--no-checkout") .string_arg("--local") + .string_arg("--no-hardlinks") .path_arg(local_repo) .path_arg(dot_git_dir); const auto clone_output = System::cmd_execute_and_capture_output(clone_cmd_builder.extract()); @@ -578,6 +583,7 @@ If you wish to silence this error and use classic mode, you can: .string_arg("clone") .string_arg("--no-checkout") .string_arg("--local") + .string_arg("--no-hardlinks") .path_arg(local_repo) .path_arg(dot_git_dir); const auto clone_output = System::cmd_execute_and_capture_output(clone_cmd_builder.extract()); @@ -662,8 +668,8 @@ If you wish to silence this error and use classic mode, you can: * Since we are checking a git tree object, all files will be checked out to the root of `work-tree`. * Because of that, it makes sense to use the git hash as the name for the directory. */ - const fs::path local_repo = this->root; - const fs::path destination = this->versions_output / fs::u8path(git_tree) / fs::u8path(port_name); + const fs::path& local_repo = this->root; + fs::path destination = this->versions_output / fs::u8path(git_tree) / fs::u8path(port_name); if (!fs.exists(destination / "CONTROL") && !fs.exists(destination / "vcpkg.json")) { diff --git a/toolsrc/src/vcpkg/versiondeserializers.cpp b/toolsrc/src/vcpkg/versiondeserializers.cpp index ee64630e3..d791e3e14 100644 --- a/toolsrc/src/vcpkg/versiondeserializers.cpp +++ b/toolsrc/src/vcpkg/versiondeserializers.cpp @@ -146,75 +146,6 @@ namespace vcpkg namespace { - struct VersionDbEntryDeserializer final : Json::IDeserializer<VersionDbEntry> - { - static constexpr StringLiteral GIT_TREE = "git-tree"; - - StringView type_name() const override { return "a version database entry"; } - View<StringView> valid_fields() const override - { - static const StringView u[] = {GIT_TREE}; - static const auto t = vcpkg::Util::Vectors::concat<StringView>(schemed_deserializer_fields(), u); - return t; - } - - Optional<VersionDbEntry> visit_object(Json::Reader& r, const Json::Object& obj) override - { - VersionDbEntry ret; - - auto schemed_version = visit_required_schemed_deserializer(type_name(), r, obj); - ret.scheme = schemed_version.scheme; - ret.version = std::move(schemed_version.versiont); - - static Json::StringDeserializer git_tree_deserializer("a git object SHA"); - - r.required_object_field(type_name(), obj, GIT_TREE, ret.git_tree, git_tree_deserializer); - - return std::move(ret); - } - - static VersionDbEntryDeserializer instance; - }; - - struct VersionDbEntryArrayDeserializer final : Json::IDeserializer<std::vector<VersionDbEntry>> - { - virtual StringView type_name() const override { return "an array of versions"; } - - virtual Optional<std::vector<VersionDbEntry>> visit_array(Json::Reader& r, const Json::Array& arr) override - { - return r.array_elements(arr, VersionDbEntryDeserializer::instance); - } - - static VersionDbEntryArrayDeserializer instance; - }; - - VersionDbEntryDeserializer VersionDbEntryDeserializer::instance; - VersionDbEntryArrayDeserializer VersionDbEntryArrayDeserializer::instance; - - struct BaselineDeserializer final : Json::IDeserializer<std::map<std::string, VersionT, std::less<>>> - { - StringView type_name() const override { return "a baseline object"; } - - Optional<type> visit_object(Json::Reader& r, const Json::Object& obj) override - { - std::map<std::string, VersionT, std::less<>> result; - - for (auto&& pr : obj) - { - const auto& version_value = pr.second; - VersionT version; - r.visit_in_key(version_value, pr.first, version, get_versiont_deserializer_instance()); - - result.emplace(pr.first.to_string(), std::move(version)); - } - - return std::move(result); - } - - static BaselineDeserializer instance; - }; - BaselineDeserializer BaselineDeserializer::instance; - struct VersionTDeserializer final : Json::IDeserializer<VersionT> { StringView type_name() const override { return "a version object"; } @@ -245,71 +176,4 @@ namespace namespace vcpkg { Json::IDeserializer<VersionT>& get_versiont_deserializer_instance() { return VersionTDeserializer::instance; } - - ExpectedS<std::map<std::string, VersionT, std::less<>>> parse_baseline_file(Files::Filesystem& fs, - StringView baseline_name, - const fs::path& baseline_file_path) - { - if (!fs.exists(baseline_file_path)) - { - return Strings::format("Couldn't find `%s`", fs::u8string(baseline_file_path)); - } - - auto value = Json::parse_file(VCPKG_LINE_INFO, fs, baseline_file_path); - if (!value.first.is_object()) - { - return Strings::format("Error: `%s` does not have a top-level object.", fs::u8string(baseline_file_path)); - } - - const auto& obj = value.first.object(); - auto baseline_value = obj.get(baseline_name); - if (!baseline_value) - { - return Strings::format( - "Error: `%s` does not contain the baseline \"%s\"", fs::u8string(baseline_file_path), baseline_name); - } - - Json::Reader r; - std::map<std::string, VersionT, std::less<>> result; - r.visit_in_key(*baseline_value, baseline_name, result, BaselineDeserializer::instance); - if (!r.errors().empty()) - { - return Strings::format( - "Error: failed to parse `%s`:\n%s", fs::u8string(baseline_file_path), Strings::join("\n", r.errors())); - } - return result; - } - - ExpectedS<std::vector<VersionDbEntry>> parse_versions_file(Files::Filesystem& fs, - StringView port_name, - const fs::path& versions_file_path) - { - (void)port_name; - if (!fs.exists(versions_file_path)) - { - return Strings::format("Couldn't find the versions database file: %s", fs::u8string(versions_file_path)); - } - - auto versions_json = Json::parse_file(VCPKG_LINE_INFO, fs, versions_file_path); - if (!versions_json.first.is_object()) - { - return Strings::format("Error: `%s` does not have a top level object.", fs::u8string(versions_file_path)); - } - - const auto& versions_object = versions_json.first.object(); - auto maybe_versions_array = versions_object.get("versions"); - if (!maybe_versions_array || !maybe_versions_array->is_array()) - { - return Strings::format("Error: `%s` does not contain a versions array.", fs::u8string(versions_file_path)); - } - - std::vector<VersionDbEntry> db_entries; - // Avoid warning treated as error. - if (maybe_versions_array != nullptr) - { - Json::Reader r; - r.visit_in_key(*maybe_versions_array, "versions", db_entries, VersionDbEntryArrayDeserializer::instance); - } - return db_entries; - } } diff --git a/toolsrc/src/vcpkg/versions.cpp b/toolsrc/src/vcpkg/versions.cpp index 5ea2a8182..239c0e728 100644 --- a/toolsrc/src/vcpkg/versions.cpp +++ b/toolsrc/src/vcpkg/versions.cpp @@ -244,4 +244,4 @@ namespace vcpkg::Versions Checks::unreachable(VCPKG_LINE_INFO); } -}
\ No newline at end of file +} |
