[vcpkg] Lift --x-json to a global option, implement experimental x-package-info command (#12845)

* [vcpkg] Improve error reporting in vcpkg::Json

* [vcpkg] Lift --x-json to a common option

* [vcpkg] Address warnings-as-errors in VS2015

* [vcpkg] Remove unused local

* [vcpkg] Extract vcpkg::Install::get_cmake_usage

* [vcpkg] Implement vcpkg::serialize_ipv(ipv, paths)

* [vcpkg] Implement x-package-info to enable tooling

* [vcpkg] Fixup tests to respect new cli mode

Co-authored-by: Robert Schumacher <roschuma@microsoft.com>
This commit is contained in:
ras0219 2020-08-13 18:36:33 -07:00 committed by GitHub
parent 5bb91a9452
commit 0b5bbe30d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 287 additions and 32 deletions

View File

@ -227,13 +227,13 @@ namespace vcpkg::Json
Value& operator[](StringView key) noexcept
{
auto res = this->get(key);
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, res);
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, res, "missing key: \"%s\"", key);
return *res;
}
const Value& operator[](StringView key) const noexcept
{
auto res = this->get(key);
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, res);
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, res, "missing key: \"%s\"", key);
return *res;
}

View File

@ -39,7 +39,7 @@ namespace vcpkg
std::vector<std::string> default_features;
std::vector<std::string> dependencies;
std::string abi;
Type type;
Type type = {Type::UNKNOWN};
};
bool operator==(const BinaryParagraph&, const BinaryParagraph&);

View File

@ -0,0 +1,13 @@
#pragma once
#include <vcpkg/commands.interface.h>
namespace vcpkg::Commands::Info
{
extern const CommandStructure COMMAND_STRUCTURE;
struct InfoCommand : PathsCommand
{
virtual void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) const override;
};
}

View File

@ -2,6 +2,7 @@
#include <vcpkg/base/chrono.h>
#include <vcpkg/binaryparagraph.h>
#include <vcpkg/build.h>
#include <vcpkg/dependencies.h>
#include <vcpkg/vcpkgcmdarguments.h>
@ -90,6 +91,16 @@ namespace vcpkg::Install
const Build::IBuildLogsRecorder& build_logs_recorder,
const CMakeVars::CMakeVarProvider& var_provider);
struct CMakeUsageInfo
{
std::string message;
bool usage_file = false;
Optional<bool> header_only;
std::map<std::string, std::vector<std::string>> cmake_targets_map;
};
CMakeUsageInfo get_cmake_usage(const BinaryParagraph& bpgh, const VcpkgPaths& paths);
extern const CommandStructure COMMAND_STRUCTURE;
void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, Triplet default_triplet);

View File

@ -61,4 +61,6 @@ namespace vcpkg
const StatusParagraph* core;
std::vector<const StatusParagraph*> features;
};
Json::Value serialize_ipv(const InstalledPackageView& ipv, const struct VcpkgPaths& paths);
}

View File

@ -153,6 +153,9 @@ namespace vcpkg
constexpr static StringLiteral WAIT_FOR_LOCK_SWITCH = "x-wait-for-lock";
Optional<bool> wait_for_lock = nullopt;
constexpr static StringLiteral JSON_SWITCH = "x-json";
Optional<bool> json = nullopt;
// feature flags
constexpr static StringLiteral FEATURE_FLAGS_ENV = "VCPKG_FEATURE_FLAGS";
constexpr static StringLiteral FEATURE_FLAGS_ARG = "feature-flags";
@ -169,6 +172,7 @@ namespace vcpkg
bool binary_caching_enabled() const { return binary_caching.value_or(true); }
bool compiler_tracking_enabled() const { return compiler_tracking.value_or(true); }
bool output_json() const { return json.value_or(false); }
std::string command;
std::vector<std::string> command_arguments;

View File

