diff options
| author | nicole mazzuca <mazzucan@outlook.com> | 2020-09-02 09:13:44 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-09-02 09:13:44 -0700 |
| commit | 9740611cab8faa9d9ada57265471e8e4786d37bc (patch) | |
| tree | 71ca07c2bdd77a89f9596e759e618f64077821d9 | |
| parent | f4a2dce829fb7cbf7e988edc5eceb40e35cbf2a6 (diff) | |
| download | vcpkg-9740611cab8faa9d9ada57265471e8e4786d37bc.tar.gz vcpkg-9740611cab8faa9d9ada57265471e8e4786d37bc.zip | |
[vcpkg] Registries MVP (#13038)
37 files changed, 1605 insertions, 611 deletions
diff --git a/toolsrc/.clang-format b/toolsrc/.clang-format index cdaabb8c1..374a4233e 100644 --- a/toolsrc/.clang-format +++ b/toolsrc/.clang-format @@ -40,13 +40,15 @@ IncludeCategories: Priority: -1 - Regex: '^<catch2/catch\.hpp>$' Priority: 1 - - Regex: '^<vcpkg/fwd/.*\.h>$' + - Regex: '^<vcpkg/base/fwd/.*\.h>$' Priority: 2 - - Regex: '^<vcpkg/base/.*\.h>$' + - Regex: '^<vcpkg/fwd/.*\.h>$' Priority: 3 - - Regex: '^<vcpkg/.*\.h>$' + - Regex: '^<vcpkg/base/.*\.h>$' Priority: 4 - - Regex: '^<[a-z0-9_]*\.h>$' + - Regex: '^<vcpkg/.*\.h>$' Priority: 5 - - Regex: '^<[a-z0-9_]*>$' # C++ standard library + - Regex: '^<[a-z0-9_]*\.h>$' Priority: 6 + - Regex: '^<[a-z0-9_]*>$' # C++ standard library + Priority: 7 diff --git a/toolsrc/CMakeLists.txt b/toolsrc/CMakeLists.txt index 8b525c248..aa54a791b 100644 --- a/toolsrc/CMakeLists.txt +++ b/toolsrc/CMakeLists.txt @@ -33,8 +33,8 @@ endif() file(GLOB VCPKGLIB_SOURCES CONFIGURE_DEPENDS src/vcpkg/*.cpp) file(GLOB VCPKGLIB_BASE_SOURCES CONFIGURE_DEPENDS src/vcpkg/base/*.cpp) -file(GLOB VCPKGLIB_INCLUDES CONFIGURE_DEPENDS include/vcpkg/*.h) -file(GLOB VCPKGLIB_BASE_INCLUDES CONFIGURE_DEPENDS include/vcpkg/base/*.h) +file(GLOB VCPKGLIB_INCLUDES CONFIGURE_DEPENDS include/vcpkg/*.h include/vcpkg/fwd/*.h) +file(GLOB VCPKGLIB_BASE_INCLUDES CONFIGURE_DEPENDS include/vcpkg/base/*.h include/vcpkg/base/fwd/*.h) set(VCPKG_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/vcpkg.cpp) @@ -154,19 +154,20 @@ endif() find_program(CLANG_FORMAT clang-format) if(CLANG_FORMAT) - add_custom_target(format COMMAND ${CLANG_FORMAT} -i -verbose - ${CMAKE_CURRENT_SOURCE_DIR}/src/pch.cpp - ${VCPKGLIB_BASE_SOURCES} - ${VCPKGLIB_SOURCES} - ${CMAKE_CURRENT_SOURCE_DIR}/include/pch.h - ${VCPKGLIB_BASE_INCLUDES} - ${VCPKGLIB_INCLUDES} - - ${VCPKG_SOURCES} - ${VCPKGMETRICSUPLOADER_SOURCES} - - ${VCPKG_TEST_SOURCES} - ${VCPKG_TEST_INCLUDES} - - ${VCPKG_FUZZ_SOURCES}) + # doing all of these formats in one line has a tendency to overflow the command line length + add_custom_target(format + COMMAND ${CLANG_FORMAT} -i -verbose ${CMAKE_CURRENT_SOURCE_DIR}/src/pch.cpp + COMMAND ${CLANG_FORMAT} -i -verbose ${VCPKGLIB_BASE_SOURCES} + COMMAND ${CLANG_FORMAT} -i -verbose ${VCPKGLIB_SOURCES} + COMMAND ${CLANG_FORMAT} -i -verbose ${CMAKE_CURRENT_SOURCE_DIR}/include/pch.h + COMMAND ${CLANG_FORMAT} -i -verbose ${VCPKGLIB_BASE_INCLUDES} + COMMAND ${CLANG_FORMAT} -i -verbose ${VCPKGLIB_INCLUDES} + + COMMAND ${CLANG_FORMAT} -i -verbose ${VCPKG_SOURCES} + COMMAND ${CLANG_FORMAT} -i -verbose ${VCPKGMETRICSUPLOADER_SOURCES} + + COMMAND ${CLANG_FORMAT} -i -verbose ${VCPKG_TEST_SOURCES} + COMMAND ${CLANG_FORMAT} -i -verbose ${VCPKG_TEST_INCLUDES} + + COMMAND ${CLANG_FORMAT} -i -verbose ${VCPKG_FUZZ_SOURCES}) endif() diff --git a/toolsrc/include/vcpkg/base/files.h b/toolsrc/include/vcpkg/base/files.h index e0a555024..08bbe7906 100644 --- a/toolsrc/include/vcpkg/base/files.h +++ b/toolsrc/include/vcpkg/base/files.h @@ -157,7 +157,7 @@ namespace vcpkg::Files /// <summary>Read text lines from a file</summary> /// <remarks>Lines will have up to one trailing carriage-return character stripped (CRLF)</remarks> virtual Expected<std::vector<std::string>> read_lines(const fs::path& file_path) const = 0; - virtual fs::path find_file_recursively_up(const fs::path& starting_dir, const std::string& filename) const = 0; + virtual fs::path find_file_recursively_up(const fs::path& starting_dir, const fs::path& filename) const = 0; virtual std::vector<fs::path> get_files_recursive(const fs::path& dir) const = 0; virtual std::vector<fs::path> get_files_non_recursive(const fs::path& dir) const = 0; void write_lines(const fs::path& file_path, const std::vector<std::string>& lines, LineInfo linfo); diff --git a/toolsrc/include/vcpkg/base/fwd/json.h b/toolsrc/include/vcpkg/base/fwd/json.h new file mode 100644 index 000000000..7071103c1 --- /dev/null +++ b/toolsrc/include/vcpkg/base/fwd/json.h @@ -0,0 +1,44 @@ +#pragma once + +#include <vcpkg/base/fwd/optional.h> +#include <vcpkg/base/fwd/span.h> +#include <vcpkg/base/fwd/stringview.h> + +namespace vcpkg::Json +{ + struct JsonStyle; + enum class ValueKind : int; + struct Value; + struct Object; + struct Array; + + struct ReaderError; + struct BasicReaderError; + struct Reader; + + // This is written all the way out so that one can include a subclass in a header + template<class Type> + struct IDeserializer + { + using type = Type; + virtual StringView type_name() const = 0; + + virtual Span<const StringView> valid_fields() const; + + virtual Optional<Type> visit_null(Reader&, StringView); + virtual Optional<Type> visit_boolean(Reader&, StringView, bool); + virtual Optional<Type> visit_integer(Reader& r, StringView field_name, int64_t i); + virtual Optional<Type> visit_number(Reader&, StringView, double); + virtual Optional<Type> visit_string(Reader&, StringView, StringView); + virtual Optional<Type> visit_array(Reader&, StringView, const Array&); + virtual Optional<Type> visit_object(Reader&, StringView, const Object&); + + protected: + IDeserializer() = default; + IDeserializer(const IDeserializer&) = default; + IDeserializer& operator=(const IDeserializer&) = default; + IDeserializer(IDeserializer&&) = default; + IDeserializer& operator=(IDeserializer&&) = default; + virtual ~IDeserializer() = default; + }; +} diff --git a/toolsrc/include/vcpkg/base/fwd/optional.h b/toolsrc/include/vcpkg/base/fwd/optional.h new file mode 100644 index 000000000..2c3a7ea6c --- /dev/null +++ b/toolsrc/include/vcpkg/base/fwd/optional.h @@ -0,0 +1,7 @@ +#pragma once + +namespace vcpkg +{ + template<class T> + struct Optional; +} diff --git a/toolsrc/include/vcpkg/base/fwd/span.h b/toolsrc/include/vcpkg/base/fwd/span.h new file mode 100644 index 000000000..af78aa642 --- /dev/null +++ b/toolsrc/include/vcpkg/base/fwd/span.h @@ -0,0 +1,7 @@ +#pragma once + +namespace vcpkg +{ + template<class T> + struct Span; +} diff --git a/toolsrc/include/vcpkg/base/fwd/stringview.h b/toolsrc/include/vcpkg/base/fwd/stringview.h new file mode 100644 index 000000000..e297fd43b --- /dev/null +++ b/toolsrc/include/vcpkg/base/fwd/stringview.h @@ -0,0 +1,6 @@ +#pragma once + +namespace vcpkg +{ + struct StringView; +} diff --git a/toolsrc/include/vcpkg/base/json.h b/toolsrc/include/vcpkg/base/json.h index a4ad44ebd..3539f2df4 100644 --- a/toolsrc/include/vcpkg/base/json.h +++ b/toolsrc/include/vcpkg/base/json.h @@ -1,5 +1,7 @@ #pragma once +#include <vcpkg/base/fwd/json.h> + #include <vcpkg/base/expected.h> #include <vcpkg/base/files.h> #include <vcpkg/base/parse.h> @@ -64,10 +66,7 @@ namespace vcpkg::Json int indent = 2; }; - struct Array; - struct Object; - - enum class ValueKind + enum class ValueKind : int { Null, Boolean, @@ -81,7 +80,6 @@ namespace vcpkg::Json namespace impl { struct ValueImpl; - struct SyntaxErrorImpl; } struct Value @@ -289,6 +287,53 @@ namespace vcpkg::Json underlying_t underlying_; }; + VCPKG_MSVC_WARNING(push); + VCPKG_MSVC_WARNING(disable : 4505); + + template<class Type> + Span<const StringView> IDeserializer<Type>::valid_fields() const + { + return {}; + } + + template<class Type> + Optional<Type> IDeserializer<Type>::visit_null(Reader&, StringView) + { + return nullopt; + } + template<class Type> + Optional<Type> IDeserializer<Type>::visit_boolean(Reader&, StringView, bool) + { + return nullopt; + } + template<class Type> + Optional<Type> IDeserializer<Type>::visit_integer(Reader& r, StringView field_name, int64_t i) + { + return this->visit_number(r, field_name, static_cast<double>(i)); + } + template<class Type> + Optional<Type> IDeserializer<Type>::visit_number(Reader&, StringView, double) + { + return nullopt; + } + template<class Type> + Optional<Type> IDeserializer<Type>::visit_string(Reader&, StringView, StringView) + { + return nullopt; + } + template<class Type> + Optional<Type> IDeserializer<Type>::visit_array(Reader&, StringView, const Array&) + { + return nullopt; + } + template<class Type> + Optional<Type> IDeserializer<Type>::visit_object(Reader&, StringView, const Object&) + { + return nullopt; + } + + VCPKG_MSVC_WARNING(pop); + struct ReaderError { virtual void add_missing_field(std::string&& type, std::string&& key) = 0; @@ -299,6 +344,37 @@ namespace vcpkg::Json virtual ~ReaderError() = default; }; + struct BasicReaderError : ReaderError + { + virtual void add_missing_field(std::string&& type, std::string&& key) override + { + missing_fields.emplace_back(std::move(type), std::move(key)); + } + virtual void add_expected_type(std::string&& key, std::string&& expected_type) override + { + expected_types.emplace_back(std::move(key), std::move(expected_type)); + } + virtual void add_extra_fields(std::string&& type, std::vector<std::string>&& fields) override + { + extra_fields.emplace_back(std::move(type), std::move(fields)); + } + virtual void add_mutually_exclusive_fields(std::string&& type, std::vector<std::string>&& fields) override + { + mutually_exclusive_fields.emplace_back(std::move(type), std::move(fields)); + } + + bool has_error() const + { + return !missing_fields.empty() || !expected_types.empty() || !extra_fields.empty() || + !mutually_exclusive_fields.empty(); + } + + std::vector<std::pair<std::string, std::string>> missing_fields; + std::vector<std::pair<std::string, std::string>> expected_types; + std::vector<std::pair<std::string, std::vector<std::string>>> extra_fields; + std::vector<std::pair<std::string, std::vector<std::string>>> mutually_exclusive_fields; + }; + struct Reader { explicit Reader(ReaderError* err) : err(err) { } @@ -308,30 +384,31 @@ namespace vcpkg::Json private: ReaderError* err; - template<class Visitor> - using VisitorType = typename std::remove_reference_t<Visitor>::type; - - template<class Visitor> - Optional<VisitorType<Visitor>> internal_visit(const Value& value, StringView key, Visitor& visitor) + template<class Type> + Optional<Type> internal_visit(const Value& value, StringView key, IDeserializer<Type>& visitor) { switch (value.kind()) { - using VK = Json::ValueKind; - case VK::Null: return visitor.visit_null(*this, key); - case VK::Boolean: return visitor.visit_boolean(*this, key, value.boolean()); - case VK::Integer: return visitor.visit_integer(*this, key, value.integer()); - case VK::Number: return visitor.visit_number(*this, key, value.number()); - case VK::String: return visitor.visit_string(*this, key, value.string()); - case VK::Array: return visitor.visit_array(*this, key, value.array()); - case VK::Object: return visitor.visit_object(*this, key, value.object()); + case ValueKind::Null: return visitor.visit_null(*this, key); + case ValueKind::Boolean: return visitor.visit_boolean(*this, key, value.boolean()); + case ValueKind::Integer: return visitor.visit_integer(*this, key, value.integer()); + case ValueKind::Number: return visitor.visit_number(*this, key, value.number()); + case ValueKind::String: return visitor.visit_string(*this, key, value.string()); + case ValueKind::Array: return visitor.visit_array(*this, key, value.array()); + case ValueKind::Object: + { + const auto& obj = value.object(); + check_for_unexpected_fields(obj, visitor.valid_fields(), visitor.type_name()); + return visitor.visit_object(*this, key, value.object()); + } } vcpkg::Checks::exit_fail(VCPKG_LINE_INFO); } // returns whether the field was found, not whether it was valid - template<class Visitor> - bool internal_field(const Object& obj, StringView key, VisitorType<Visitor>& place, Visitor& visitor) + template<class Type> + bool internal_field(const Object& obj, StringView key, Type& place, IDeserializer<Type>& visitor) { auto value = obj.get(key); if (!value) @@ -339,7 +416,7 @@ namespace vcpkg::Json return false; } - Optional<VisitorType<Visitor>> opt = internal_visit(*value, key, visitor); + Optional<Type> opt = internal_visit(*value, key, visitor); if (auto val = opt.get()) { @@ -353,27 +430,115 @@ namespace vcpkg::Json return true; } + static std::vector<std::string> invalid_json_fields(const Json::Object& obj, + Span<const StringView> known_fields) noexcept + { + const auto field_is_unknown = [known_fields](StringView sv) { + // allow directives + if (sv.size() != 0 && *sv.begin() == '$') + { + return false; + } + return std::find(known_fields.begin(), known_fields.end(), sv) == known_fields.end(); + }; + + std::vector<std::string> res; + for (const auto& kv : obj) + { + if (field_is_unknown(kv.first)) + { + res.push_back(kv.first.to_string()); + } + } + + return res; + } + + // 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, Span<const StringView> valid_fields, StringView type_name) + { + if (valid_fields.size() == 0) + { + return; + } + + auto extra_fields = invalid_json_fields(obj, valid_fields); + if (!extra_fields.empty()) + { + error().add_extra_fields(type_name.to_string(), std::move(extra_fields)); + } + } + public: - template<class Visitor> + template<class Type> void required_object_field( - StringView type, const Object& obj, StringView key, VisitorType<Visitor>& place, Visitor&& visitor) + StringView type, const Object& obj, StringView key, Type& place, IDeserializer<Type>& visitor) { if (!internal_field(obj, key, place, visitor)) { err->add_missing_field(type.to_string(), key.to_string()); } } + template<class Type> + void required_object_field( + StringView type, const Object& obj, StringView key, Type& place, IDeserializer<Type>&& visitor) + { + required_object_field(type, obj, key, place, visitor); + } - template<class Visitor> - void optional_object_field(const Object& obj, StringView key, VisitorType<Visitor>& place, Visitor&& visitor) + // returns whether key \in obj + template<class Type> + bool optional_object_field(const Object& obj, StringView key, Type& place, IDeserializer<Type>& visitor) + { + return internal_field(obj, key, place, visitor); + } + template<class Type> + bool optional_object_field(const Object& obj, StringView key, Type& place, IDeserializer<Type>&& visitor) { - internal_field(obj, key, place, visitor); + return optional_object_field(obj, key, place, visitor); } - template<class Visitor> - Optional<std::vector<VisitorType<Visitor>>> array_elements(const Array& arr, StringView key, Visitor&& visitor) + template<class Type> + Optional<Type> visit_value(const Value& value, StringView key, IDeserializer<Type>& visitor) + { + return internal_visit(value, key, visitor); + } + template<class Type> + Optional<Type> visit_value(const Value& value, StringView key, IDeserializer<Type>&& visitor) { - std::vector<VisitorType<Visitor>> result; + return visit_value(value, key, visitor); + } + + template<class Type> + Optional<Type> visit_value(const Array& value, StringView key, IDeserializer<Type>& visitor) + { + return visitor.visit_array(*this, key, value); + } + template<class Type> + Optional<Type> visit_value(const Array& value, StringView key, IDeserializer<Type>&& visitor) + { + return visit_value(value, key, visitor); + } + + template<class Type> + Optional<Type> visit_value(const Object& value, StringView key, IDeserializer<Type>& visitor) + { + check_for_unexpected_fields(value, visitor.valid_fields(), visitor.type_name()); + return visitor.visit_object(*this, key, value); + } + template<class Type> + Optional<Type> visit_value(const Object& value, StringView key, IDeserializer<Type>&& visitor) + { + return visit_value(value, key, visitor); + } + + template<class Type> + Optional<std::vector<Type>> array_elements(const Array& arr, StringView key, IDeserializer<Type>& visitor) + { + std::vector<Type> result; for (const auto& el : arr) { auto opt = internal_visit(el, key, visitor); @@ -388,42 +553,161 @@ namespace vcpkg::Json } return std::move(result); } + template<class Type> + Optional<std::vector<Type>> array_elements(const Array& arr, StringView key, IDeserializer<Type>&& visitor) + { + return array_elements(arr, key, visitor); + } + }; + + struct StringDeserializer final : IDeserializer<std::string> + { + virtual StringView type_name() const override { return type_name_; } + virtual Optional<std::string> visit_string(Reader&, StringView, StringView sv) override + { + return sv.to_string(); + } + + explicit StringDeserializer(StringView type_name_) : type_name_(type_name_) { } + + private: + StringView type_name_; + }; + + struct PathDeserializer final : IDeserializer<fs::path> + { + virtual StringView type_name() const override { return "a path"; } + virtual Optional<fs::path> visit_string(Reader&, StringView, StringView sv) override { return fs::u8path(sv); } + }; + + struct NaturalNumberDeserializer final : IDeserializer<int> + { + virtual StringView type_name() const override { return "a natural number"; } + + virtual Optional<int> visit_integer(Reader&, StringView, int64_t value) override + { + if (value > std::numeric_limits<int>::max() || value < 0) + { + return nullopt; + } + return static_cast<int>(value); + } + }; + + struct BooleanDeserializer final : IDeserializer<bool> + { + virtual StringView type_name() const override { return "a boolean"; } + + virtual Optional<bool> visit_boolean(Reader&, StringView, bool b) override { return b; } + }; + + enum class AllowEmpty : bool + { + No, + Yes, }; - // Warning: NEVER use this type except as a CRTP base template<class Underlying> - struct VisitorCrtpBase + struct ArrayDeserializer final : IDeserializer<std::vector<typename Underlying::type>> + { + using typename IDeserializer<std::vector<typename Underlying::type>>::type; + + virtual StringView type_name() const override { return type_name_; } + + ArrayDeserializer(StringView type_name_, AllowEmpty allow_empty, Underlying&& t = {}) + : type_name_(type_name_), underlying_visitor_(static_cast<Underlying&&>(t)), allow_empty_(allow_empty) + { + } + + virtual Optional<type> visit_array(Reader& r, StringView key, const Array& arr) override + { + if (allow_empty_ == AllowEmpty::No && arr.size() == 0) + { + return nullopt; + } + return r.array_elements(arr, key, underlying_visitor_); + } + + private: + StringView type_name_; + Underlying underlying_visitor_; + AllowEmpty allow_empty_; + }; + + struct ParagraphDeserializer final : IDeserializer<std::vector<std::string>> { - // the following function must be defined by the Underlying class - // const char* type_name(); + virtual StringView type_name() const override { return "a string or array of strings"; } - // We do this auto dance since function bodies are checked _after_ typedefs in the superclass, - // but function declarations are checked beforehand. Therefore, we can get C++ to use this typedef - // only once the function bodies are checked by returning `auto` and specifying the - // return type in the function body. - auto visit_null(Reader&, StringView) { return Optional<typename Underlying::type>(nullopt); } - auto visit_boolean(Reader&, StringView, bool) { return Optional<typename Underlying::type>(nullopt); } - auto visit_integer(Reader& r, StringView field_name, int64_t i) + virtual Optional<std::vector<std::string>> visit_string(Reader&, StringView, StringView sv) override { - return static_cast<Underlying&>(*this).visit_number(r, field_name, static_cast<double>(i)); + std::vector<std::string> out; + out.push_back(sv.to_string()); + return out; + } + + virtual Optional<std::vector<std::string>> visit_array(Reader& r, StringView key, const Array& arr) override + { + return r.array_elements(arr, key, StringDeserializer{"a string"}); + } + }; + + struct IdentifierDeserializer final : Json::IDeserializer<std::string> + { + virtual StringView type_name() const override { return "an identifier"; } + + // [a-z0-9]+(-[a-z0-9]+)*, plus not any of {prn, aux, nul, con, lpt[1-9], com[1-9], core, default} + static bool is_ident(StringView sv); + + virtual Optional<std::string> visit_string(Json::Reader&, StringView, StringView sv) override + { + if (is_ident(sv)) + { + return sv.to_string(); + } + else + { + return nullopt; + } } - auto visit_number(Reader&, StringView, double) { return Optional<typename Underlying::type>(nullopt); } - auto visit_string(Reader&, StringView, StringView) { return Optional<typename Underlying::type>(nullopt); } - auto visit_array(Reader&, StringView, const Json::Array&) + }; + + struct PackageNameDeserializer final : Json::IDeserializer<std::string> + { + virtual StringView type_name() const override { return "a package name"; } + + static bool is_package_name(StringView sv) { - return Optional<typename Underlying::type>(nullopt); + if (sv.size() == 0) + { + return false; + } + + for (const auto& ident : Strings::split(sv, '.')) + { + if (!IdentifierDeserializer::is_ident(ident)) + { + return false; + } + } + + return true; } - auto visit_object(Reader&, StringView, const Json::Object&) + + virtual Optional<std::string> visit_string(Json::Reader&, StringView, StringView sv) override { - return Optional<typename Underlying::type>(nullopt); + if (!is_package_name(sv)) + { + return nullopt; + } + return sv.to_string(); } - // we can't make the SMFs protected because of an issue with /permissive mode }; ExpectedT<std::pair<Value, JsonStyle>, std::unique_ptr<Parse::IParseError>> parse_file( const Files::Filesystem&, const fs::path&, std::error_code& ec) noexcept; ExpectedT<std::pair<Value, JsonStyle>, std::unique_ptr<Parse::IParseError>> parse( StringView text, const fs::path& filepath = {}) noexcept; + std::pair<Value, JsonStyle> parse_file(vcpkg::LineInfo linfo, const Files::Filesystem&, const fs::path&) noexcept; std::string stringify(const Value&, JsonStyle style); std::string stringify(const Object&, JsonStyle style); diff --git a/toolsrc/include/vcpkg/base/optional.h b/toolsrc/include/vcpkg/base/optional.h index 091cd498e..54e370dff 100644 --- a/toolsrc/include/vcpkg/base/optional.h +++ b/toolsrc/include/vcpkg/base/optional.h @@ -1,5 +1,7 @@ #pragma once +#include <vcpkg/base/fwd/optional.h> + #include <vcpkg/base/lineinfo.h> #include <vcpkg/base/pragmas.h> @@ -15,6 +17,9 @@ namespace vcpkg const static constexpr NullOpt nullopt{0}; + template<class T> + struct Optional; + namespace details { template<class T, bool B = std::is_copy_constructible<T>::value> @@ -167,6 +172,7 @@ namespace vcpkg { constexpr OptionalStorage() noexcept : m_t(nullptr) { } constexpr OptionalStorage(T& t) : m_t(&t) { } + constexpr OptionalStorage(Optional<T>& t) : m_t(t.get()) { } constexpr bool has_value() const { return m_t != nullptr; } @@ -176,6 +182,24 @@ namespace vcpkg T* m_t; }; + template<class T, bool B> + struct OptionalStorage<const T&, B> + { + constexpr OptionalStorage() noexcept : m_t(nullptr) { } + constexpr OptionalStorage(const T& t) : m_t(&t) { } + constexpr OptionalStorage(const Optional<T>& t) : m_t(t.get()) { } + constexpr OptionalStorage(const Optional<const T>& t) : m_t(t.get()) { } + constexpr OptionalStorage(Optional<T>&& t) = delete; + constexpr OptionalStorage(Optional<const T>&& t) = delete; + + constexpr bool has_value() const { return m_t != nullptr; } + + const T& value() const { return *this->m_t; } + + private: + const T* m_t; + }; + // Note: implemented in checks.cpp to cut the header dependency void exit_if_null(bool b, const LineInfo& line_info); } @@ -325,27 +349,4 @@ namespace vcpkg if (auto p = o.get()) return t != *p; return true; } - - template<class Container, class Projection> - auto common_projection(const Container& input, Projection proj) - -> Optional<std::decay_t<decltype(proj(*(input.begin())))>> - { - const auto last = input.end(); - auto first = input.begin(); - if (first == last) - { - return nullopt; - } - - const auto& prototype = proj(*first); - while (++first != last) - { - if (prototype != proj(*first)) - { - return nullopt; - } - } - - return prototype; - } } diff --git a/toolsrc/include/vcpkg/base/stringview.h b/toolsrc/include/vcpkg/base/stringview.h index 405142c36..9238990fe 100644 --- a/toolsrc/include/vcpkg/base/stringview.h +++ b/toolsrc/include/vcpkg/base/stringview.h @@ -1,5 +1,7 @@ #pragma once +#include <vcpkg/base/fwd/stringview.h> + #include <vcpkg/base/optional.h> #include <limits> diff --git a/toolsrc/include/vcpkg/base/util.h b/toolsrc/include/vcpkg/base/util.h index 11807b249..0453ac20a 100644 --- a/toolsrc/include/vcpkg/base/util.h +++ b/toolsrc/include/vcpkg/base/util.h @@ -1,5 +1,7 @@ #pragma once +#include <vcpkg/base/optional.h> + #include <algorithm> #include <functional> #include <map> @@ -63,7 +65,10 @@ namespace vcpkg::Util } template<class Range, class Func> - using FmapOut = std::remove_reference_t<decltype(std::declval<Func&>()(*std::declval<Range>().begin()))>; + using FmapRefOut = decltype(std::declval<Func&>()(*std::declval<Range>().begin())); + + template<class Range, class Func> + using FmapOut = std::decay_t<FmapRefOut<Range, Func>>; template<class Range, class Func, class Out = FmapOut<Range, Func>> std::vector<Out> fmap(Range&& xs, Func&& f) @@ -77,6 +82,28 @@ namespace vcpkg::Util return ret; } + template<class Range, class Proj, class Out = FmapRefOut<Range, Proj>> + Optional<Out> common_projection(Range&& input, Proj&& proj) + { + const auto last = input.end(); + auto first = input.begin(); + if (first == last) + { + return nullopt; + } + + Out prototype = proj(*first); + while (++first != last) + { + if (prototype != proj(*first)) + { + return nullopt; + } + } + + return prototype; + } + template<class Cont, class Func> using FmapFlattenOut = std::decay_t<decltype(*begin(std::declval<Func>()(*begin(std::declval<Cont>()))))>; diff --git a/toolsrc/include/vcpkg/configuration.h b/toolsrc/include/vcpkg/configuration.h new file mode 100644 index 000000000..44440fa04 --- /dev/null +++ b/toolsrc/include/vcpkg/configuration.h @@ -0,0 +1,41 @@ +#pragma once + +#include <vcpkg/fwd/configuration.h> +#include <vcpkg/fwd/vcpkgcmdarguments.h> + +#include <vcpkg/base/json.h> + +#include <vcpkg/registries.h> + +namespace vcpkg +{ + struct Configuration + { + // This member is set up via two different configuration options, + // `registries` and `default_registry`. The fall back logic is + // taken care of in RegistrySet. + RegistrySet registry_set; + }; + + 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 Span<const StringView> valid_fields() const override + { + constexpr static StringView t[] = {DEFAULT_REGISTRY, REGISTRIES}; + return t; + } + + virtual Optional<Configuration> visit_object(Json::Reader& r, StringView, const Json::Object& obj) override; + + ConfigurationDeserializer(const VcpkgCmdArguments& args); + + private: + bool print_json; + + bool registries_enabled; + }; +} diff --git a/toolsrc/include/vcpkg/fwd/configuration.h b/toolsrc/include/vcpkg/fwd/configuration.h new file mode 100644 index 000000000..5fa9cee34 --- /dev/null +++ b/toolsrc/include/vcpkg/fwd/configuration.h @@ -0,0 +1,6 @@ +#pragma once + +namespace vcpkg +{ + struct Configuration; +} diff --git a/toolsrc/include/vcpkg/fwd/registries.h b/toolsrc/include/vcpkg/fwd/registries.h new file mode 100644 index 000000000..7352c429d --- /dev/null +++ b/toolsrc/include/vcpkg/fwd/registries.h @@ -0,0 +1,8 @@ +#pragma once + +namespace vcpkg +{ + struct RegistryImpl; + struct Registry; + struct RegistrySet; +} diff --git a/toolsrc/include/vcpkg/paragraphs.h b/toolsrc/include/vcpkg/paragraphs.h index 25d037591..95726f913 100644 --- a/toolsrc/include/vcpkg/paragraphs.h +++ b/toolsrc/include/vcpkg/paragraphs.h @@ -17,23 +17,33 @@ namespace vcpkg::Paragraphs bool is_port_directory(const Files::Filesystem& fs, const fs::path& path); - Parse::ParseExpected<SourceControlFile> try_load_manifest(const Files::Filesystem& fs, - const std::string& port_name, - const fs::path& path_to_manifest, - std::error_code& ec); - Parse::ParseExpected<SourceControlFile> try_load_port(const Files::Filesystem& fs, const fs::path& path); ExpectedS<BinaryControlFile> try_load_cached_package(const VcpkgPaths& paths, const PackageSpec& spec); struct LoadResults { - std::vector<std::unique_ptr<SourceControlFile>> paragraphs; + std::vector<SourceControlFileLocation> paragraphs; std::vector<std::unique_ptr<Parse::ParseControlErrorInfo>> errors; }; - LoadResults try_load_all_ports(const Files::Filesystem& fs, const fs::path& ports_dir); - - std::vector<std::unique_ptr<SourceControlFile>> load_all_ports(const Files::Filesystem& fs, - const fs::path& ports_dir); + // this allows one to pass this around as an overload set to stuff like `Util::fmap`, + // as opposed to making it a function + constexpr struct + { + const std::string& operator()(const SourceControlFileLocation* loc) const + { + return (*this)(*loc->source_control_file); + } + const std::string& operator()(const SourceControlFileLocation& loc) const + { + return (*this)(*loc.source_control_file); + } + const std::string& operator()(const SourceControlFile& scf) const { return scf.core_paragraph->name; } + } get_name_of_control_file; + + LoadResults try_load_all_registry_ports(const VcpkgPaths& paths); + + std::vector<SourceControlFileLocation> load_all_registry_ports(const VcpkgPaths& paths); + std::vector<SourceControlFileLocation> load_overlay_ports(const VcpkgPaths& paths, const fs::path& dir); } diff --git a/toolsrc/include/vcpkg/portfileprovider.h b/toolsrc/include/vcpkg/portfileprovider.h index e4b5d9cb5..d3fa0b51f 100644 --- a/toolsrc/include/vcpkg/portfileprovider.h +++ b/toolsrc/include/vcpkg/portfileprovider.h @@ -26,14 +26,13 @@ namespace vcpkg::PortFileProvider struct PathsPortFileProvider : Util::ResourceBase, PortFileProvider { - explicit PathsPortFileProvider(const vcpkg::VcpkgPaths& paths, - const std::vector<std::string>& ports_dirs_paths); + explicit PathsPortFileProvider(const vcpkg::VcpkgPaths& paths, const std::vector<std::string>& overlay_ports); ExpectedS<const SourceControlFileLocation&> get_control_file(const std::string& src_name) const override; std::vector<const SourceControlFileLocation*> load_all_control_files() const override; private: - Files::Filesystem& filesystem; - std::vector<fs::path> ports_dirs; + const VcpkgPaths& paths; + std::vector<fs::path> overlay_ports; mutable std::unordered_map<std::string, SourceControlFileLocation> cache; }; } diff --git a/toolsrc/include/vcpkg/registries.h b/toolsrc/include/vcpkg/registries.h new file mode 100644 index 000000000..78c055dae --- /dev/null +++ b/toolsrc/include/vcpkg/registries.h @@ -0,0 +1,97 @@ +#pragma once + +#include <vcpkg/base/fwd/json.h> + +#include <vcpkg/base/files.h> +#include <vcpkg/base/optional.h> + +#include <vcpkg/vcpkgpaths.h> + +#include <memory> +#include <string> +#include <system_error> +#include <vector> + +namespace vcpkg +{ + struct RegistryImpl + { + virtual fs::path get_registry_root(const VcpkgPaths& paths) const = 0; + + virtual ~RegistryImpl() = default; + }; + + struct Registry + { + // requires: static_cast<bool>(implementation) + Registry(std::vector<std::string>&& packages, std::unique_ptr<RegistryImpl>&& implementation); + + Registry(std::vector<std::string>&&, std::nullptr_t) = delete; + + // always ordered lexicographically + Span<const std::string> packages() const { return packages_; } + const RegistryImpl& implementation() const { return *implementation_; } + + static std::unique_ptr<RegistryImpl> builtin_registry(); + + private: + std::vector<std::string> packages_; + std::unique_ptr<RegistryImpl> implementation_; + }; + + 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_DIRECTORY = "directory"; + + virtual StringView type_name() const override; + virtual Span<const StringView> valid_fields() const override; + + virtual Optional<std::unique_ptr<RegistryImpl>> visit_null(Json::Reader&, StringView) override; + virtual Optional<std::unique_ptr<RegistryImpl>> visit_object(Json::Reader&, + StringView, + const Json::Object&) override; + }; + + struct RegistryDeserializer final : Json::IDeserializer<Registry> + { + constexpr static StringLiteral PACKAGES = "packages"; + + virtual StringView type_name() const override; + virtual Span<const StringView> valid_fields() const override; + + virtual Optional<Registry> visit_object(Json::Reader&, StringView, const Json::Object&) override; + }; + + // this type implements the registry fall back logic from the registries RFC: + // A port name maps to one of the non-default registries if that registry declares + // that it is the registry for that port name, else it maps to the default registry + // if that registry exists; else, there is no registry for a port. + // The way one sets this up is via the `"registries"` and `"default_registry"` + // configuration fields. + struct RegistrySet + { + RegistrySet(); + + // 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; + + Span<const Registry> registries() const { return registries_; } + + const RegistryImpl* 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::nullptr_t r); + + private: + std::unique_ptr<RegistryImpl> default_registry_; + std::vector<Registry> registries_; + }; + +} diff --git a/toolsrc/include/vcpkg/sourceparagraph.h b/toolsrc/include/vcpkg/sourceparagraph.h index ac3699ca9..d5069adde 100644 --- a/toolsrc/include/vcpkg/sourceparagraph.h +++ b/toolsrc/include/vcpkg/sourceparagraph.h @@ -1,7 +1,8 @@ #pragma once +#include <vcpkg/base/fwd/json.h> + #include <vcpkg/base/expected.h> -#include <vcpkg/base/json.h> #include <vcpkg/base/span.h> #include <vcpkg/base/system.h> #include <vcpkg/base/system.print.h> diff --git a/toolsrc/include/vcpkg/vcpkgcmdarguments.h b/toolsrc/include/vcpkg/vcpkgcmdarguments.h index 3e956c83e..626e598be 100644 --- a/toolsrc/include/vcpkg/vcpkgcmdarguments.h +++ b/toolsrc/include/vcpkg/vcpkgcmdarguments.h @@ -170,9 +170,12 @@ namespace vcpkg Optional<bool> compiler_tracking = nullopt; constexpr static StringLiteral MANIFEST_MODE_FEATURE = "manifests"; Optional<bool> manifest_mode = nullopt; + constexpr static StringLiteral REGISTRIES_FEATURE = "registries"; + Optional<bool> registries_feature = nullopt; bool binary_caching_enabled() const { return binary_caching.value_or(true); } bool compiler_tracking_enabled() const { return compiler_tracking.value_or(true); } + bool registries_enabled() const { return registries_feature.value_or(false); } bool output_json() const { return json.value_or(false); } std::string command; diff --git a/toolsrc/include/vcpkg/vcpkgpaths.h b/toolsrc/include/vcpkg/vcpkgpaths.h index 2262543d9..ae2ee3823 100644 --- a/toolsrc/include/vcpkg/vcpkgpaths.h +++ b/toolsrc/include/vcpkg/vcpkgpaths.h @@ -1,5 +1,10 @@ #pragma once +#include <vcpkg/base/fwd/json.h> + +#include <vcpkg/fwd/configuration.h> +#include <vcpkg/fwd/registries.h> +#include <vcpkg/fwd/vcpkgcmdarguments.h> #include <vcpkg/fwd/vcpkgpaths.h> #include <vcpkg/base/cache.h> @@ -45,7 +50,6 @@ namespace vcpkg } struct BinaryParagraph; - struct VcpkgCmdArguments; struct PackageSpec; struct Triplet; @@ -77,6 +81,7 @@ namespace vcpkg fs::path original_cwd; fs::path root; fs::path manifest_root_dir; + fs::path config_root_dir; fs::path buildtrees; fs::path downloads; fs::path packages; @@ -102,6 +107,10 @@ namespace vcpkg const fs::path& get_tool_exe(const std::string& tool) const; const std::string& get_tool_version(const std::string& tool) const; + Optional<const Json::Object&> get_manifest() const; + Optional<const Json::JsonStyle&> get_manifest_style() const; + const Configuration& get_configuration() const; + /// <summary>Retrieve a toolset matching a VS version</summary> /// <remarks> /// Valid version strings are "v120", "v140", "v141", and "". Empty string gets the latest. @@ -112,7 +121,7 @@ namespace vcpkg const System::Environment& get_action_env(const Build::AbiInfo& abi_info) const; const std::string& get_triplet_info(const Build::AbiInfo& abi_info) const; - bool manifest_mode_enabled() const { return !manifest_root_dir.empty(); } + bool manifest_mode_enabled() const { return get_manifest().has_value(); } void track_feature_flag_metrics() const; diff --git a/toolsrc/src/vcpkg-test/files.cpp b/toolsrc/src/vcpkg-test/files.cpp index 255c87d69..a8c7c2ba2 100644 --- a/toolsrc/src/vcpkg-test/files.cpp +++ b/toolsrc/src/vcpkg-test/files.cpp @@ -155,6 +155,25 @@ namespace } } +TEST_CASE ("fs::combine works correctly", "[filesystem][files]") +{ + using namespace fs; + using namespace vcpkg::Files; + CHECK(combine(u8path("/a/b"), u8path("c/d")) == u8path("/a/b/c/d")); + CHECK(combine(u8path("a/b"), u8path("c/d")) == u8path("a/b/c/d")); + CHECK(combine(u8path("/a/b"), u8path("/c/d")) == u8path("/c/d")); + +#if defined(_WIN32) + CHECK(combine(u8path("C:/a/b"), u8path("c/d")) == u8path("C:/a/b/c/d")); + CHECK(combine(u8path("C:a/b"), u8path("c/d")) == u8path("C:a/b/c/d")); + CHECK(combine(u8path("C:a/b"), u8path("/c/d")) == u8path("C:/c/d")); + CHECK(combine(u8path("C:/a/b"), u8path("/c/d")) == u8path("C:/c/d")); + CHECK(combine(u8path("C:/a/b"), u8path("D:/c/d")) == u8path("D:/c/d")); + CHECK(combine(u8path("C:/a/b"), u8path("D:c/d")) == u8path("D:c/d")); + CHECK(combine(u8path("C:/a/b"), u8path("C:c/d")) == u8path("C:/a/b/c/d")); +#endif +} + TEST_CASE ("remove all", "[files]") { auto urbg = get_urbg(0); diff --git a/toolsrc/src/vcpkg-test/optional.cpp b/toolsrc/src/vcpkg-test/optional.cpp index 929410eb3..f3c61387f 100644 --- a/toolsrc/src/vcpkg-test/optional.cpp +++ b/toolsrc/src/vcpkg-test/optional.cpp @@ -1,6 +1,7 @@ #include <catch2/catch.hpp> #include <vcpkg/base/optional.h> +#include <vcpkg/base/util.h> #include <vector> @@ -27,9 +28,39 @@ TEST_CASE ("equal", "[optional]") CHECK(Optional<int>{42} == Optional<int>{42}); } +TEST_CASE ("ref conversion", "[optional]") +{ + using vcpkg::Optional; + + Optional<int> i_empty; + Optional<int> i_1 = 1; + const Optional<int> ci_1 = 1; + + Optional<int&> ref_empty = i_empty; + Optional<const int&> cref_empty = i_empty; + + Optional<int&> ref_1 = i_1; + Optional<const int&> cref_1 = ci_1; + + REQUIRE(ref_empty.has_value() == false); + REQUIRE(cref_empty.has_value() == false); + + REQUIRE(ref_1.get() == i_1.get()); + REQUIRE(cref_1.get() == ci_1.get()); + + ref_empty = i_1; + cref_empty = ci_1; + REQUIRE(ref_empty.get() == i_1.get()); + REQUIRE(cref_empty.get() == ci_1.get()); + + const int x = 5; + cref_1 = x; + REQUIRE(cref_1.get() == &x); +} + TEST_CASE ("common_projection", "[optional]") { - using vcpkg::common_projection; + using vcpkg::Util::common_projection; std::vector<int> input; CHECK(!common_projection(input, identity_projection{}).has_value()); input.push_back(42); diff --git a/toolsrc/src/vcpkg/base/files.cpp b/toolsrc/src/vcpkg/base/files.cpp index 91f22bd2f..42efd473f 100644 --- a/toolsrc/src/vcpkg/base/files.cpp +++ b/toolsrc/src/vcpkg/base/files.cpp @@ -498,8 +498,7 @@ namespace vcpkg::Files return output; } - virtual fs::path find_file_recursively_up(const fs::path& starting_dir, - const std::string& filename) const override + virtual fs::path find_file_recursively_up(const fs::path& starting_dir, const fs::path& filename) const override { fs::path current_dir = starting_dir; if (exists(VCPKG_LINE_INFO, current_dir / filename)) @@ -1154,7 +1153,8 @@ namespace vcpkg::Files { #if VCPKG_USE_STD_FILESYSTEM return lhs / rhs; -#else // ^^^ VCPKG_USE_STD_FILESYSTEM // !VCPKG_USE_STD_FILESYSTEM vvv +#else // ^^^ std::filesystem // std::experimental::filesystem vvv +#if !defined(_WIN32) if (rhs.is_absolute()) { return rhs; @@ -1163,6 +1163,37 @@ namespace vcpkg::Files { return lhs / rhs; } -#endif +#else // ^^^ unix // windows vvv + auto rhs_root_directory = rhs.root_directory(); + auto rhs_root_name = rhs.root_name(); + + if (rhs_root_directory.empty() && rhs_root_name.empty()) + { + return lhs / rhs; + } + else if (rhs_root_directory.empty()) + { + // !rhs_root_name.empty() + if (rhs_root_name == lhs.root_name()) + { + return lhs / rhs.relative_path(); + } + else + { + return rhs; + } + } + else if (rhs_root_name.empty()) + { + // !rhs_root_directory.empty() + return lhs.root_name() / rhs; + } + else + { + // rhs.absolute() + return rhs; + } +#endif // ^^^ windows +#endif // ^^^ std::experimental::filesystem } } diff --git a/toolsrc/src/vcpkg/base/json.cpp b/toolsrc/src/vcpkg/base/json.cpp index 62f50f9d0..58121ac8e 100644 --- a/toolsrc/src/vcpkg/base/json.cpp +++ b/toolsrc/src/vcpkg/base/json.cpp @@ -5,6 +5,8 @@ #include <inttypes.h> +#include <regex> + namespace vcpkg::Json { using VK = ValueKind; @@ -986,6 +988,33 @@ namespace vcpkg::Json }; } + bool IdentifierDeserializer::is_ident(StringView sv) + { + static const std::regex BASIC_IDENTIFIER = std::regex(R"([a-z0-9]+(-[a-z0-9]+)*)"); + + // we only check for lowercase in RESERVED since we already remove all + // strings with uppercase letters from the basic check + static const std::regex RESERVED = std::regex(R"(prn|aux|nul|con|(lpt|com)[1-9]|core|default)"); + + // back-compat + if (sv == "all_modules") + { + return true; + } + + if (!std::regex_match(sv.begin(), sv.end(), BASIC_IDENTIFIER)) + { + return false; // we're not even in the shape of an identifier + } + + if (std::regex_match(sv.begin(), sv.end(), RESERVED)) + { + return false; // we're a reserved identifier + } + + return true; + } + ExpectedT<std::pair<Value, JsonStyle>, std::unique_ptr<Parse::IParseError>> parse_file(const Files::Filesystem& fs, const fs::path& path, std::error_code& ec) noexcept @@ -1002,6 +1031,26 @@ namespace vcpkg::Json } } + std::pair<Value, JsonStyle> parse_file(vcpkg::LineInfo linfo, + const Files::Filesystem& fs, + const fs::path& path) noexcept + { + std::error_code ec; + auto ret = parse_file(fs, path, ec); + if (ec) + { + System::print2(System::Color::error, "Failed to read ", fs::u8string(path), ": ", ec.message(), "\n"); + Checks::exit_fail(linfo); + } + else if (!ret) + { + System::print2(System::Color::error, "Failed to parse ", fs::u8string(path), ":\n"); + System::print2(ret.error()->format()); + Checks::exit_fail(linfo); + } + return ret.value_or_exit(linfo); + } + ExpectedT<std::pair<Value, JsonStyle>, std::unique_ptr<Parse::IParseError>> parse(StringView json, const fs::path& filepath) noexcept { diff --git a/toolsrc/src/vcpkg/commands.ci.cpp b/toolsrc/src/vcpkg/commands.ci.cpp index e9577e4ad..ee7b4d4a0 100644 --- a/toolsrc/src/vcpkg/commands.ci.cpp +++ b/toolsrc/src/vcpkg/commands.ci.cpp @@ -14,6 +14,7 @@ #include <vcpkg/input.h> #include <vcpkg/install.h> #include <vcpkg/packagespec.h> +#include <vcpkg/paragraphs.h> #include <vcpkg/platform-expression.h> #include <vcpkg/vcpkgcmdarguments.h> #include <vcpkg/vcpkglib.h> @@ -457,9 +458,7 @@ namespace vcpkg::Commands::CI XunitTestResults xunitTestResults; std::vector<std::string> all_ports = - Util::fmap(provider.load_all_control_files(), [](const SourceControlFileLocation*& scfl) -> std::string { - return scfl->source_control_file.get()->core_paragraph->name; - }); + Util::fmap(provider.load_all_control_files(), Paragraphs::get_name_of_control_file); std::vector<TripletAndSummary> results; auto timer = Chrono::ElapsedTimer::create_started(); for (Triplet triplet : triplets) diff --git a/toolsrc/src/vcpkg/commands.edit.cpp b/toolsrc/src/vcpkg/commands.edit.cpp index 13091352e..7ee74eae0 100644 --- a/toolsrc/src/vcpkg/commands.edit.cpp +++ b/toolsrc/src/vcpkg/commands.edit.cpp @@ -85,10 +85,9 @@ namespace vcpkg::Commands::Edit static std::vector<std::string> valid_arguments(const VcpkgPaths& paths) { - auto sources_and_errors = Paragraphs::try_load_all_ports(paths.get_filesystem(), paths.ports); + auto sources_and_errors = Paragraphs::try_load_all_registry_ports(paths); - return Util::fmap(sources_and_errors.paragraphs, - [](auto&& pgh) -> std::string { return pgh->core_paragraph->name; }); + return Util::fmap(sources_and_errors.paragraphs, Paragraphs::get_name_of_control_file); } static constexpr std::array<CommandSwitch, 2> EDIT_SWITCHES = { diff --git a/toolsrc/src/vcpkg/commands.format-manifest.cpp b/toolsrc/src/vcpkg/commands.format-manifest.cpp index 72cac6958..24beae0d8 100644 --- a/toolsrc/src/vcpkg/commands.format-manifest.cpp +++ b/toolsrc/src/vcpkg/commands.format-manifest.cpp @@ -8,6 +8,7 @@ #include <vcpkg/portfileprovider.h> #include <vcpkg/sourceparagraph.h> #include <vcpkg/vcpkgcmdarguments.h> +#include <vcpkg/vcpkgpaths.h> namespace { @@ -41,7 +42,9 @@ namespace return nullopt; } - auto scf = SourceControlFile::parse_manifest_file(manifest_path, parsed_json.object()); + auto parsed_json_obj = parsed_json.object(); + + auto scf = SourceControlFile::parse_manifest_file(manifest_path, parsed_json_obj); if (!scf.has_value()) { System::printf(System::Color::error, "Failed to parse manifest file: %s\n", path_string); diff --git a/toolsrc/src/vcpkg/commands.portsdiff.cpp b/toolsrc/src/vcpkg/commands.portsdiff.cpp index a25d441c8..392ca70dd 100644 --- a/toolsrc/src/vcpkg/commands.portsdiff.cpp +++ b/toolsrc/src/vcpkg/commands.portsdiff.cpp @@ -100,13 +100,13 @@ namespace vcpkg::Commands::PortsDiff System::cmd_execute_and_capture_output(cmd, System::get_clean_environment()); System::cmd_execute_and_capture_output(Strings::format(R"("%s" reset)", fs::u8string(git_exe)), System::get_clean_environment()); - const auto all_ports = - Paragraphs::load_all_ports(paths.get_filesystem(), temp_checkout_path / ports_dir_name_as_string); + const auto ports_at_commit = + Paragraphs::load_overlay_ports(paths, temp_checkout_path / ports_dir_name_as_string); std::map<std::string, VersionT> names_and_versions; - for (auto&& port : all_ports) + for (auto&& port : ports_at_commit) { - const auto& core_pgh = *port->core_paragraph; - names_and_versions.emplace(port->core_paragraph->name, VersionT(core_pgh.version, core_pgh.port_version)); + const auto& core_pgh = *port.source_control_file->core_paragraph; + names_and_versions.emplace(core_pgh.name, VersionT(core_pgh.version, core_pgh.port_version)); } fs.remove_all(temp_checkout_path, VCPKG_LINE_INFO); return names_and_versions; diff --git a/toolsrc/src/vcpkg/configuration.cpp b/toolsrc/src/vcpkg/configuration.cpp new file mode 100644 index 000000000..adc1f814d --- /dev/null +++ b/toolsrc/src/vcpkg/configuration.cpp @@ -0,0 +1,66 @@ +#include <vcpkg/base/system.print.h> + +#include <vcpkg/configuration.h> +#include <vcpkg/vcpkgcmdarguments.h> + +namespace vcpkg +{ + Optional<Configuration> ConfigurationDeserializer::visit_object(Json::Reader& r, + StringView, + const Json::Object& obj) + { + RegistrySet registries; + + bool registries_feature_flags_warning = false; + + { + std::unique_ptr<RegistryImpl> default_registry; + if (r.optional_object_field(obj, DEFAULT_REGISTRY, default_registry, RegistryImplDeserializer{})) + { + if (!registries_enabled) + { + registries_feature_flags_warning = true; + } + else + { + registries.set_default_registry(std::move(default_registry)); + } + } + } + + std::vector<Registry> regs; + r.optional_object_field( + obj, + REGISTRIES, + regs, + Json::ArrayDeserializer<RegistryDeserializer>{"an array of registries", Json::AllowEmpty::Yes}); + + if (!regs.empty() && !registries_enabled) + { + registries_feature_flags_warning = true; + regs.clear(); + } + + for (Registry& reg : regs) + { + registries.add_registry(std::move(reg)); + } + + if (registries_feature_flags_warning && !print_json) + { + 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); + } + + return Configuration{std::move(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 08c160f89..cc0b6b931 100644 --- a/toolsrc/src/vcpkg/install.cpp +++ b/toolsrc/src/vcpkg/install.cpp @@ -551,10 +551,9 @@ namespace vcpkg::Install std::vector<std::string> get_all_port_names(const VcpkgPaths& paths) { - auto sources_and_errors = Paragraphs::try_load_all_ports(paths.get_filesystem(), paths.ports); + auto sources_and_errors = Paragraphs::try_load_all_registry_ports(paths); - return Util::fmap(sources_and_errors.paragraphs, - [](auto&& pgh) -> std::string { return pgh->core_paragraph->name; }); + return Util::fmap(sources_and_errors.paragraphs, Paragraphs::get_name_of_control_file); } const CommandStructure COMMAND_STRUCTURE = { @@ -781,7 +780,7 @@ namespace vcpkg::Install auto var_provider_storage = CMakeVars::make_triplet_cmake_var_provider(paths); auto& var_provider = *var_provider_storage; - if (paths.manifest_mode_enabled()) + if (auto manifest = paths.get_manifest().get()) { Optional<fs::path> pkgsconfig; auto it_pkgsconfig = options.settings.find(OPTION_WRITE_PACKAGES_CONFIG); @@ -790,18 +789,12 @@ namespace vcpkg::Install pkgsconfig = fs::u8path(it_pkgsconfig->second); } - std::error_code ec; - auto manifest_path = paths.manifest_root_dir / fs::u8path("vcpkg.json"); - auto maybe_manifest_scf = Paragraphs::try_load_manifest(fs, "manifest", manifest_path, ec); - if (ec) - { - Checks::exit_with_message( - VCPKG_LINE_INFO, "Failed to read manifest %s: %s", fs::u8string(manifest_path), ec.message()); - } - else if (!maybe_manifest_scf) + auto maybe_manifest_scf = SourceControlFile::parse_manifest_file("manifest", *manifest); + if (!maybe_manifest_scf) { print_error_message(maybe_manifest_scf.error()); - Checks::exit_with_message(VCPKG_LINE_INFO, "Failed to read manifest %s.", fs::u8string(manifest_path)); + Checks::exit_with_message( + VCPKG_LINE_INFO, "Failed to parse manifest %s/vcpkg.json.", fs::u8string(paths.manifest_root_dir)); } auto& manifest_scf = *maybe_manifest_scf.value_or_exit(VCPKG_LINE_INFO); diff --git a/toolsrc/src/vcpkg/paragraphs.cpp b/toolsrc/src/vcpkg/paragraphs.cpp index 37f919b63..19c1d266d 100644 --- a/toolsrc/src/vcpkg/paragraphs.cpp +++ b/toolsrc/src/vcpkg/paragraphs.cpp @@ -5,8 +5,10 @@ #include <vcpkg/base/util.h> #include <vcpkg/binaryparagraph.h> +#include <vcpkg/configuration.h> #include <vcpkg/paragraphparser.h> #include <vcpkg/paragraphs.h> +#include <vcpkg/registries.h> using namespace vcpkg::Parse; using namespace vcpkg; @@ -259,10 +261,10 @@ namespace vcpkg::Paragraphs return fs.exists(path / fs::u8path("CONTROL")) || fs.exists(path / fs::u8path("vcpkg.json")); } - ParseExpected<SourceControlFile> try_load_manifest(const Files::Filesystem& fs, - const std::string& port_name, - const fs::path& path_to_manifest, - std::error_code& ec) + static ParseExpected<SourceControlFile> try_load_manifest(const Files::Filesystem& fs, + const std::string& port_name, + const fs::path& path_to_manifest, + std::error_code& ec) { auto error_info = std::make_unique<ParseControlErrorInfo>(); auto res = Json::parse_file(fs, path_to_manifest, ec); @@ -343,34 +345,84 @@ namespace vcpkg::Paragraphs return pghs.error(); } - LoadResults try_load_all_ports(const Files::Filesystem& fs, const fs::path& ports_dir) + static void load_port_names_from_root(std::vector<std::string>& ports, + const VcpkgPaths& paths, + const fs::path& registry_root) { - LoadResults ret; - auto port_dirs = fs.get_files_non_recursive(ports_dir); + const auto& fs = paths.get_filesystem(); + auto port_dirs = fs.get_files_non_recursive(registry_root); Util::sort(port_dirs); + + // TODO: search in `b-` for ports starting with `b` Util::erase_remove_if(port_dirs, [&](auto&& port_dir_entry) { return fs.is_regular_file(port_dir_entry) && port_dir_entry.filename() == ".DS_Store"; }); for (auto&& path : port_dirs) { - auto maybe_spgh = try_load_port(fs, path); + ports.push_back(fs::u8string(path.filename())); + } + } + + LoadResults try_load_all_registry_ports(const VcpkgPaths& paths) + { + LoadResults ret; + const auto& fs = paths.get_filesystem(); + + std::vector<std::string> ports; + + const auto& registries = paths.get_configuration().registry_set; + + for (const auto& registry : registries.registries()) + { + load_port_names_from_root(ports, paths, registry.implementation().get_registry_root(paths)); + } + if (auto registry = registries.default_registry()) + { + load_port_names_from_root(ports, paths, registry->get_registry_root(paths)); + } + + Util::sort_unique_erase(ports); + + for (const auto& port_name : ports) + { + auto impl = registries.registry_for_port(port_name); + if (!impl) + { + // this is a port for which no registry is set + // this can happen when there's no default registry, + // and a registry has a port definition which it doesn't own the name of. + continue; + } + + auto root = impl->get_registry_root(paths); + + auto port_path = root / fs::u8path(port_name); + + if (!fs.exists(port_path)) + { + // the registry that owns the name of this port does not actually contain the port + // this can happen if R1 contains the port definition for <abc>, but doesn't + // declare it owns <abc>. + continue; + } + + auto maybe_spgh = try_load_port(fs, port_path); if (const auto spgh = maybe_spgh.get()) { - ret.paragraphs.emplace_back(std::move(*spgh)); + ret.paragraphs.emplace_back(std::move(*spgh), std::move(port_path)); } else { ret.errors.emplace_back(std::move(maybe_spgh).error()); } } + return ret; } - std::vector<std::unique_ptr<SourceControlFile>> load_all_ports(const Files::Filesystem& fs, - const fs::path& ports_dir) + static void load_results_print_error(const LoadResults& results) { - auto results = try_load_all_ports(fs, ports_dir); if (!results.errors.empty()) { if (Debug::g_debugging) @@ -388,6 +440,39 @@ namespace vcpkg::Paragraphs "Use '--debug' to get more information about the parse failures.\n\n"); } } + } + + std::vector<SourceControlFileLocation> load_all_registry_ports(const VcpkgPaths& paths) + { + auto results = try_load_all_registry_ports(paths); + load_results_print_error(results); return std::move(results.paragraphs); } + + std::vector<SourceControlFileLocation> load_overlay_ports(const VcpkgPaths& paths, const fs::path& directory) + { + LoadResults ret; + + std::vector<std::string> port_names; + load_port_names_from_root(port_names, paths, directory); + + const auto& fs = paths.get_filesystem(); + + for (const auto& name : port_names) + { + auto path = directory / fs::u8path(name); + auto maybe_spgh = try_load_port(fs, path); + if (const auto spgh = maybe_spgh.get()) + { + ret.paragraphs.emplace_back(std::move(*spgh), std::move(path)); + } + else + { + ret.errors.emplace_back(std::move(maybe_spgh).error()); + } + } + + load_results_print_error(ret); + return std::move(ret.paragraphs); + } } diff --git a/toolsrc/src/vcpkg/portfileprovider.cpp b/toolsrc/src/vcpkg/portfileprovider.cpp index 0e9b355de..0e065eff7 100644 --- a/toolsrc/src/vcpkg/portfileprovider.cpp +++ b/toolsrc/src/vcpkg/portfileprovider.cpp @@ -1,7 +1,9 @@ #include <vcpkg/base/system.debug.h> +#include <vcpkg/configuration.h> #include <vcpkg/paragraphs.h> #include <vcpkg/portfileprovider.h> +#include <vcpkg/registries.h> #include <vcpkg/sourceparagraph.h> namespace vcpkg::PortFileProvider @@ -23,12 +25,12 @@ namespace vcpkg::PortFileProvider return Util::fmap(ports, [](auto&& kvpair) -> const SourceControlFileLocation* { return &kvpair.second; }); } - PathsPortFileProvider::PathsPortFileProvider(const vcpkg::VcpkgPaths& paths, - const std::vector<std::string>& ports_dirs_paths) - : filesystem(paths.get_filesystem()) + PathsPortFileProvider::PathsPortFileProvider(const vcpkg::VcpkgPaths& paths_, + const std::vector<std::string>& overlay_ports_) + : paths(paths_) { auto& fs = paths.get_filesystem(); - for (auto&& overlay_path : ports_dirs_paths) + for (auto&& overlay_path : overlay_ports_) { if (!overlay_path.empty()) { @@ -45,17 +47,16 @@ namespace vcpkg::PortFileProvider Debug::print("Using overlay: ", fs::u8string(overlay), "\n"); Checks::check_exit( - VCPKG_LINE_INFO, filesystem.exists(overlay), "Error: Path \"%s\" does not exist", overlay.string()); + VCPKG_LINE_INFO, fs.exists(overlay), "Error: Path \"%s\" does not exist", fs::u8string(overlay)); Checks::check_exit(VCPKG_LINE_INFO, fs::is_directory(fs.status(VCPKG_LINE_INFO, overlay)), "Error: Path \"%s\" must be a directory", overlay.string()); - ports_dirs.emplace_back(overlay); + overlay_ports.emplace_back(overlay); } } - ports_dirs.emplace_back(paths.ports); } ExpectedS<const SourceControlFileLocation&> PathsPortFileProvider::get_control_file(const std::string& spec) const @@ -66,12 +67,14 @@ namespace vcpkg::PortFileProvider return cache_it->second; } - for (auto&& ports_dir : ports_dirs) + const auto& fs = paths.get_filesystem(); + + for (auto&& ports_dir : overlay_ports) { // Try loading individual port - if (Paragraphs::is_port_directory(filesystem, ports_dir)) + if (Paragraphs::is_port_directory(fs, ports_dir)) { - auto maybe_scf = Paragraphs::try_load_port(filesystem, ports_dir); + auto maybe_scf = Paragraphs::try_load_port(fs, ports_dir); if (auto scf = maybe_scf.get()) { if (scf->get()->core_paragraph->name == spec) @@ -92,22 +95,54 @@ namespace vcpkg::PortFileProvider continue; } - auto ports_spec = ports_dir / spec; - if (Paragraphs::is_port_directory(filesystem, ports_spec)) + auto ports_spec = ports_dir / fs::u8path(spec); + if (Paragraphs::is_port_directory(fs, ports_spec)) { - auto found_scf = Paragraphs::try_load_port(filesystem, ports_spec); + auto found_scf = Paragraphs::try_load_port(fs, ports_spec); + if (auto scf = found_scf.get()) + { + if (scf->get()->core_paragraph->name == spec) + { + auto it = cache.emplace(std::piecewise_construct, + std::forward_as_tuple(std::move(spec)), + std::forward_as_tuple(std::move(*scf), std::move(ports_spec))); + return it.first->second; + } + 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); + } + else + { + vcpkg::print_error_message(found_scf.error()); + Checks::exit_with_message( + VCPKG_LINE_INFO, "Error: Failed to load port %s from %s", spec, fs::u8string(ports_dir)); + } + } + } + + if (auto registry = paths.get_configuration().registry_set.registry_for_port(spec)) + { + auto registry_root = registry->get_registry_root(paths); + auto port_directory = registry_root / fs::u8path(spec); + + if (fs.exists(port_directory)) + { + auto found_scf = Paragraphs::try_load_port(fs, port_directory); if (auto scf = found_scf.get()) { if (scf->get()->core_paragraph->name == spec) { auto it = cache.emplace(std::piecewise_construct, std::forward_as_tuple(spec), - std::forward_as_tuple(std::move(*scf), ports_dir / spec)); + std::forward_as_tuple(std::move(*scf), std::move(port_directory))); return it.first->second; } Checks::exit_with_message(VCPKG_LINE_INFO, "Error: Failed to load port from %s: names did not match: '%s' != '%s'", - fs::u8string(ports_dir / spec), + fs::u8string(port_directory), spec, scf->get()->core_paragraph->name); } @@ -115,7 +150,7 @@ namespace vcpkg::PortFileProvider { vcpkg::print_error_message(found_scf.error()); Checks::exit_with_message( - VCPKG_LINE_INFO, "Error: Failed to load port from %s", spec, fs::u8string(ports_dir)); + VCPKG_LINE_INFO, "Error: Failed to load port %s from %s", spec, fs::u8string(port_directory)); } } } @@ -129,20 +164,19 @@ namespace vcpkg::PortFileProvider cache.clear(); std::vector<const SourceControlFileLocation*> ret; - for (auto&& ports_dir : ports_dirs) + for (const fs::path& ports_dir : overlay_ports) { // Try loading individual port - if (Paragraphs::is_port_directory(filesystem, ports_dir)) + if (Paragraphs::is_port_directory(paths.get_filesystem(), ports_dir)) { - auto maybe_scf = Paragraphs::try_load_port(filesystem, ports_dir); + auto maybe_scf = Paragraphs::try_load_port(paths.get_filesystem(), ports_dir); if (auto scf = maybe_scf.get()) { auto port_name = scf->get()->core_paragraph->name; if (cache.find(port_name) == cache.end()) { - auto it = cache.emplace(std::piecewise_construct, - std::forward_as_tuple(port_name), - std::forward_as_tuple(std::move(*scf), ports_dir)); + auto scfl = SourceControlFileLocation{std::move(*scf), ports_dir}; + auto it = cache.emplace(std::move(port_name), std::move(scfl)); ret.emplace_back(&it.first->second); } } @@ -156,19 +190,29 @@ namespace vcpkg::PortFileProvider } // Try loading all ports inside ports_dir - auto found_scf = Paragraphs::load_all_ports(filesystem, ports_dir); - for (auto&& scf : found_scf) + auto found_scfls = Paragraphs::load_overlay_ports(paths, ports_dir); + for (auto&& scfl : found_scfls) { - auto port_name = scf->core_paragraph->name; + auto port_name = scfl.source_control_file->core_paragraph->name; if (cache.find(port_name) == cache.end()) { - auto it = cache.emplace(std::piecewise_construct, - std::forward_as_tuple(port_name), - std::forward_as_tuple(std::move(scf), ports_dir / port_name)); + auto it = cache.emplace(std::move(port_name), std::move(scfl)); ret.emplace_back(&it.first->second); } } } + + auto all_ports = Paragraphs::load_all_registry_ports(paths); + for (auto&& scfl : all_ports) + { + auto port_name = scfl.source_control_file->core_paragraph->name; + if (cache.find(port_name) == cache.end()) + { + auto it = cache.emplace(port_name, std::move(scfl)); + ret.emplace_back(&it.first->second); + } + } + return ret; } } diff --git a/toolsrc/src/vcpkg/registries.cpp b/toolsrc/src/vcpkg/registries.cpp new file mode 100644 index 000000000..5df38e6ef --- /dev/null +++ b/toolsrc/src/vcpkg/registries.cpp @@ -0,0 +1,128 @@ +#include <vcpkg/base/json.h> + +#include <vcpkg/registries.h> + +namespace +{ + struct BuiltinRegistry final : vcpkg::RegistryImpl + { + virtual fs::path get_registry_root(const vcpkg::VcpkgPaths& paths) const override { return paths.ports; } + }; + + struct DirectoryRegistry final : vcpkg::RegistryImpl + { + virtual fs::path get_registry_root(const vcpkg::VcpkgPaths& paths) const override + { + return vcpkg::Files::combine(paths.config_root_dir, path); + } + + DirectoryRegistry(fs::path&& path_) : path(path_) { } + + fs::path path; + }; +} + +namespace vcpkg +{ + std::unique_ptr<RegistryImpl> Registry::builtin_registry() { return std::make_unique<BuiltinRegistry>(); } + + Registry::Registry(std::vector<std::string>&& packages, std::unique_ptr<RegistryImpl>&& impl) + : packages_(std::move(packages)), implementation_(std::move(impl)) + { + Checks::check_exit(VCPKG_LINE_INFO, implementation_ != nullptr); + } + + StringView RegistryImplDeserializer::type_name() const { return "a registry"; } + + Span<const StringView> RegistryImplDeserializer::valid_fields() const + { + static const StringView t[] = {KIND, PATH}; + return t; + } + + Optional<std::unique_ptr<RegistryImpl>> RegistryImplDeserializer::visit_null(Json::Reader&, StringView) + { + return nullptr; + } + + Optional<std::unique_ptr<RegistryImpl>> RegistryImplDeserializer::visit_object(Json::Reader& r, + StringView, + const Json::Object& obj) + { + std::string kind; + r.required_object_field( + type_name(), obj, KIND, kind, Json::StringDeserializer{"a registry implementation kind"}); + + if (kind == KIND_BUILTIN) + { + if (obj.contains(PATH)) + { + r.error().add_extra_fields(type_name().to_string(), {PATH}); + } + return static_cast<std::unique_ptr<RegistryImpl>>(std::make_unique<BuiltinRegistry>()); + } + else if (kind == KIND_DIRECTORY) + { + fs::path path; + r.required_object_field(type_name(), obj, PATH, path, Json::PathDeserializer{}); + + return static_cast<std::unique_ptr<RegistryImpl>>(std::make_unique<DirectoryRegistry>(std::move(path))); + } + else + { + return nullopt; + } + } + + StringView RegistryDeserializer::type_name() const { return "a registry"; } + + Span<const StringView> RegistryDeserializer::valid_fields() const + { + static const StringView t[] = { + RegistryImplDeserializer::KIND, + RegistryImplDeserializer::PATH, + PACKAGES, + }; + return t; + } + + Optional<Registry> RegistryDeserializer::visit_object(Json::Reader& r, StringView key, const Json::Object& obj) + { + auto impl = RegistryImplDeserializer{}.visit_object(r, key, obj); + + if (!impl.has_value()) + { + return nullopt; + } + + std::vector<std::string> packages; + r.required_object_field( + type_name(), + obj, + PACKAGES, + packages, + Json::ArrayDeserializer<Json::PackageNameDeserializer>{"an array of registries", Json::AllowEmpty::Yes}); + + return Registry{std::move(packages), std::move(impl).value_or_exit(VCPKG_LINE_INFO)}; + } + + RegistrySet::RegistrySet() : default_registry_(Registry::builtin_registry()), registries_() { } + + const RegistryImpl* RegistrySet::registry_for_port(StringView name) const + { + for (const auto& registry : registries()) + { + const auto& packages = registry.packages(); + if (std::find(packages.begin(), packages.end(), name) != packages.end()) + { + return ®istry.implementation(); + } + } + return default_registry(); + } + + 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::nullptr_t) { default_registry_.reset(); } +} diff --git a/toolsrc/src/vcpkg/sourceparagraph.cpp b/toolsrc/src/vcpkg/sourceparagraph.cpp index 84d9eec78..a00fd7579 100644 --- a/toolsrc/src/vcpkg/sourceparagraph.cpp +++ b/toolsrc/src/vcpkg/sourceparagraph.cpp @@ -80,24 +80,6 @@ namespace vcpkg static const std::string SUPPORTS = "Supports"; } - namespace ManifestFields - { - constexpr static StringLiteral NAME = "name"; - constexpr static StringLiteral VERSION = "version-string"; - - constexpr static StringLiteral PORT_VERSION = "port-version"; - constexpr static StringLiteral MAINTAINERS = "maintainers"; - constexpr static StringLiteral DESCRIPTION = "description"; - constexpr static StringLiteral HOMEPAGE = "homepage"; - constexpr static StringLiteral DOCUMENTATION = "documentation"; - constexpr static StringLiteral LICENSE = "license"; - constexpr static StringLiteral DEPENDENCIES = "dependencies"; - constexpr static StringLiteral DEV_DEPENDENCIES = "dev-dependencies"; - constexpr static StringLiteral FEATURES = "features"; - constexpr static StringLiteral DEFAULT_FEATURES = "default-features"; - constexpr static StringLiteral SUPPORTS = "supports"; - } - static Span<const StringView> get_list_of_valid_fields() { static const StringView valid_fields[] = { @@ -116,111 +98,7 @@ namespace vcpkg return valid_fields; } - static Span<const StringView> get_list_of_manifest_fields() - { - constexpr static StringView valid_fields[] = { - ManifestFields::NAME, - ManifestFields::VERSION, - - ManifestFields::PORT_VERSION, - ManifestFields::MAINTAINERS, - ManifestFields::DESCRIPTION, - ManifestFields::HOMEPAGE, - ManifestFields::DOCUMENTATION, - ManifestFields::LICENSE, - ManifestFields::DEPENDENCIES, - ManifestFields::DEV_DEPENDENCIES, - ManifestFields::FEATURES, - ManifestFields::DEFAULT_FEATURES, - ManifestFields::SUPPORTS, - }; - - return valid_fields; - } - - void print_error_message(Span<const std::unique_ptr<Parse::ParseControlErrorInfo>> error_info_list) - { - Checks::check_exit(VCPKG_LINE_INFO, error_info_list.size() > 0); - - for (auto&& error_info : error_info_list) - { - Checks::check_exit(VCPKG_LINE_INFO, error_info != nullptr); - if (!error_info->error.empty()) - { - System::print2( - System::Color::error, "Error: while loading ", error_info->name, ":\n", error_info->error, '\n'); - } - } - - bool have_remaining_fields = false; - for (auto&& error_info : error_info_list) - { - if (!error_info->extra_fields.empty()) - { - System::print2(System::Color::error, - "Error: There are invalid fields in the control or manifest file of ", - error_info->name, - '\n'); - System::print2("The following fields were not expected:\n"); - - for (const auto& pr : error_info->extra_fields) - { - System::print2(" In ", pr.first, ": ", Strings::join(", ", pr.second), "\n"); - } - have_remaining_fields = true; - } - } - - if (have_remaining_fields) - { - System::print2("This is the list of valid fields for CONTROL files (case-sensitive): \n\n ", - Strings::join("\n ", get_list_of_valid_fields()), - "\n\n"); - System::print2("And this is the list of valid fields for manifest files: \n\n ", - Strings::join("\n ", get_list_of_manifest_fields()), - "\n\n"); -#if defined(_WIN32) - auto bootstrap = ".\\bootstrap-vcpkg.bat"; -#else - auto bootstrap = "./bootstrap-vcpkg.sh"; -#endif - System::print2("You may need to update the vcpkg binary; try running %s to update.\n\n", bootstrap); - } - - for (auto&& error_info : error_info_list) - { - if (!error_info->missing_fields.empty()) - { - System::print2(System::Color::error, - "Error: There are missing fields in the control file of ", - error_info->name, - '\n'); - System::print2("The following fields were missing:\n"); - for (const auto& pr : error_info->missing_fields) - { - System::print2(" In ", pr.first, ": ", Strings::join(", ", pr.second), "\n"); - } - } - } - - for (auto&& error_info : error_info_list) - { - if (!error_info->expected_types.empty()) - { - System::print2(System::Color::error, - "Error: There are invalid field types in the CONTROL or manifest file of ", - error_info->name, - '\n'); - System::print2("The following fields had the wrong types:\n\n"); - - for (const auto& pr : error_info->expected_types) - { - System::printf(" %s was expected to be %s\n", pr.first, pr.second); - } - System::print2("\n"); - } - } - } + void print_error_message(Span<const std::unique_ptr<Parse::ParseControlErrorInfo>> error_info_list); std::string Type::to_string(const Type& t) { @@ -479,202 +357,136 @@ namespace vcpkg return control_file; } - static std::vector<std::string> invalid_json_fields(const Json::Object& obj, - Span<const StringView> known_fields) noexcept + struct PlatformExprDeserializer : Json::IDeserializer<PlatformExpression::Expr> { - const auto field_is_unknown = [known_fields](StringView sv) { - // allow directives - if (sv.size() != 0 && *sv.begin() == '$') - { - return false; - } - return std::find(known_fields.begin(), known_fields.end(), sv) == known_fields.end(); - }; + virtual StringView type_name() const override { return "a platform expression"; } - std::vector<std::string> res; - for (const auto& kv : obj) + virtual Optional<PlatformExpression::Expr> visit_string(Json::Reader&, StringView, StringView sv) override { - if (field_is_unknown(kv.first)) + auto opt = + PlatformExpression::parse_platform_expression(sv, PlatformExpression::MultipleBinaryOperators::Deny); + if (auto res = opt.get()) { - res.push_back(kv.first.to_string()); + return std::move(*res); } - } - - return res; - } - - struct StringField : Json::VisitorCrtpBase<StringField> - { - using type = std::string; - StringView type_name() { return type_name_; } - - Optional<std::string> visit_string(Json::Reader&, StringView, StringView sv) { return sv.to_string(); } - - explicit StringField(StringView type_name_) : type_name_(type_name_) { } - - private: - StringView type_name_; - }; - - struct NaturalNumberField : Json::VisitorCrtpBase<NaturalNumberField> - { - using type = int; - StringView type_name() { return "a natural number"; } - - Optional<int> visit_integer(Json::Reader&, StringView, int64_t value) - { - if (value > std::numeric_limits<int>::max() || value < 0) + else { + Debug::print("Failed to parse platform expression: ", opt.error(), "\n"); return nullopt; } - return static_cast<int>(value); } }; - struct BooleanField : Json::VisitorCrtpBase<BooleanField> + struct DependencyDeserializer : Json::IDeserializer<Dependency> { - using type = bool; - StringView type_name() { return "a boolean"; } + virtual StringView type_name() const override { return "a dependency"; } - Optional<bool> visit_boolean(Json::Reader&, StringView, bool b) { return b; } - }; - - enum class AllowEmpty : bool - { - No, - Yes, - }; - - template<class T> - struct ArrayField : Json::VisitorCrtpBase<ArrayField<T>> - { - using type = std::vector<typename T::type>; - - StringView type_name() { return type_name_; } + constexpr static StringLiteral NAME = "name"; + constexpr static StringLiteral FEATURES = "features"; + constexpr static StringLiteral DEFAULT_FEATURES = "default-features"; + constexpr static StringLiteral PLATFORM = "platform"; - ArrayField(StringView type_name_, AllowEmpty allow_empty, T&& t = {}) - : type_name_(type_name_), underlying_visitor_(static_cast<T&&>(t)), allow_empty_(allow_empty) + virtual Span<const StringView> valid_fields() const override { + static const StringView t[] = { + NAME, + FEATURES, + DEFAULT_FEATURES, + PLATFORM, + }; + + return t; } - Optional<type> visit_array(Json::Reader& r, StringView key, const Json::Array& arr) + virtual Optional<Dependency> visit_string(Json::Reader&, StringView, StringView sv) override { - if (allow_empty_ == AllowEmpty::No && arr.size() == 0) + if (!Json::PackageNameDeserializer::is_package_name(sv)) { return nullopt; } - return r.array_elements(arr, key, underlying_visitor_); - } - - private: - StringView type_name_; - T underlying_visitor_; - AllowEmpty allow_empty_; - }; - - struct ParagraphField : Json::VisitorCrtpBase<ParagraphField> - { - using type = std::vector<std::string>; - StringView type_name() { return "a string or array of strings"; } - - Optional<std::vector<std::string>> visit_string(Json::Reader&, StringView, StringView sv) - { - std::vector<std::string> out; - out.push_back(sv.to_string()); - return out; - } - Optional<std::vector<std::string>> visit_array(Json::Reader& r, StringView key, const Json::Array& arr) - { - return r.array_elements(arr, key, StringField{"a string"}); + Dependency dep; + dep.name = sv.to_string(); + return dep; } - }; - struct IdentifierField : Json::VisitorCrtpBase<IdentifierField> - { - using type = std::string; - StringView type_name() { return "an identifier"; } - - // [a-z0-9]+(-[a-z0-9]+)*, plus not any of {prn, aux, nul, con, lpt[1-9], com[1-9], core, default} - static bool is_ident(StringView sv) + virtual Optional<Dependency> visit_object(Json::Reader& r, StringView, const Json::Object& obj) override { - static const std::regex BASIC_IDENTIFIER = std::regex(R"([a-z0-9]+(-[a-z0-9]+)*)"); - - // we only check for lowercase in RESERVED since we already remove all - // strings with uppercase letters from the basic check - static const std::regex RESERVED = std::regex(R"(prn|aux|nul|con|(lpt|com)[1-9]|core|default)"); + Dependency dep; - // back-compat - if (sv == "all_modules") + for (const auto& el : obj) { - return true; + if (Strings::starts_with(el.first, "$")) + { + dep.extra_info.insert_or_replace(el.first.to_string(), el.second); + } } - if (!std::regex_match(sv.begin(), sv.end(), BASIC_IDENTIFIER)) - { - return false; // we're not even in the shape of an identifier - } + r.required_object_field(type_name(), obj, NAME, dep.name, Json::PackageNameDeserializer{}); + r.optional_object_field(obj, + FEATURES, + dep.features, + Json::ArrayDeserializer<Json::IdentifierDeserializer>{"an array of identifiers", + Json::AllowEmpty::Yes}); - if (std::regex_match(sv.begin(), sv.end(), RESERVED)) + bool default_features = true; + r.optional_object_field(obj, DEFAULT_FEATURES, default_features, Json::BooleanDeserializer{}); + if (!default_features) { - return false; // we're a reserved identifier + dep.features.push_back("core"); } - return true; - } + r.optional_object_field(obj, PLATFORM, dep.platform, PlatformExprDeserializer{}); - Optional<std::string> visit_string(Json::Reader&, StringView, StringView sv) - { - if (is_ident(sv)) - { - return sv.to_string(); - } - else - { - return nullopt; - } + return dep; } }; - struct PackageNameField : Json::VisitorCrtpBase<PackageNameField> + struct FeatureDeserializer : Json::IDeserializer<std::unique_ptr<FeatureParagraph>> { - using type = std::string; - StringView type_name() { return "a package name"; } + virtual StringView type_name() const override { return "a feature"; } + + constexpr static StringLiteral NAME = "name"; + constexpr static StringLiteral DESCRIPTION = "description"; + constexpr static StringLiteral DEPENDENCIES = "dependencies"; - static bool is_package_name(StringView sv) + virtual Span<const StringView> valid_fields() const override { - if (sv.size() == 0) - { - return false; - } + static const StringView t[] = {NAME, DESCRIPTION, DEPENDENCIES}; + return t; + } + + virtual Optional<std::unique_ptr<FeatureParagraph>> visit_object(Json::Reader& r, + StringView, + const Json::Object& obj) override + { + auto feature = std::make_unique<FeatureParagraph>(); - for (const auto& ident : Strings::split(sv, '.')) + for (const auto& el : obj) { - if (!IdentifierField::is_ident(ident)) + if (Strings::starts_with(el.first, "$")) { - return false; + feature->extra_info.insert_or_replace(el.first.to_string(), el.second); } } - return true; - } + r.required_object_field(type_name(), obj, NAME, feature->name, Json::IdentifierDeserializer{}); + r.required_object_field(type_name(), obj, DESCRIPTION, feature->description, Json::ParagraphDeserializer{}); + r.optional_object_field( + obj, + DEPENDENCIES, + feature->dependencies, + Json::ArrayDeserializer<DependencyDeserializer>{"an array of dependencies", Json::AllowEmpty::Yes}); - Optional<std::string> visit_string(Json::Reader&, StringView, StringView sv) - { - if (!is_package_name(sv)) - { - return nullopt; - } - return sv.to_string(); + return std::move(feature); } }; // We "parse" this so that we can add actual license parsing at some point in the future // without breaking anyone - struct LicenseExpressionField : Json::VisitorCrtpBase<LicenseExpressionField> + struct LicenseExpressionDeserializer : Json::IDeserializer<std::string> { - using type = std::string; - StringView type_name() { return "an SPDX license expression"; } + virtual StringView type_name() const override { return "an SPDX license expression"; } enum class Mode { @@ -691,12 +503,11 @@ namespace vcpkg constexpr static StringView VALID_LICENSES[] = #include "spdx-licenses.inc" ; - constexpr static StringView VALID_EXCEPTIONS[] = -#include "spdx-exceptions.inc" +#include "spdx-licenses.inc" ; - Optional<std::string> visit_string(Json::Reader&, StringView, StringView sv) + virtual Optional<std::string> visit_string(Json::Reader&, StringView, StringView sv) override { Mode mode = Mode::ExpectExpression; size_t open_parens = 0; @@ -811,129 +622,104 @@ namespace vcpkg } }; - struct PlatformExprField : Json::VisitorCrtpBase<PlatformExprField> - { - using type = PlatformExpression::Expr; - StringView type_name() { return "a platform expression"; } - - Optional<PlatformExpression::Expr> visit_string(Json::Reader&, StringView, StringView sv) - { - auto opt = - PlatformExpression::parse_platform_expression(sv, PlatformExpression::MultipleBinaryOperators::Deny); - if (auto res = opt.get()) - { - return std::move(*res); - } - else - { - Debug::print("Failed to parse platform expression: ", opt.error(), "\n"); - return nullopt; - } - } - }; - - struct DependencyField : Json::VisitorCrtpBase<DependencyField> + struct ManifestDeserializer : Json::IDeserializer<std::unique_ptr<SourceControlFile>> { - using type = Dependency; - StringView type_name() { return "a dependency"; } + virtual StringView type_name() const override { return "a manifest"; } constexpr static StringLiteral NAME = "name"; + constexpr static StringLiteral VERSION = "version-string"; + + constexpr static StringLiteral PORT_VERSION = "port-version"; + constexpr static StringLiteral MAINTAINERS = "maintainers"; + constexpr static StringLiteral DESCRIPTION = "description"; + constexpr static StringLiteral HOMEPAGE = "homepage"; + constexpr static StringLiteral DOCUMENTATION = "documentation"; + constexpr static StringLiteral LICENSE = "license"; + constexpr static StringLiteral DEPENDENCIES = "dependencies"; + constexpr static StringLiteral DEV_DEPENDENCIES = "dev-dependencies"; constexpr static StringLiteral FEATURES = "features"; constexpr static StringLiteral DEFAULT_FEATURES = "default-features"; - constexpr static StringLiteral PLATFORM = "platform"; - const static StringView KNOWN_FIELDS[4]; // not constexpr in MSVC 2015 + constexpr static StringLiteral SUPPORTS = "supports"; - Optional<Dependency> visit_string(Json::Reader&, StringView, StringView sv) + virtual Span<const StringView> valid_fields() const override { - if (!PackageNameField::is_package_name(sv)) - { - return nullopt; - } + static const StringView t[] = { + NAME, + VERSION, + + PORT_VERSION, + MAINTAINERS, + DESCRIPTION, + HOMEPAGE, + DOCUMENTATION, + LICENSE, + DEPENDENCIES, + DEV_DEPENDENCIES, + FEATURES, + DEFAULT_FEATURES, + SUPPORTS, + }; - Dependency dep; - dep.name = sv.to_string(); - return dep; + return t; } - Optional<Dependency> visit_object(Json::Reader& r, StringView, const Json::Object& obj) + virtual Optional<std::unique_ptr<SourceControlFile>> visit_object(Json::Reader& r, + StringView, + const Json::Object& obj) override { - { - auto extra_fields = invalid_json_fields(obj, KNOWN_FIELDS); - if (!extra_fields.empty()) - { - r.error().add_extra_fields(type_name().to_string(), std::move(extra_fields)); - } - } + auto control_file = std::make_unique<SourceControlFile>(); + control_file->core_paragraph = std::make_unique<SourceParagraph>(); - Dependency dep; + auto& spgh = control_file->core_paragraph; + spgh->type = Type{Type::PORT}; for (const auto& el : obj) { if (Strings::starts_with(el.first, "$")) { - dep.extra_info.insert_or_replace(el.first.to_string(), el.second); + spgh->extra_info.insert_or_replace(el.first.to_string(), el.second); } } - r.required_object_field(type_name(), obj, NAME, dep.name, PackageNameField{}); + constexpr static StringView type_name = "vcpkg.json"; + r.required_object_field(type_name, obj, NAME, spgh->name, Json::IdentifierDeserializer{}); + r.required_object_field(type_name, obj, VERSION, spgh->version, Json::StringDeserializer{"a version"}); + r.optional_object_field(obj, PORT_VERSION, spgh->port_version, Json::NaturalNumberDeserializer{}); + r.optional_object_field(obj, MAINTAINERS, spgh->maintainers, Json::ParagraphDeserializer{}); + r.optional_object_field(obj, DESCRIPTION, spgh->description, Json::ParagraphDeserializer{}); + r.optional_object_field(obj, HOMEPAGE, spgh->homepage, Json::StringDeserializer{"a url"}); + r.optional_object_field(obj, DOCUMENTATION, spgh->documentation, Json::StringDeserializer{"a url"}); + r.optional_object_field(obj, LICENSE, spgh->license, LicenseExpressionDeserializer{}); r.optional_object_field( - obj, FEATURES, dep.features, ArrayField<IdentifierField>{"an array of identifiers", AllowEmpty::Yes}); + obj, + DEPENDENCIES, + spgh->dependencies, + Json::ArrayDeserializer<DependencyDeserializer>{"an array of dependencies", Json::AllowEmpty::Yes}); - bool default_features = true; - r.optional_object_field(obj, DEFAULT_FEATURES, default_features, BooleanField{}); - if (!default_features) + if (obj.contains(DEV_DEPENDENCIES)) { - dep.features.push_back("core"); + System::print2(System::Color::error, "dev_dependencies are not yet supported"); + Checks::exit_fail(VCPKG_LINE_INFO); } - r.optional_object_field(obj, PLATFORM, dep.platform, PlatformExprField{}); + r.optional_object_field(obj, SUPPORTS, spgh->supports_expression, PlatformExprDeserializer{}); - return dep; - } - }; - const StringView DependencyField::KNOWN_FIELDS[] = {NAME, FEATURES, DEFAULT_FEATURES, PLATFORM}; - - struct FeatureField : Json::VisitorCrtpBase<FeatureField> - { - using type = std::unique_ptr<FeatureParagraph>; - StringView type_name() { return "a feature"; } - - constexpr static StringLiteral NAME = "name"; - constexpr static StringLiteral DESCRIPTION = "description"; - constexpr static StringLiteral DEPENDENCIES = "dependencies"; - const static StringView KNOWN_FIELDS[3]; // Not constexpr in MSVC 2015 - - Optional<std::unique_ptr<FeatureParagraph>> visit_object(Json::Reader& r, StringView, const Json::Object& obj) - { - { - auto extra_fields = invalid_json_fields(obj, KNOWN_FIELDS); - if (!extra_fields.empty()) - { - r.error().add_extra_fields(type_name().to_string(), std::move(extra_fields)); - } - } - - auto feature = std::make_unique<FeatureParagraph>(); - - for (const auto& el : obj) - { - if (Strings::starts_with(el.first, "$")) - { - feature->extra_info.insert_or_replace(el.first.to_string(), el.second); - } - } - - r.required_object_field(type_name(), obj, NAME, feature->name, IdentifierField{}); - r.required_object_field(type_name(), obj, DESCRIPTION, feature->description, ParagraphField{}); r.optional_object_field(obj, - DEPENDENCIES, - feature->dependencies, - ArrayField<DependencyField>{"an array of dependencies", AllowEmpty::Yes}); + DEFAULT_FEATURES, + spgh->default_features, + Json::ArrayDeserializer<Json::IdentifierDeserializer>{"an array of identifiers", + Json::AllowEmpty::Yes}); - return std::move(feature); + r.optional_object_field( + obj, + FEATURES, + control_file->feature_paragraphs, + Json::ArrayDeserializer<FeatureDeserializer>{"an array of feature definitions", Json::AllowEmpty::Yes}); + + canonicalize(*control_file); + return std::move(control_file); } }; - const StringView FeatureField::KNOWN_FIELDS[] = {NAME, DESCRIPTION, DEPENDENCIES}; Parse::ParseExpected<SourceControlFile> SourceControlFile::parse_manifest_file(const fs::path& path_to_manifest, const Json::Object& manifest) @@ -970,68 +756,105 @@ namespace vcpkg auto visit = Json::Reader{&err}; err.pcei.name = fs::u8string(path_to_manifest); + + auto res = visit.visit_value(manifest, "$", ManifestDeserializer{}); + + if (err.pcei.has_error()) { - auto extra_fields = invalid_json_fields(manifest, get_list_of_manifest_fields()); - if (!extra_fields.empty()) - { - err.pcei.extra_fields["manifest"] = std::move(extra_fields); - } + return std::make_unique<ParseControlErrorInfo>(std::move(err.pcei)); } + else if (auto p = res.get()) + { + return std::move(*p); + } + else + { + Checks::unreachable(VCPKG_LINE_INFO); + } + } - auto control_file = std::make_unique<SourceControlFile>(); - control_file->core_paragraph = std::make_unique<SourceParagraph>(); - - auto& spgh = control_file->core_paragraph; - spgh->type = Type{Type::PORT}; + void print_error_message(Span<const std::unique_ptr<Parse::ParseControlErrorInfo>> error_info_list) + { + Checks::check_exit(VCPKG_LINE_INFO, error_info_list.size() > 0); - for (const auto& el : manifest) + for (auto&& error_info : error_info_list) { - if (Strings::starts_with(el.first, "$")) + Checks::check_exit(VCPKG_LINE_INFO, error_info != nullptr); + if (!error_info->error.empty()) { - spgh->extra_info.insert_or_replace(el.first.to_string(), el.second); + System::print2( + System::Color::error, "Error: while loading ", error_info->name, ":\n", error_info->error, '\n'); } } - constexpr static StringView type_name = "vcpkg.json"; - visit.required_object_field(type_name, manifest, ManifestFields::NAME, spgh->name, IdentifierField{}); - visit.required_object_field( - type_name, manifest, ManifestFields::VERSION, spgh->version, StringField{"a version"}); - visit.optional_object_field(manifest, ManifestFields::PORT_VERSION, spgh->port_version, NaturalNumberField{}); - visit.optional_object_field(manifest, ManifestFields::MAINTAINERS, spgh->maintainers, ParagraphField{}); - visit.optional_object_field(manifest, ManifestFields::DESCRIPTION, spgh->description, ParagraphField{}); - visit.optional_object_field(manifest, ManifestFields::HOMEPAGE, spgh->homepage, StringField{"a url"}); - visit.optional_object_field(manifest, ManifestFields::DOCUMENTATION, spgh->documentation, StringField{"a url"}); - visit.optional_object_field(manifest, ManifestFields::LICENSE, spgh->license, LicenseExpressionField{}); - visit.optional_object_field(manifest, - ManifestFields::DEPENDENCIES, - spgh->dependencies, - ArrayField<DependencyField>{"an array of dependencies", AllowEmpty::Yes}); - - if (manifest.contains(ManifestFields::DEV_DEPENDENCIES)) + bool have_remaining_fields = false; + for (auto&& error_info : error_info_list) { - System::print2(System::Color::error, "dev_dependencies are not yet supported"); - Checks::exit_fail(VCPKG_LINE_INFO); - } - - visit.optional_object_field(manifest, ManifestFields::SUPPORTS, spgh->supports_expression, PlatformExprField{}); + if (!error_info->extra_fields.empty()) + { + System::print2(System::Color::error, + "Error: There are invalid fields in the control or manifest file of ", + error_info->name, + '\n'); + System::print2("The following fields were not expected:\n"); - visit.optional_object_field(manifest, - ManifestFields::DEFAULT_FEATURES, - spgh->default_features, - ArrayField<IdentifierField>{"an array of identifiers", AllowEmpty::Yes}); + for (const auto& pr : error_info->extra_fields) + { + System::print2(" In ", pr.first, ": ", Strings::join(", ", pr.second), "\n"); + } + have_remaining_fields = true; + } + } - visit.optional_object_field(manifest, - ManifestFields::FEATURES, - control_file->feature_paragraphs, - ArrayField<FeatureField>{"an array of feature definitions", AllowEmpty::Yes}); + if (have_remaining_fields) + { + System::print2("This is the list of valid fields for CONTROL files (case-sensitive): \n\n ", + Strings::join("\n ", get_list_of_valid_fields()), + "\n\n"); + System::print2("And this is the list of valid fields for manifest files: \n\n ", + Strings::join("\n ", ManifestDeserializer{}.valid_fields()), + "\n\n"); +#if defined(_WIN32) + auto bootstrap = ".\\bootstrap-vcpkg.bat"; +#else + auto bootstrap = "./bootstrap-vcpkg.sh"; +#endif + System::printf("You may need to update the vcpkg binary; try running %s to update.\n\n", bootstrap); + } - if (err.pcei.has_error()) + for (auto&& error_info : error_info_list) { - return std::make_unique<ParseControlErrorInfo>(std::move(err.pcei)); + if (!error_info->missing_fields.empty()) + { + System::print2(System::Color::error, + "Error: There are missing fields in the control file of ", + error_info->name, + '\n'); + System::print2("The following fields were missing:\n"); + for (const auto& pr : error_info->missing_fields) + { + System::print2(" In ", pr.first, ": ", Strings::join(", ", pr.second), "\n"); + } + } } - canonicalize(*control_file); - return std::move(control_file); + for (auto&& error_info : error_info_list) + { + if (!error_info->expected_types.empty()) + { + System::print2(System::Color::error, + "Error: There are invalid field types in the CONTROL or manifest file of ", + error_info->name, + '\n'); + System::print2("The following fields had the wrong types:\n\n"); + + for (const auto& pr : error_info->expected_types) + { + System::printf(" %s was expected to be %s\n", pr.first, pr.second); + } + System::print2("\n"); + } + } } Optional<const FeatureParagraph&> SourceControlFile::find_feature(const std::string& featurename) const @@ -1127,18 +950,18 @@ namespace vcpkg dep_obj.insert(el.first.to_string(), el.second); } - dep_obj.insert(DependencyField::NAME, Json::Value::string(dep.name)); + dep_obj.insert(DependencyDeserializer::NAME, Json::Value::string(dep.name)); auto features_copy = dep.features; auto core_it = std::find(features_copy.begin(), features_copy.end(), "core"); if (core_it != features_copy.end()) { - dep_obj.insert(DependencyField::DEFAULT_FEATURES, Json::Value::boolean(false)); + dep_obj.insert(DependencyDeserializer::DEFAULT_FEATURES, Json::Value::boolean(false)); features_copy.erase(core_it); } - serialize_optional_array(dep_obj, DependencyField::FEATURES, features_copy); - serialize_optional_string(dep_obj, DependencyField::PLATFORM, to_string(dep.platform)); + serialize_optional_array(dep_obj, DependencyDeserializer::FEATURES, features_copy); + serialize_optional_string(dep_obj, DependencyDeserializer::PLATFORM, to_string(dep.platform)); } }; @@ -1149,25 +972,26 @@ namespace vcpkg obj.insert(el.first.to_string(), el.second); } - obj.insert(ManifestFields::NAME, Json::Value::string(scf.core_paragraph->name)); - obj.insert(ManifestFields::VERSION, Json::Value::string(scf.core_paragraph->version)); + obj.insert(ManifestDeserializer::NAME, Json::Value::string(scf.core_paragraph->name)); + obj.insert(ManifestDeserializer::VERSION, Json::Value::string(scf.core_paragraph->version)); if (scf.core_paragraph->port_version != 0 || debug) { - obj.insert(ManifestFields::PORT_VERSION, Json::Value::integer(scf.core_paragraph->port_version)); + obj.insert(ManifestDeserializer::PORT_VERSION, Json::Value::integer(scf.core_paragraph->port_version)); } - serialize_paragraph(obj, ManifestFields::MAINTAINERS, scf.core_paragraph->maintainers); - serialize_paragraph(obj, ManifestFields::DESCRIPTION, scf.core_paragraph->description); + serialize_paragraph(obj, ManifestDeserializer::MAINTAINERS, scf.core_paragraph->maintainers); + serialize_paragraph(obj, ManifestDeserializer::DESCRIPTION, scf.core_paragraph->description); - serialize_optional_string(obj, ManifestFields::HOMEPAGE, scf.core_paragraph->homepage); - serialize_optional_string(obj, ManifestFields::DOCUMENTATION, scf.core_paragraph->documentation); - serialize_optional_string(obj, ManifestFields::LICENSE, scf.core_paragraph->license); - serialize_optional_string(obj, ManifestFields::SUPPORTS, to_string(scf.core_paragraph->supports_expression)); + serialize_optional_string(obj, ManifestDeserializer::HOMEPAGE, scf.core_paragraph->homepage); + serialize_optional_string(obj, ManifestDeserializer::DOCUMENTATION, scf.core_paragraph->documentation); + serialize_optional_string(obj, ManifestDeserializer::LICENSE, scf.core_paragraph->license); + serialize_optional_string( + obj, ManifestDeserializer::SUPPORTS, to_string(scf.core_paragraph->supports_expression)); if (!scf.core_paragraph->dependencies.empty() || debug) { - auto& deps = obj.insert(ManifestFields::DEPENDENCIES, Json::Array()); + auto& deps = obj.insert(ManifestDeserializer::DEPENDENCIES, Json::Array()); for (const auto& dep : scf.core_paragraph->dependencies) { @@ -1175,11 +999,11 @@ namespace vcpkg } } - serialize_optional_array(obj, ManifestFields::DEFAULT_FEATURES, scf.core_paragraph->default_features); + serialize_optional_array(obj, ManifestDeserializer::DEFAULT_FEATURES, scf.core_paragraph->default_features); if (!scf.feature_paragraphs.empty() || debug) { - auto& arr = obj.insert(ManifestFields::FEATURES, Json::Array()); + auto& arr = obj.insert(ManifestDeserializer::FEATURES, Json::Array()); for (const auto& feature : scf.feature_paragraphs) { auto& feature_obj = arr.push_back(Json::Object()); @@ -1188,12 +1012,12 @@ namespace vcpkg feature_obj.insert(el.first.to_string(), el.second); } - feature_obj.insert(FeatureField::NAME, Json::Value::string(feature->name)); - serialize_paragraph(feature_obj, FeatureField::DESCRIPTION, feature->description, true); + feature_obj.insert(FeatureDeserializer::NAME, Json::Value::string(feature->name)); + serialize_paragraph(feature_obj, FeatureDeserializer::DESCRIPTION, feature->description, true); if (!feature->dependencies.empty() || debug) { - auto& deps = feature_obj.insert(FeatureField::DEPENDENCIES, Json::Array()); + auto& deps = feature_obj.insert(FeatureDeserializer::DEPENDENCIES, Json::Array()); for (const auto& dep : feature->dependencies) { serialize_dependency(deps, dep); diff --git a/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp index 7377baafb..18288104f 100644 --- a/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp +++ b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp @@ -49,6 +49,7 @@ namespace vcpkg {VcpkgCmdArguments::BINARY_CACHING_FEATURE, args.binary_caching}, {VcpkgCmdArguments::MANIFEST_MODE_FEATURE, args.manifest_mode}, {VcpkgCmdArguments::COMPILER_TRACKING_FEATURE, args.compiler_tracking}, + {VcpkgCmdArguments::REGISTRIES_FEATURE, args.registries_feature}, }; for (const auto& desc : flag_descriptions) @@ -730,6 +731,7 @@ namespace vcpkg {BINARY_CACHING_FEATURE, binary_caching}, {MANIFEST_MODE_FEATURE, manifest_mode}, {COMPILER_TRACKING_FEATURE, compiler_tracking}, + {REGISTRIES_FEATURE, registries_feature}, }; for (const auto& flag : flags) @@ -754,6 +756,7 @@ namespace vcpkg } flags[] = { {BINARY_CACHING_FEATURE, binary_caching_enabled()}, {COMPILER_TRACKING_FEATURE, compiler_tracking_enabled()}, + {REGISTRIES_FEATURE, registries_enabled()}, }; for (const auto& flag : flags) diff --git a/toolsrc/src/vcpkg/vcpkgpaths.cpp b/toolsrc/src/vcpkg/vcpkgpaths.cpp index 660c180e2..677ac1a09 100644 --- a/toolsrc/src/vcpkg/vcpkgpaths.cpp +++ b/toolsrc/src/vcpkg/vcpkgpaths.cpp @@ -8,9 +8,12 @@ #include <vcpkg/binaryparagraph.h> #include <vcpkg/build.h> #include <vcpkg/commands.h> +#include <vcpkg/configuration.h> #include <vcpkg/globalstate.h> #include <vcpkg/metrics.h> #include <vcpkg/packagespec.h> +#include <vcpkg/registries.h> +#include <vcpkg/sourceparagraph.h> #include <vcpkg/tools.h> #include <vcpkg/vcpkgcmdarguments.h> #include <vcpkg/vcpkgpaths.h> @@ -81,6 +84,141 @@ namespace namespace vcpkg { + static Configuration deserialize_configuration(const Json::Object& obj, const VcpkgCmdArguments& args) + { + Json::BasicReaderError err; + Json::Reader reader{&err}; + auto deserializer = ConfigurationDeserializer(args); + + auto parsed_config_opt = reader.visit_value(obj, "$", deserializer); + if (err.has_error()) + { + if (!err.missing_fields.empty()) + { + System::print2(System::Color::error, "Error: missing fields in configuration:\n"); + for (const auto& missing : err.missing_fields) + { + System::printf( + System::Color::error, " %s was expected to have: %s\n", missing.first, missing.second); + } + } + if (!err.expected_types.empty()) + { + System::print2(System::Color::error, "Error: Invalid types in configuration:\n"); + for (const auto& expected : err.expected_types) + { + System::printf( + System::Color::error, " %s was expected to be %s\n", expected.first, expected.second); + } + } + if (!err.extra_fields.empty()) + { + System::print2(System::Color::error, "Error: Invalid fields in configuration:\n"); + for (const auto& extra : err.extra_fields) + { + System::printf(System::Color::error, + " %s had invalid fields: %s\n", + extra.first, + Strings::join(", ", extra.second)); + } + } + if (!err.mutually_exclusive_fields.empty()) + { + // this should never happen + Checks::unreachable(VCPKG_LINE_INFO); + } + + Checks::exit_fail(VCPKG_LINE_INFO); + } + + return std::move(parsed_config_opt).value_or_exit(VCPKG_LINE_INFO); + } + + struct ManifestAndConfig + { + fs::path config_directory; + Configuration config; + }; + + static std::pair<Json::Object, Json::JsonStyle> load_manifest(const Files::Filesystem& fs, + const fs::path& manifest_dir) + { + std::error_code ec; + auto manifest_path = manifest_dir / fs::u8path("vcpkg.json"); + auto manifest_opt = Json::parse_file(fs, manifest_path, ec); + if (ec) + { + Checks::exit_with_message(VCPKG_LINE_INFO, + "Failed to load manifest from directory %s: %s", + fs::u8string(manifest_dir), + ec.message()); + } + + if (!manifest_opt.has_value()) + { + Checks::exit_with_message(VCPKG_LINE_INFO, + "Failed to parse manifest at %s: %s", + fs::u8string(manifest_path), + manifest_opt.error()->format()); + } + auto manifest_value = std::move(manifest_opt).value_or_exit(VCPKG_LINE_INFO); + + if (!manifest_value.first.is_object()) + { + System::print2(System::Color::error, + "Failed to parse manifest at ", + fs::u8string(manifest_path), + ": Manifest files must have a top-level object\n"); + Checks::exit_fail(VCPKG_LINE_INFO); + } + return {std::move(manifest_value.first.object()), std::move(manifest_value.second)}; + } + + struct ConfigAndPath + { + fs::path config_directory; + Configuration config; + }; + + // doesn't yet implement searching upwards for configurations, nor inheritance of configurations + static ConfigAndPath load_configuration(const Files::Filesystem& fs, + const VcpkgCmdArguments& args, + const fs::path& vcpkg_root, + const fs::path& manifest_dir) + { + fs::path config_dir; + + if (!manifest_dir.empty()) + { + // manifest mode + config_dir = manifest_dir; + } + else + { + // classic mode + config_dir = vcpkg_root; + } + + auto path_to_config = config_dir / fs::u8path("vcpkg-configuration.json"); + if (!fs.exists(path_to_config)) + { + return {}; + } + + auto parsed_config = Json::parse_file(VCPKG_LINE_INFO, fs, path_to_config); + if (!parsed_config.first.is_object()) + { + System::print2(System::Color::error, + "Failed to parse ", + fs::u8string(path_to_config), + ": configuration files must have a top-level object\n"); + Checks::exit_fail(VCPKG_LINE_INFO); + } + auto config_obj = std::move(parsed_config.first.object()); + + return {std::move(config_dir), deserialize_configuration(config_obj, args)}; + } + namespace details { struct VcpkgPathsImpl @@ -104,6 +242,9 @@ namespace vcpkg Build::EnvCache m_env_cache; fs::SystemHandle file_lock_handle; + + Optional<std::pair<Json::Object, Json::JsonStyle>> m_manifest_doc; + Configuration m_config; }; } @@ -136,7 +277,7 @@ namespace vcpkg } else { - manifest_root_dir = filesystem.find_file_recursively_up(original_cwd, "vcpkg.json"); + manifest_root_dir = filesystem.find_file_recursively_up(original_cwd, fs::u8path("vcpkg.json")); } uppercase_win32_drive_letter(manifest_root_dir); @@ -162,6 +303,8 @@ namespace vcpkg System::printf(System::Color::error, " %s\n", ec.message()); Checks::exit_fail(VCPKG_LINE_INFO); } + + m_pimpl->m_manifest_doc = load_manifest(filesystem, manifest_root_dir); } else { @@ -198,6 +341,11 @@ If you wish to silence this error and use classic mode, you can: process_output_directory(filesystem, root, args.install_root_dir.get(), "installed", VCPKG_LINE_INFO); } + auto config_file = load_configuration(filesystem, args, root, manifest_root_dir); + + config_root_dir = std::move(config_file.config_directory); + m_pimpl->m_config = std::move(config_file.config); + buildtrees = process_output_directory(filesystem, root, args.buildtrees_root_dir.get(), "buildtrees", VCPKG_LINE_INFO); downloads = @@ -336,6 +484,20 @@ If you wish to silence this error and use classic mode, you can: return m_pimpl->m_tool_cache->get_tool_version(*this, tool); } + Optional<const Json::Object&> VcpkgPaths::get_manifest() const + { + if (auto p = m_pimpl->m_manifest_doc.get()) + { + return p->first; + } + else + { + return nullopt; + } + } + + const Configuration& VcpkgPaths::get_configuration() const { return m_pimpl->m_config; } + const Toolset& VcpkgPaths::get_toolset(const Build::PreBuildInfo& prebuildinfo) const { if (!prebuildinfo.using_vcvars()) diff --git a/toolsrc/windows-bootstrap/vcpkglib/vcpkglib.vcxproj b/toolsrc/windows-bootstrap/vcpkglib/vcpkglib.vcxproj index 334532a36..240b0115b 100644 --- a/toolsrc/windows-bootstrap/vcpkglib/vcpkglib.vcxproj +++ b/toolsrc/windows-bootstrap/vcpkglib/vcpkglib.vcxproj @@ -225,6 +225,7 @@ <ClInclude Include="..\..\include\vcpkg\portfileprovider.h" />
<ClInclude Include="..\..\include\vcpkg\postbuildlint.h" />
<ClInclude Include="..\..\include\vcpkg\postbuildlint.buildtype.h" />
+ <ClInclude Include="..\..\include\vcpkg\registries.h" />
<ClInclude Include="..\..\include\vcpkg\remove.h" />
<ClInclude Include="..\..\include\vcpkg\sourceparagraph.h" />
<ClInclude Include="..\..\include\vcpkg\statusparagraph.h" />
@@ -294,6 +295,7 @@ <ClCompile Include="..\..\src\vcpkg\commands.upgrade.cpp" />
<ClCompile Include="..\..\src\vcpkg\commands.version.cpp" />
<ClCompile Include="..\..\src\vcpkg\commands.xvsinstances.cpp" />
+ <ClCompile Include="..\..\src\vcpkg\configuration.cpp" />
<ClCompile Include="..\..\src\vcpkg\dependencies.cpp" />
<ClCompile Include="..\..\src\vcpkg\export.cpp" />
<ClCompile Include="..\..\src\vcpkg\export.chocolatey.cpp" />
@@ -310,6 +312,7 @@ <ClCompile Include="..\..\src\vcpkg\portfileprovider.cpp" />
<ClCompile Include="..\..\src\vcpkg\postbuildlint.buildtype.cpp" />
<ClCompile Include="..\..\src\vcpkg\postbuildlint.cpp" />
+ <ClCompile Include="..\..\src\vcpkg\registries.cpp" />
<ClCompile Include="..\..\src\vcpkg\remove.cpp" />
<ClCompile Include="..\..\src\vcpkg\sourceparagraph.cpp" />
<ClCompile Include="..\..\src\vcpkg\statusparagraph.cpp" />
|
