[vcpkg] Add x-set-installed command (#10817)

This command takes a list of ports, and causes the final state of the
installed directory to be as-if one ran the install on an empty
installed directory (removing any unnecessary packages).

This is especially useful with the new `--x-install-root` option, which
allows one to set the `installed` directory for vcpkg to use.

Additionally, as a drive-by, we do some `stdfs` clean-up and add a
`.is_feature()` member function to BinaryParagraph (as opposed to
checking for `.feature().empty()`, which is far less clear to read).

This feature is experimental.
This commit is contained in:
nicole mazzuca 2020-04-17 15:49:59 -07:00 committed by GitHub
parent 71377f69e2
commit 556325a1f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 227 additions and 36 deletions

View File

@ -22,6 +22,7 @@ namespace fs
using stdfs::path;
using stdfs::perms;
using stdfs::u8path;
using stdfs::directory_iterator;
#if defined(_WIN32)
enum class file_type
@ -155,9 +156,13 @@ namespace vcpkg::Files
fs::file_status status(const fs::path& p, ignore_errors_t) const noexcept;
fs::file_status symlink_status(LineInfo li, const fs::path& p) const noexcept;
fs::file_status symlink_status(const fs::path& p, ignore_errors_t) const noexcept;
virtual fs::path absolute(const fs::path& path, std::error_code& ec) const = 0;
fs::path absolute(LineInfo li, const fs::path& path) const;
virtual fs::path canonical(const fs::path& path, std::error_code& ec) const = 0;
fs::path canonical(LineInfo li, const fs::path& path) const;
fs::path canonical(const fs::path& path, ignore_errors_t) const;
virtual fs::path current_path(std::error_code&) const = 0;
fs::path current_path(LineInfo li) const;
virtual std::vector<fs::path> find_from_PATH(const std::string& name) const = 0;
};

View File

@ -28,6 +28,8 @@ namespace vcpkg
std::string dir() const;
bool is_feature() const { return !feature.empty(); }
PackageSpec spec;
std::string version;
std::string description;

View File

@ -139,6 +139,12 @@ namespace vcpkg::Commands
void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths);
}
namespace SetInstalled
{
extern const CommandStructure COMMAND_STRUCTURE;
void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, Triplet default_triplet);
}
template<class T>
struct PackageNameAndFunction
{

View File

@ -40,7 +40,7 @@ namespace vcpkg
std::vector<std::unique_ptr<StatusParagraph>*> find_all(const std::string& name, Triplet triplet);
Optional<InstalledPackageView> find_all_installed(const PackageSpec& spec) const;
Optional<InstalledPackageView> get_installed_package_view(const PackageSpec& spec) const;
/// <summary>Find the StatusParagraph for given spec if installed</summary>
/// <param name="spec">Package specification to find the status for</param>

View File

@ -86,6 +86,7 @@ namespace vcpkg
static VcpkgCmdArguments create_from_arg_sequence(const std::string* arg_begin, const std::string* arg_end);
std::unique_ptr<std::string> vcpkg_root_dir;
std::unique_ptr<std::string> install_root_dir;
std::unique_ptr<std::string> scripts_root_dir;
std::unique_ptr<std::string> triplet;
std::unique_ptr<std::vector<std::string>> overlay_ports;

View File

@ -58,6 +58,7 @@ namespace vcpkg
};
static Expected<VcpkgPaths> create(const fs::path& vcpkg_root_dir,
const Optional<fs::path>& install_root_dir,
const Optional<fs::path>& vcpkg_scripts_root_dir,
const std::string& default_vs_path,
const std::vector<std::string>* triplets_dirs);

View File