@ -24,7 +24,7 @@ TEST_CASE ("get_available_basic_commands works", "[commands]")
TEST_CASE ("get_available_paths_commands works", "[commands]")
{
auto commands_list = Commands::get_available_paths_commands();
CHECK(commands_list.size() == 18);
CHECK(commands_list.size() == 19);
CHECK(Commands::find("/?", commands_list) != nullptr);
CHECK(Commands::find("help", commands_list) != nullptr);
@ -42,6 +42,7 @@ TEST_CASE ("get_available_paths_commands works", "[commands]")
CHECK(Commands::find("fetch", commands_list) != nullptr);
CHECK(Commands::find("x-ci-clean", commands_list) != nullptr);
CHECK(Commands::find("x-history", commands_list) != nullptr);
CHECK(Commands::find("x-package-info", commands_list) != nullptr);
CHECK(Commands::find("x-vsinstances", commands_list) != nullptr);
CHECK(Commands::find("x-format-manifest", commands_list) != nullptr);

View File

@ -8,6 +8,10 @@
#include <vcpkg-test/util.h>
#if defined(_MSC_VER)
#pragma warning(disable : 6237)
#endif
using namespace vcpkg;
using namespace vcpkg::Paragraphs;
using namespace vcpkg::Test;
@ -248,7 +252,7 @@ TEST_CASE ("Serialize all the ports", "[manifests]")
std::vector<std::string> args_list = {"x-format-manifest"};
auto& fs = Files::get_real_filesystem();
auto args = VcpkgCmdArguments::create_from_arg_sequence(args_list.data(), args_list.data() + args_list.size());
auto paths = VcpkgPaths{fs, args};
VcpkgPaths paths{fs, args};
std::vector<SourceControlFile> scfs;

View File

@ -8,6 +8,10 @@
#include <utility>
#include <vector>
#if defined(_MSC_VER)
#pragma warning(disable : 6237)
#endif
TEST_CASE ("b32 encoding", "[strings]")
{
using u64 = uint64_t;

View File

@ -11,6 +11,10 @@
#include <string>
#if defined(_MSC_VER)
#pragma warning(disable : 6237)
#endif
using vcpkg::nullopt;
using vcpkg::Optional;
using vcpkg::StringView;

View File

@ -77,7 +77,9 @@ static void inner(vcpkg::Files::Filesystem& fs, const VcpkgCmdArguments& args)
paths.track_feature_flag_metrics();
fs.current_path(paths.root, VCPKG_LINE_INFO);
if (args.command == "install" || args.command == "remove" || args.command == "export" || args.command == "update")
if ((args.command == "install" || args.command == "remove" || args.command == "export" ||
args.command == "update") &&
!args.output_json())
{
Commands::Version::warn_if_vcpkg_version_mismatch(paths);
std::string surveydate = *GlobalState::g_surveydate.lock();

View File

@ -141,30 +141,30 @@ namespace vcpkg::Json
}
StringView Value::string() const noexcept
{
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_string());
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_string(), "json value is not string");
return underlying_->string;
}
const Array& Value::array() const& noexcept
{
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_array());
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_array(), "json value is not array");
return underlying_->array;
}
Array& Value::array() & noexcept
{
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_array());
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_array(), "json value is not array");
return underlying_->array;
}
Array&& Value::array() && noexcept { return std::move(this->array()); }
const Object& Value::object() const& noexcept
{
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_object());
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_object(), "json value is not object");
return underlying_->object;
}
Object& Value::object() & noexcept
{
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_object());
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_object(), "json value is not object");
return underlying_->object;
}
Object&& Value::object() && noexcept { return std::move(this->object()); }

View File

@ -15,6 +15,7 @@
#include <vcpkg/commands.format-manifest.h>
#include <vcpkg/commands.h>
#include <vcpkg/commands.hash.h>
#include <vcpkg/commands.info.h>
#include <vcpkg/commands.integrate.h>
#include <vcpkg/commands.list.h>
#include <vcpkg/commands.owns.h>
@ -49,6 +50,7 @@ namespace vcpkg::Commands
static const Help::HelpCommand help{};
static const Search::SearchCommand search{};
static const List::ListCommand list{};
static const Info::InfoCommand info{};
static const Integrate::IntegrateCommand integrate{};
static const Owns::OwnsCommand owns{};
static const Update::UpdateCommand update{};
@ -80,6 +82,7 @@ namespace vcpkg::Commands
{"hash", &hash},
{"fetch", &fetch},
{"x-ci-clean", &ciclean},
{"x-package-info", &info},
{"x-history", &porthistory},
{"x-vsinstances", &vsinstances},
{"x-format-manifest", &format_manifest},

View File

@ -0,0 +1,146 @@
#include "pch.h"
#include <vcpkg/base/json.h>
#include <vcpkg/base/parse.h>
#include <vcpkg/base/stringliteral.h>
#include <vcpkg/commands.info.h>
#include <vcpkg/input.h>
#include <vcpkg/install.h>
#include <vcpkg/portfileprovider.h>
#include <vcpkg/statusparagraphs.h>
#include <vcpkg/vcpkglib.h>
#include <vcpkg/versiont.h>
namespace vcpkg::Commands::Info
{
static constexpr StringLiteral OPTION_TRANSITIVE = "x-transitive";
static constexpr StringLiteral OPTION_INSTALLED = "x-installed";
static constexpr CommandSwitch INFO_SWITCHES[] = {
{OPTION_INSTALLED, "(experimental) Report on installed packages instead of available"},
{OPTION_TRANSITIVE, "(experimental) Also report on dependencies of installed packages"},
};
const CommandStructure COMMAND_STRUCTURE = {
Strings::format("Display detailed information on packages.\n%s",
create_example_string("x-package-info zlib openssl:x64-windows")),
1,
SIZE_MAX,
{INFO_SWITCHES, {}},
nullptr,
};
void InfoCommand::perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) const
{
const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE);
if (!args.output_json())
{
Checks::exit_with_message(
VCPKG_LINE_INFO, "This command currently requires --%s", VcpkgCmdArguments::JSON_SWITCH);
}
const bool installed = Util::Sets::contains(options.switches, OPTION_INSTALLED);
const bool transitive = Util::Sets::contains(options.switches, OPTION_TRANSITIVE);
if (transitive && !installed)
{
Checks::exit_with_message(VCPKG_LINE_INFO, "--%s requires --%s", OPTION_TRANSITIVE, OPTION_INSTALLED);
}
if (installed)
{
const StatusParagraphs status_paragraphs = database_load_check(paths);
std::set<PackageSpec> specs_written;
std::vector<PackageSpec> specs_to_write;
for (auto&& arg : args.command_arguments)
{
Parse::ParserBase parser(arg, "<command>");
auto maybe_qpkg = parse_qualified_specifier(parser);
if (!parser.at_eof() || !maybe_qpkg)
{
parser.add_error("expected a package specifier");
}
else if (!maybe_qpkg.get()->triplet)
{
parser.add_error("expected an explicit triplet");
}
else if (maybe_qpkg.get()->features)
{
parser.add_error("unexpected list of features");
}
else if (maybe_qpkg.get()->platform)
{
parser.add_error("unexpected qualifier");
}
if (auto err = parser.get_error())
{
System::print2(err->format(), "\n");
Checks::exit_fail(VCPKG_LINE_INFO);
}
auto& qpkg = *maybe_qpkg.get();
auto t = Triplet::from_canonical_name(std::string(*qpkg.triplet.get()));
Input::check_triplet(t, paths);
specs_to_write.emplace_back(qpkg.name, t);
}
Json::Object response;
Json::Object results;
while (!specs_to_write.empty())
{
auto spec = std::move(specs_to_write.back());
specs_to_write.pop_back();
if (!specs_written.insert(spec).second) continue;
auto maybe_ipv = status_paragraphs.get_installed_package_view(spec);
if (auto ipv = maybe_ipv.get())
{
results.insert(spec.to_string(), serialize_ipv(*ipv, paths));
if (transitive)
{
auto deps = ipv->dependencies();
specs_to_write.insert(specs_to_write.end(),
std::make_move_iterator(deps.begin()),
std::make_move_iterator(deps.end()));
}
}
}
response.insert("results", std::move(results));
System::print2(Json::stringify(response, {}));
}
else
{
Json::Object response;
Json::Object results;
PortFileProvider::PathsPortFileProvider provider(paths, args.overlay_ports);
for (auto&& arg : args.command_arguments)
{
Parse::ParserBase parser(arg, "<command>");
auto maybe_pkg = parse_package_name(parser);
if (!parser.at_eof() || !maybe_pkg)
{
parser.add_error("expected only a package identifier");
}
if (auto err = parser.get_error())
{
System::print2(err->format(), "\n");
Checks::exit_fail(VCPKG_LINE_INFO);
}
auto& pkg = *maybe_pkg.get();
if (results.contains(pkg)) continue;
auto maybe_scfl = provider.get_control_file(pkg);
Json::Object obj;
if (auto pscfl = maybe_scfl.get())
{
results.insert(pkg, serialize_manifest(*pscfl->source_control_file));
}
}
response.insert("results", std::move(results));
System::print2(Json::stringify(response, {}));
}
}
}