@ -62,6 +62,8 @@ static void invalid_command(const std::string& cmd)
static void inner(const VcpkgCmdArguments& args)
{
auto& fs = Files::get_real_filesystem();
Metrics::g_metrics.lock()->track_property("command", args.command);
if (args.command.empty())
{
@ -88,26 +90,26 @@ static void inner(const VcpkgCmdArguments& args)
}
fs::path vcpkg_root_dir;
if (args.vcpkg_root_dir != nullptr)
if (args.vcpkg_root_dir)
{
vcpkg_root_dir = fs::stdfs::absolute(fs::u8path(*args.vcpkg_root_dir));
vcpkg_root_dir = fs.absolute(VCPKG_LINE_INFO, fs::u8path(*args.vcpkg_root_dir));
}
else
{
const auto vcpkg_root_dir_env = System::get_environment_variable("VCPKG_ROOT");
if (const auto v = vcpkg_root_dir_env.get())
{
vcpkg_root_dir = fs::stdfs::absolute(*v);
vcpkg_root_dir = fs.absolute(VCPKG_LINE_INFO, *v);
}
else
{
const fs::path current_path = fs::stdfs::current_path();
vcpkg_root_dir = Files::get_real_filesystem().find_file_recursively_up(current_path, ".vcpkg-root");
const fs::path current_path = fs.current_path(VCPKG_LINE_INFO);
vcpkg_root_dir = fs.find_file_recursively_up(current_path, ".vcpkg-root");
if (vcpkg_root_dir.empty())
{
vcpkg_root_dir = Files::get_real_filesystem().find_file_recursively_up(
fs::stdfs::absolute(System::get_exe_path_of_current_process()), ".vcpkg-root");
vcpkg_root_dir = fs.find_file_recursively_up(
fs.absolute(VCPKG_LINE_INFO, System::get_exe_path_of_current_process()), ".vcpkg-root");
}
}
}
@ -116,17 +118,23 @@ static void inner(const VcpkgCmdArguments& args)
Debug::print("Using vcpkg-root: ", vcpkg_root_dir.u8string(), '\n');
Optional<fs::path> install_root_dir;
if (args.install_root_dir) {
install_root_dir = Files::get_real_filesystem().canonical(VCPKG_LINE_INFO, fs::u8path(*args.install_root_dir));
Debug::print("Using install-root: ", install_root_dir.value_or_exit(VCPKG_LINE_INFO).u8string(), '\n');
}
Optional<fs::path> vcpkg_scripts_root_dir = nullopt;
if (nullptr != args.scripts_root_dir)
if (args.scripts_root_dir)
{
vcpkg_scripts_root_dir = fs::stdfs::canonical(fs::u8path(*args.scripts_root_dir));
vcpkg_scripts_root_dir = Files::get_real_filesystem().canonical(VCPKG_LINE_INFO, fs::u8path(*args.scripts_root_dir));
Debug::print("Using scripts-root: ", vcpkg_scripts_root_dir.value_or_exit(VCPKG_LINE_INFO).u8string(), '\n');
}
auto default_vs_path = System::get_environment_variable("VCPKG_VISUAL_STUDIO_PATH").value_or("");
const Expected<VcpkgPaths> expected_paths =
VcpkgPaths::create(vcpkg_root_dir, vcpkg_scripts_root_dir, default_vs_path, args.overlay_triplets.get());
VcpkgPaths::create(vcpkg_root_dir, install_root_dir, vcpkg_scripts_root_dir, default_vs_path, args.overlay_triplets.get());
Checks::check_exit(VCPKG_LINE_INFO,
!expected_paths.error(),
"Error: Invalid vcpkg root directory %s: %s",

View File

@ -272,6 +272,14 @@ namespace vcpkg::Files
this->remove_all(path, ec, failure_point);
}
fs::path Filesystem::absolute(LineInfo li, const fs::path& path) const
{
std::error_code ec;
const auto result = this->absolute(path, ec);
if (ec) Checks::exit_with_message(li, "Error getting absolute path of %s: %s", path.string(), ec.message());
return result;
}
fs::path Filesystem::canonical(LineInfo li, const fs::path& path) const
{
std::error_code ec;
@ -286,6 +294,14 @@ namespace vcpkg::Files
std::error_code ec;
return this->canonical(path, ec);
}
fs::path Filesystem::current_path(LineInfo li) const
{
std::error_code ec;
const auto result = this->current_path(ec);
if (ec) Checks::exit_with_message(li, "Error getting current path: %s", ec.message());
return result;
}
struct RealFilesystem final : Filesystem
{
@ -699,11 +715,36 @@ namespace vcpkg::Files
}
}
virtual fs::path absolute(const fs::path& path, std::error_code& ec) const override
{
#if USE_STD_FILESYSTEM
return fs::stdfs::absolute(path, ec);
#else // ^^^ USE_STD_FILESYSTEM / !USE_STD_FILESYSTEM vvv
#if _WIN32
// absolute was called system_complete in experimental filesystem
return fs::stdfs::system_complete(path, ec);
#else // ^^^ _WIN32 / !_WIN32 vvv
if (path.is_absolute()) {
auto current_path = this->current_path(ec);
if (ec) return fs::path();
return std::move(current_path) / path;
} else {
return path;
}
#endif
#endif
}
virtual fs::path canonical(const fs::path& path, std::error_code& ec) const override
{
return fs::stdfs::canonical(path, ec);
}
virtual fs::path current_path(std::error_code& ec) const override
{
return fs::stdfs::current_path(ec);
}
virtual std::vector<fs::path> find_from_PATH(const std::string& name) const override
{
#if defined(_WIN32)

View File

@ -171,7 +171,7 @@ namespace
{
fs.copy_file(log_file.path(),
tmp_log_path_destination / log_file.path().filename(),
fs::stdfs::copy_options::none,
fs::copy_options::none,
ec);
}
}