View File

@ -9,8 +9,6 @@ namespace vcpkg::Commands::List
{
static constexpr StringLiteral OPTION_FULLDESC = "x-full-desc"; // TODO: This should find a better home, eventually
static constexpr StringLiteral OPTION_JSON = "--x-json";
static void do_print_json(std::vector<const vcpkg::StatusParagraph*> installed_packages)
{
Json::Object obj;
@ -73,9 +71,8 @@ namespace vcpkg::Commands::List
}
}
static constexpr std::array<CommandSwitch, 2> LIST_SWITCHES = {{
static constexpr std::array<CommandSwitch, 1> LIST_SWITCHES = {{
{OPTION_FULLDESC, "Do not truncate long text"},
{OPTION_JSON, "List libraries in JSON format"},
}};
const CommandStructure COMMAND_STRUCTURE = {
@ -113,7 +110,6 @@ namespace vcpkg::Commands::List
});
const auto enable_fulldesc = Util::Sets::contains(options.switches, OPTION_FULLDESC.to_string());
const auto enable_json = Util::Sets::contains(options.switches, OPTION_JSON.to_string());
if (!args.command_arguments.empty())
{
@ -124,7 +120,7 @@ namespace vcpkg::Commands::List
installed_packages = pghs;
}
if (enable_json)
if (args.output_json())
{
do_print_json(installed_packages);
}

View File

@ -573,21 +573,35 @@ namespace vcpkg::Install
};
static void print_cmake_information(const BinaryParagraph& bpgh, const VcpkgPaths& paths)
{
auto usage = get_cmake_usage(bpgh, paths);
if (!usage.message.empty())
{
System::print2(usage.message);
}
}
CMakeUsageInfo get_cmake_usage(const BinaryParagraph& bpgh, const VcpkgPaths& paths)
{
static const std::regex cmake_library_regex(R"(\badd_library\(([^\$\s\)]+)\s)",
std::regex_constants::ECMAScript);
CMakeUsageInfo ret;
auto& fs = paths.get_filesystem();
auto usage_file = paths.installed / bpgh.spec.triplet().canonical_name() / "share" / bpgh.spec.name() / "usage";
if (fs.exists(usage_file))
{
ret.usage_file = true;
auto maybe_contents = fs.read_contents(usage_file);
if (auto p_contents = maybe_contents.get())
{
System::print2(*p_contents, '\n');
ret.message = std::move(*p_contents);
ret.message.push_back('\n');
}
return;
return ret;
}
auto files = fs.read_lines(paths.listfile_path(bpgh));
@ -652,6 +666,8 @@ namespace vcpkg::Install
}
}
ret.header_only = is_header_only;
if (library_targets.empty())
{
if (is_header_only && !header_path.empty())
@ -670,20 +686,21 @@ namespace vcpkg::Install
"The package ", bpgh.spec, " is header only and can be used from CMake via:\n\n");
Strings::append(msg, " find_path(", name, "_INCLUDE_DIRS \"", header_path, "\")\n");
Strings::append(msg, " target_include_directories(main PRIVATE ${", name, "_INCLUDE_DIRS})\n\n");
System::print2(msg);
ret.message = std::move(msg);
}
}
else
{
System::print2("The package ", bpgh.spec, " provides CMake targets:\n\n");
auto msg = Strings::concat("The package ", bpgh.spec, " provides CMake targets:\n\n");
for (auto&& library_target_pair : library_targets)
{
auto config_it = config_files.find(library_target_pair.first);
if (config_it != config_files.end())
System::printf(" find_package(%s CONFIG REQUIRED)\n", config_it->second);
Strings::append(msg, " find_package(", config_it->second, " CONFIG REQUIRED)\n ");
else
System::printf(" find_package(%s CONFIG REQUIRED)\n", library_target_pair.first);
Strings::append(msg, " find_package(", library_target_pair.first, " CONFIG REQUIRED)\n ");
std::sort(library_target_pair.second.begin(),
library_target_pair.second.end(),
@ -695,22 +712,27 @@ namespace vcpkg::Install
if (library_target_pair.second.size() <= 4)
{
System::printf(" target_link_libraries(main PRIVATE %s)\n\n",
Strings::join(" ", library_target_pair.second));
Strings::append(msg,
" target_link_libraries(main PRIVATE ",
Strings::join(" ", library_target_pair.second),
")\n\n");
}
else
{
auto omitted = library_target_pair.second.size() - 4;
library_target_pair.second.erase(library_target_pair.second.begin() + 4,
library_target_pair.second.end());
System::printf(" # Note: %zd target(s) were omitted.\n"
" target_link_libraries(main PRIVATE %s)\n\n",
omitted,
Strings::join(" ", library_target_pair.second));
msg += Strings::format(" # Note: %zd target(s) were omitted.\n"
" target_link_libraries(main PRIVATE %s)\n\n",
omitted,
Strings::join(" ", library_target_pair.second));
}
}
ret.message = std::move(msg);
}
ret.cmake_targets_map = std::move(library_targets);
}
return ret;
}
///

View File

@ -1,6 +1,8 @@
#include <vcpkg/base/checks.h>
#include <vcpkg/install.h>
#include <vcpkg/statusparagraphs.h>
#include <vcpkg/vcpkgpaths.h>
namespace vcpkg
{
@ -143,4 +145,42 @@ namespace vcpkg
out_str.push_back('\n');
}
}
Json::Value serialize_ipv(const InstalledPackageView& ipv, const VcpkgPaths& paths)
{
const auto& fs = paths.get_filesystem();
Json::Object iobj;
iobj.insert("version-string", Json::Value::string(ipv.core->package.version));
iobj.insert("port-version", Json::Value::integer(ipv.core->package.port_version));
iobj.insert("triplet", Json::Value::string(ipv.spec().triplet().to_string()));
iobj.insert("abi", Json::Value::string(ipv.core->package.abi));
Json::Array deps;
for (auto&& dep : ipv.dependencies())
deps.push_back(Json::Value::string(dep.to_string()));
if (deps.size() != 0)
{
iobj.insert("dependencies", std::move(deps));
}
Json::Array features;
for (auto&& feature : ipv.features)
{
features.push_back(Json::Value::string(feature->package.feature));
}
if (features.size() != 0)
{
iobj.insert("features", std::move(features));
}
auto usage = Install::get_cmake_usage(ipv.core->package, paths);
if (!usage.message.empty())
{
iobj.insert("usage", Json::Value::string(std::move(usage.message)));
}
auto owns_files = fs.read_lines(paths.listfile_path(ipv.core->package)).value_or_exit(VCPKG_LINE_INFO);
Json::Array owns;
for (auto&& owns_file : owns_files)
owns.push_back(Json::Value::string(std::move(owns_file)));
iobj.insert("owns", std::move(owns));
return Json::Value::object(std::move(iobj));
}
}