View File

@ -62,7 +62,7 @@ namespace vcpkg
// for compatibility with previous vcpkg versions, we discard all irrelevant information
return dep.name;
});
if (this->feature.empty())
if (!this->is_feature())
{
this->default_features = parse_default_features_list(parser.optional_field(Fields::DEFAULTFEATURES))
.value_or_exit(VCPKG_LINE_INFO);
@ -109,7 +109,7 @@ namespace vcpkg
std::string BinaryParagraph::displayname() const
{
if (this->feature.empty() || this->feature == "core")
if (!this->is_feature() || this->feature == "core")
return Strings::format("%s:%s", this->spec.name(), this->spec.triplet());
return Strings::format("%s[%s]:%s", this->spec.name(), this->feature, this->spec.triplet());
}
@ -126,7 +126,7 @@ namespace vcpkg
out_str.append("Package: ").append(pgh.spec.name()).push_back('\n');
if (!pgh.version.empty())
out_str.append("Version: ").append(pgh.version).push_back('\n');
else if (!pgh.feature.empty())
else if (pgh.is_feature())
out_str.append("Feature: ").append(pgh.feature).push_back('\n');
if (!pgh.depends.empty())
{

View File

@ -890,7 +890,7 @@ namespace vcpkg::Build
ExtendedBuildResult result = do_build_package_and_clean_buildtrees(paths, action);
fs.create_directories(abi_package_dir, ec);
fs.copy_file(abi_file, abi_file_in_package, fs::stdfs::copy_options::none, ec);
fs.copy_file(abi_file, abi_file_in_package, fs::copy_options::none, ec);
Checks::check_exit(VCPKG_LINE_INFO, !ec, "Could not copy into file: %s", abi_file_in_package.u8string());
if (binary_caching_enabled && result.code == BuildResult::SUCCEEDED)

View File

@ -17,6 +17,7 @@ namespace vcpkg::Commands
{
static std::vector<PackageNameAndFunction<CommandTypeA>> t = {
{"install", &Install::perform_and_exit},
{"x-set-installed", &SetInstalled::perform_and_exit},
{"ci", &CI::perform_and_exit},
{"remove", &Remove::perform_and_exit},
{"upgrade", &Upgrade::perform_and_exit},

View File

@ -0,0 +1,111 @@
#include "pch.h"
#include <vcpkg/base/system.print.h>
#include <vcpkg/commands.h>
#include <vcpkg/globalstate.h>
#include <vcpkg/help.h>
#include <vcpkg/input.h>
#include <vcpkg/install.h>
#include <vcpkg/remove.h>
#include <vcpkg/portfileprovider.h>
#include <vcpkg/vcpkglib.h>
namespace vcpkg::Commands::SetInstalled
{
const CommandStructure COMMAND_STRUCTURE = {
Help::create_example_string(R"(x-set-installed <package>...)"),
1,
SIZE_MAX,
{},
nullptr,
};
void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, Triplet default_triplet)
{
// input sanitization
const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE);
const std::vector<FullPackageSpec> specs = Util::fmap(args.command_arguments, [&](auto&& arg) {
return Input::check_and_get_full_package_spec(
std::string(arg), default_triplet, COMMAND_STRUCTURE.example_text);
});
for (auto&& spec : specs)
{
Input::check_triplet(spec.package_spec.triplet(), paths);
}
const Build::BuildPackageOptions install_plan_options = {
Build::UseHeadVersion::NO,
Build::AllowDownloads::YES,
Build::OnlyDownloads::NO,
Build::CleanBuildtrees::YES,
Build::CleanPackages::YES,
Build::CleanDownloads::YES,
Build::DownloadTool::BUILT_IN,
GlobalState::g_binary_caching ? Build::BinaryCaching::YES : Build::BinaryCaching::NO,
Build::FailOnTombstone::NO,
};
PortFileProvider::PathsPortFileProvider provider(paths, args.overlay_ports.get());
auto cmake_vars = CMakeVars::make_triplet_cmake_var_provider(paths);
// We have a set of user-requested specs.
// We need to know all the specs which are required to fulfill dependencies for those specs.
// Therefore, we see what we would install into an empty installed tree, so we can use the existing code.
auto action_plan = Dependencies::create_feature_install_plan(provider, *cmake_vars, specs, {});
for (auto&& action : action_plan.install_actions)
{
action.build_options = install_plan_options;
}
cmake_vars->load_tag_vars(action_plan, provider);
Build::compute_all_abis(paths, action_plan, *cmake_vars, {});
std::set<std::string> all_abis;
for (const auto& action : action_plan.install_actions) {
all_abis.insert(action.package_abi.value_or_exit(VCPKG_LINE_INFO));
}
// currently (or once) installed specifications
auto status_db = database_load_check(paths);
std::vector<PackageSpec> specs_to_remove;
for (auto&& status_pgh : status_db)
{
if (!status_pgh->is_installed()) continue;
if (status_pgh->package.is_feature()) continue;
const auto& abi = status_pgh->package.abi;
if (abi.empty() || !Util::Sets::contains(all_abis, abi))
{
specs_to_remove.push_back(status_pgh->package.spec);
}
}
auto remove_plan = Dependencies::create_remove_plan(specs_to_remove, status_db);
for (const auto& action : remove_plan)
{
Remove::perform_remove_plan_action(paths, action, Remove::Purge::NO, &status_db);
}
auto real_action_plan = Dependencies::create_feature_install_plan(provider, *cmake_vars, specs, status_db);
for (auto& action : real_action_plan.install_actions)
{
action.build_options = install_plan_options;
}
Dependencies::print_plan(real_action_plan, true);
const auto summary = Install::perform(real_action_plan, Install::KeepGoing::NO, paths, status_db, *cmake_vars);
System::print2("\nTotal elapsed time: ", summary.total_elapsed_time, "\n\n");
Checks::exit_success(VCPKG_LINE_INFO);
}
}

View File

@ -560,7 +560,7 @@ namespace vcpkg::Dependencies
? RequestType::USER_REQUESTED
: RequestType::AUTO_SELECTED;
auto maybe_ipv = status_db.find_all_installed(spec);
auto maybe_ipv = status_db.get_installed_package_view(spec);
if (auto p_ipv = maybe_ipv.get())
{

View File

@ -34,7 +34,7 @@ namespace vcpkg::PortFileProvider
{
if (!overlay_path.empty())
{
auto overlay = fs::stdfs::canonical(fs::u8path(overlay_path));
auto overlay = fs.canonical(VCPKG_LINE_INFO, fs::u8path(overlay_path));
Checks::check_exit(VCPKG_LINE_INFO,
filesystem.exists(overlay),

View File

@ -21,7 +21,7 @@ namespace vcpkg::Remove
void remove_package(const VcpkgPaths& paths, const PackageSpec& spec, StatusParagraphs* status_db)
{
auto& fs = paths.get_filesystem();
auto maybe_ipv = status_db->find_all_installed(spec);
auto maybe_ipv = status_db->get_installed_package_view(spec);
Checks::check_exit(
VCPKG_LINE_INFO, maybe_ipv.has_value(), "unable to remove package %s: already removed", spec);
@ -72,6 +72,7 @@ namespace vcpkg::Remove
fs.remove(target, ec);
if (ec)
{
// TODO: this is racy; should we ignore this error?
#if defined(_WIN32)
fs::stdfs::permissions(target, fs::perms::owner_all | fs::perms::group_all, ec);
fs.remove(target, ec);

View File

@ -18,16 +18,16 @@ namespace vcpkg
{
if (p->package.spec.name() == name && p->package.spec.triplet() == triplet)
{
if (p->package.feature.empty())
spghs.emplace(spghs.begin(), &p);
else
if (p->package.is_feature())
spghs.emplace_back(&p);
else
spghs.emplace(spghs.begin(), &p);
}
}
return spghs;
}
Optional<InstalledPackageView> StatusParagraphs::find_all_installed(const PackageSpec& spec) const
Optional<InstalledPackageView> StatusParagraphs::get_installed_package_view(const PackageSpec& spec) const
{
InstalledPackageView ipv;
for (auto&& p : *this)
@ -35,13 +35,12 @@ namespace vcpkg
if (p->package.spec.name() == spec.name() && p->package.spec.triplet() == spec.triplet() &&
p->is_installed())
{
if (p->package.feature.empty())
{
if (p->package.is_feature()) {
ipv.features.emplace_back(p.get());
} else {
Checks::check_exit(VCPKG_LINE_INFO, ipv.core == nullptr);
ipv.core = p.get();
}
else
ipv.features.emplace_back(p.get());
}
}
if (ipv.core != nullptr)

View File

@ -152,6 +152,12 @@ namespace vcpkg
arg.substr(sizeof("--x-scripts-root=") - 1), "--x-scripts-root", args.scripts_root_dir);
continue;
}
if (Strings::starts_with(arg, "--x-install-root="))
{
parse_cojoined_value(
arg.substr(sizeof("--x-install-root=") - 1), "--x-install-root=", args.install_root_dir);
continue;
}
if (arg == "--triplet")
{
++arg_begin;

View File

@ -178,7 +178,7 @@ namespace vcpkg
{
if (!pgh->is_installed()) continue;
auto& ipv = ipv_map[pgh->package.spec];
if (pgh->package.feature.empty())
if (!pgh->package.is_feature())
{
ipv.core = pgh.get();
}
@ -206,7 +206,7 @@ namespace vcpkg
for (const std::unique_ptr<StatusParagraph>& pgh : status_db)
{
if (!pgh->is_installed() || !pgh->package.feature.empty())
if (!pgh->is_installed() || pgh->package.is_feature())
{
continue;
}

View File

@ -14,6 +14,7 @@
namespace vcpkg
{
Expected<VcpkgPaths> VcpkgPaths::create(const fs::path& vcpkg_root_dir,
const Optional<fs::path>& install_root_dir,
const Optional<fs::path>& vcpkg_scripts_root_dir,
const std::string& default_vs_path,
const std::vector<std::string>* triplets_dirs)
@ -53,7 +54,7 @@ namespace vcpkg
asPath.u8string());
}
paths.downloads = fs::stdfs::canonical(std::move(asPath), ec);
paths.downloads = fs.canonical(std::move(asPath), ec);
if (ec)
{
return ec;
@ -65,13 +66,17 @@ namespace vcpkg
}
paths.ports = paths.root / "ports";
paths.installed = paths.root / "installed";
if (auto d = install_root_dir.get()) {
paths.installed = fs.absolute(VCPKG_LINE_INFO, std::move(*d));
} else {
paths.installed = paths.root / "installed";
}
paths.triplets = paths.root / "triplets";
paths.community_triplets = paths.triplets / "community";
if (auto scripts_dir = vcpkg_scripts_root_dir.get())
{
if (scripts_dir->empty() || !fs::stdfs::is_directory(*scripts_dir))
if (scripts_dir->empty() || !fs::is_directory(fs.status(VCPKG_LINE_INFO, *scripts_dir)))
{
Metrics::g_metrics.lock()->track_property("error", "Invalid scripts override directory.");
Checks::exit_with_message(
@ -108,11 +113,11 @@ namespace vcpkg
paths.get_filesystem().exists(path),
"Error: Path does not exist '%s'",
triplets_dir);
paths.triplets_dirs.emplace_back(fs::stdfs::canonical(path));
paths.triplets_dirs.emplace_back(fs.canonical(VCPKG_LINE_INFO, path));
}
}
paths.triplets_dirs.emplace_back(fs::stdfs::canonical(paths.triplets));
paths.triplets_dirs.emplace_back(fs::stdfs::canonical(paths.community_triplets));
paths.triplets_dirs.emplace_back(fs.canonical(VCPKG_LINE_INFO, paths.triplets));
paths.triplets_dirs.emplace_back(fs.canonical(VCPKG_LINE_INFO, paths.community_triplets));
return paths;
}

View File

@ -253,6 +253,7 @@
<ClCompile Include="..\src\vcpkg\commands.porthistory.cpp" />
<ClCompile Include="..\src\vcpkg\commands.portsdiff.cpp" />
<ClCompile Include="..\src\vcpkg\commands.search.cpp" />
<ClCompile Include="..\src\vcpkg\commands.setinstalled.cpp" />
<ClCompile Include="..\src\vcpkg\commands.upgrade.cpp" />
<ClCompile Include="..\src\vcpkg\commands.version.cpp" />
<ClCompile Include="..\src\vcpkg\commands.xvsinstances.cpp" />

View File

@ -87,6 +87,9 @@
<ClCompile Include="..\src\vcpkg\commands.search.cpp">
<Filter>Source Files\vcpkg</Filter>
</ClCompile>
<ClCompile Include="..\src\vcpkg\commands.setinstalled.cpp">
<Filter>Source Files\vcpkg</Filter>
</ClCompile>
<ClCompile Include="..\src\vcpkg\commands.version.cpp">
<Filter>Source Files\vcpkg</Filter>
</ClCompile>
@ -441,4 +444,4 @@
<Filter>Header Files\vcpkg</Filter>
</ClInclude>
</ItemGroup>
</Project>
</Project>