View File

@ -308,6 +308,7 @@ namespace vcpkg
{FEATURE_PACKAGES_SWITCH, &VcpkgCmdArguments::feature_packages},
{BINARY_CACHING_SWITCH, &VcpkgCmdArguments::binary_caching},
{WAIT_FOR_LOCK_SWITCH, &VcpkgCmdArguments::wait_for_lock},
{JSON_SWITCH, &VcpkgCmdArguments::json},
};
bool found = false;
@ -604,7 +605,7 @@ namespace vcpkg
void VcpkgCmdArguments::append_common_options(HelpTableFormatter& table)
{
static auto opt = [](StringView arg, StringView joiner, StringView value) {
return Strings::format("--%s%s%s", arg, joiner, value);
return Strings::concat("--", arg, joiner, value);
};
table.format(opt(TRIPLET_ARG, " ", "<t>"), "Specify the target architecture triplet. See 'vcpkg help triplet'");
@ -623,6 +624,7 @@ namespace vcpkg
table.format(opt(INSTALL_ROOT_DIR_ARG, "=", "<path>"), "(Experimental) Specify the install root directory");
table.format(opt(PACKAGES_ROOT_DIR_ARG, "=", "<path>"), "(Experimental) Specify the packages root directory");
table.format(opt(SCRIPTS_ROOT_DIR_ARG, "=", "<path>"), "(Experimental) Specify the scripts root directory");
table.format(opt(JSON_SWITCH, "", ""), "(Experimental) Request JSON output");
}
void VcpkgCmdArguments::imbue_from_environment()

View File

@ -172,7 +172,6 @@ namespace vcpkg
{
std::map<PackageSpec, InstalledPackageView> ipv_map;
std::vector<InstalledPackageView> installed_packages;
for (auto&& pgh : status_db)
{
if (!pgh->is_installed()) continue;

View File

@ -163,7 +163,7 @@ namespace vcpkg
else
{
// we ignore the manifest root dir if the user requests -manifest
if (!manifest_root_dir.empty() && !args.manifest_mode.has_value())
if (!manifest_root_dir.empty() && !args.manifest_mode.has_value() && !args.output_json())
{
System::print2(System::Color::warning,
"Warning: manifest-root detected at ",

View File

@ -197,6 +197,7 @@
<ClInclude Include="..\..\include\vcpkg\commands.fetch.h" />
<ClInclude Include="..\..\include\vcpkg\commands.format-manifest.h" />
<ClInclude Include="..\..\include\vcpkg\commands.hash.h" />
<ClInclude Include="..\..\include\vcpkg\commands.info.h" />
<ClInclude Include="..\..\include\vcpkg\commands.integrate.h" />
<ClInclude Include="..\..\include\vcpkg\commands.interface.h" />
<ClInclude Include="..\..\include\vcpkg\commands.list.h" />
@ -284,6 +285,7 @@
<ClCompile Include="..\..\src\vcpkg\commands.fetch.cpp" />
<ClCompile Include="..\..\src\vcpkg\commands.format-manifest.cpp" />
<ClCompile Include="..\..\src\vcpkg\commands.hash.cpp" />
<ClCompile Include="..\..\src\vcpkg\commands.info.cpp" />
<ClCompile Include="..\..\src\vcpkg\commands.integrate.cpp" />
<ClCompile Include="..\..\src\vcpkg\commands.list.cpp" />
<ClCompile Include="..\..\src\vcpkg\commands.owns.cpp" />