aboutsummaryrefslogtreecommitdiff
path: root/toolsrc/src
diff options
context:
space:
mode:
authorVictor Romero <romerosanchezv@gmail.com>2021-01-07 18:04:11 -0800
committerGitHub <noreply@github.com>2021-01-07 18:04:11 -0800
commit2a42024b53ebb512fb5dd63c523338bf26c8489c (patch)
tree3941b7e312699c3ae6e45a036360cabd8a6565f3 /toolsrc/src
parentd717d4119e51d24787ee21a0ae4d8873e0889c93 (diff)
downloadvcpkg-2a42024b53ebb512fb5dd63c523338bf26c8489c.tar.gz
vcpkg-2a42024b53ebb512fb5dd63c523338bf26c8489c.zip
[vcpkg] Add commands to maintain and verify versions db integrity (#14999)
* [vcpkg] Add x-ci-verify-versions command * Code cleanup * Remove port version splitting from x-history * Fix wrong message on success * Parallelize versions file generator * Use cpu_count()/2 to avoid crashes * Check db SHA against local SHA * Check baseline version with x-ci-verify-versions and make baseline generator much faster * Implement x-add-version to update version db files * Better checks for x-add-info and make x-ci-verify-versions silent on success * Use find() instead of [] on maps * Create version file if does not exist * Allow redirection of ports/ and port_versions/ * add test ports * WIP end-to-end tests * Change pats in e2e tests * Fix e2e args * e2e once more * Pass ersions feature flag to e2e * Exit with code 1 if there are errors * Files to test for failure cases * Update test files * Add test for x-add-version * fix redirected ports in last test * Add CI check (use dummy data) * Add feature-flags=versions * Ignore subdirectories inside ports * Add --verify-git-trees switch * [vcpkg] Fix build breaks * [x-ci-verify-versions] PR comments * [x-add-version] PR comments * Fix merge conflicts * Modify tests and pipeline * Baselines should only have version-string * Refactor x-add-version * [vcpkg] Fix help message * [vcpkg] Fix minor warnings * `x-add-version --all` doesn't stop on first failure and reduced default verbosity * [vcpkg] Fix default-baseline * Load file instead of using paths provider * Format * Remove ci test * Add fish port for testing * Update version files * Update fish port to cause SHA discrepancy * Test for discrepancy between local SHA and declared SHA * Missing = operator * Check for error message since x-add-version exits with code 0 * Make x-add-version fail with non-zero exit code if not run with --all Co-authored-by: Robert Schumacher <roschuma@microsoft.com> Co-authored-by: Robert Schumacher <ras0219@outlook.com>
Diffstat (limited to 'toolsrc/src')
-rw-r--r--toolsrc/src/vcpkg-test/arguments.cpp4
-rw-r--r--toolsrc/src/vcpkg-test/commands.cpp2
-rw-r--r--toolsrc/src/vcpkg/commands.add-version.cpp383
-rw-r--r--toolsrc/src/vcpkg/commands.civerifyversions.cpp328
-rw-r--r--toolsrc/src/vcpkg/commands.cpp6
-rw-r--r--toolsrc/src/vcpkg/commands.porthistory.cpp6
-rw-r--r--toolsrc/src/vcpkg/portfileprovider.cpp8
-rw-r--r--toolsrc/src/vcpkg/registries.cpp86
-rw-r--r--toolsrc/src/vcpkg/vcpkgcmdarguments.cpp5
-rw-r--r--toolsrc/src/vcpkg/vcpkgpaths.cpp47
10 files changed, 838 insertions, 37 deletions
diff --git a/toolsrc/src/vcpkg-test/arguments.cpp b/toolsrc/src/vcpkg-test/arguments.cpp
index f5cbf7b15..27fa74b8d 100644
--- a/toolsrc/src/vcpkg-test/arguments.cpp
+++ b/toolsrc/src/vcpkg-test/arguments.cpp
@@ -15,6 +15,7 @@ TEST_CASE ("VcpkgCmdArguments from lowercase argument sequence", "[arguments]")
"C:\\vcpkg",
"--x-scripts-root=C:\\scripts",
"--x-builtin-ports-root=C:\\ports",
+ "--x-builtin-port-versions-dir=C:\\port_versions",
"--debug",
"--sendmetrics",
"--printmetrics",
@@ -27,6 +28,7 @@ TEST_CASE ("VcpkgCmdArguments from lowercase argument sequence", "[arguments]")
REQUIRE(*v.vcpkg_root_dir == "C:\\vcpkg");
REQUIRE(*v.scripts_root_dir == "C:\\scripts");
REQUIRE(*v.builtin_ports_root_dir == "C:\\ports");
+ REQUIRE(*v.builtin_port_versions_dir == "C:\\port_versions");
REQUIRE(v.debug);
REQUIRE(*v.debug.get());
REQUIRE(v.send_metrics);
@@ -49,6 +51,7 @@ TEST_CASE ("VcpkgCmdArguments from uppercase argument sequence", "[arguments]")
"C:\\vcpkg",
"--X-SCRIPTS-ROOT=C:\\scripts",
"--X-BUILTIN-PORTS-ROOT=C:\\ports",
+ "--X-BUILTIN-PORT-VERSIONS-DIR=C:\\port_versions",
"--DEBUG",
"--SENDMETRICS",
"--PRINTMETRICS",
@@ -61,6 +64,7 @@ TEST_CASE ("VcpkgCmdArguments from uppercase argument sequence", "[arguments]")
REQUIRE(*v.vcpkg_root_dir == "C:\\vcpkg");
REQUIRE(*v.scripts_root_dir == "C:\\scripts");
REQUIRE(*v.builtin_ports_root_dir == "C:\\ports");
+ REQUIRE(*v.builtin_port_versions_dir == "C:\\port_versions");
REQUIRE(v.debug);
REQUIRE(*v.debug.get());
REQUIRE(v.send_metrics);
diff --git a/toolsrc/src/vcpkg-test/commands.cpp b/toolsrc/src/vcpkg-test/commands.cpp
index 38346291c..e417505ad 100644
--- a/toolsrc/src/vcpkg-test/commands.cpp
+++ b/toolsrc/src/vcpkg-test/commands.cpp
@@ -60,6 +60,8 @@ TEST_CASE ("get_available_paths_commands works", "[commands]")
"x-history",
"x-package-info",
"x-vsinstances",
+ "x-ci-verify-versions",
+ "x-add-version",
});
}
diff --git a/toolsrc/src/vcpkg/commands.add-version.cpp b/toolsrc/src/vcpkg/commands.add-version.cpp
new file mode 100644
index 000000000..c650fa9dd
--- /dev/null
+++ b/toolsrc/src/vcpkg/commands.add-version.cpp
@@ -0,0 +1,383 @@
+
+#include <vcpkg/base/checks.h>
+#include <vcpkg/base/files.h>
+#include <vcpkg/base/json.h>
+
+#include <vcpkg/commands.add-version.h>
+#include <vcpkg/configuration.h>
+#include <vcpkg/paragraphs.h>
+#include <vcpkg/portfileprovider.h>
+#include <vcpkg/registries.h>
+#include <vcpkg/vcpkgcmdarguments.h>
+#include <vcpkg/vcpkgpaths.h>
+#include <vcpkg/versions.h>
+
+using namespace vcpkg;
+
+namespace
+{
+ using VersionGitTree = std::pair<VersionT, std::string>;
+
+ void insert_version_to_json_object(Json::Object& obj, const VersionT& version)
+ {
+ obj.insert("version-string", Json::Value::string(version.text()));
+ obj.insert("port-version", Json::Value::integer(version.port_version()));
+ }
+
+ static Json::Object serialize_baseline(const std::map<std::string, VersionT, std::less<>>& baseline)
+ {
+ Json::Object port_entries_obj;
+ for (auto&& kv_pair : baseline)
+ {
+ Json::Object baseline_version_obj;
+ insert_version_to_json_object(baseline_version_obj, kv_pair.second);
+ port_entries_obj.insert(kv_pair.first, baseline_version_obj);
+ }
+
+ Json::Object baseline_obj;
+ baseline_obj.insert("default", port_entries_obj);
+ return baseline_obj;
+ }
+
+ static Json::Object serialize_versions(const std::vector<VersionGitTree>& versions)
+ {
+ Json::Array versions_array;
+ for (auto&& version : versions)
+ {
+ Json::Object version_obj;
+ version_obj.insert("git-tree", Json::Value::string(version.second));
+ insert_version_to_json_object(version_obj, version.first);
+ versions_array.push_back(std::move(version_obj));
+ }
+
+ Json::Object output_object;
+ output_object.insert("versions", versions_array);
+ return output_object;
+ }
+
+ static void write_baseline_file(Files::Filesystem& fs,
+ const std::map<std::string, VersionT, std::less<>>& baseline_map,
+ const fs::path& output_path)
+ {
+ auto backup_path = fs::u8path(Strings::concat(fs::u8string(output_path), ".backup"));
+ if (fs.exists(output_path))
+ {
+ fs.rename(output_path, backup_path, VCPKG_LINE_INFO);
+ fs.remove(output_path, VCPKG_LINE_INFO);
+ }
+
+ std::error_code ec;
+ fs.write_contents(output_path, Json::stringify(serialize_baseline(baseline_map), {}), ec);
+ if (ec)
+ {
+ System::printf(
+ System::Color::error, "Error: Couldn't write baseline file to %s.", fs::u8string(output_path));
+ if (fs.exists(backup_path))
+ {
+ fs.rename(backup_path, output_path, VCPKG_LINE_INFO);
+ }
+ Checks::exit_fail(VCPKG_LINE_INFO);
+ }
+ if (fs.exists(backup_path))
+ {
+ fs.remove(backup_path, VCPKG_LINE_INFO);
+ }
+ }
+
+ static void write_versions_file(Files::Filesystem& fs,
+ const std::vector<VersionGitTree>& versions,
+ const fs::path& output_path)
+ {
+ auto backup_path = fs::u8path(Strings::concat(fs::u8string(output_path), ".backup"));
+ if (fs.exists(output_path))
+ {
+ fs.rename(output_path, backup_path, VCPKG_LINE_INFO);
+ fs.remove(output_path, VCPKG_LINE_INFO);
+ }
+
+ std::error_code ec;
+ fs.create_directories(output_path.parent_path(), VCPKG_LINE_INFO);
+ fs.write_contents(output_path, Json::stringify(serialize_versions(versions), {}), ec);
+ if (ec)
+ {
+ System::printf(
+ System::Color::error, "Error: Couldn't write versions file to %s.", fs::u8string(output_path));
+ if (fs.exists(backup_path))
+ {
+ fs.rename(backup_path, output_path, VCPKG_LINE_INFO);
+ }
+ Checks::exit_fail(VCPKG_LINE_INFO);
+ }
+ if (fs.exists(backup_path))
+ {
+ fs.remove(backup_path, VCPKG_LINE_INFO);
+ }
+ }
+
+ static void update_baseline_version(const VcpkgPaths& paths,
+ const std::string& port_name,
+ const VersionT& version,
+ const fs::path& baseline_path,
+ bool print_success)
+ {
+ bool is_new_file = false;
+ auto& fs = paths.get_filesystem();
+ auto baseline_map = [&]() -> std::map<std::string, vcpkg::VersionT, std::less<>> {
+ if (!fs.exists(VCPKG_LINE_INFO, baseline_path))
+ {
+ is_new_file = true;
+ std::map<std::string, vcpkg::VersionT, std::less<>> ret;
+ return ret;
+ }
+ auto maybe_baseline_map = vcpkg::get_builtin_baseline(paths);
+ return maybe_baseline_map.value_or_exit(VCPKG_LINE_INFO);
+ }();
+
+ auto it = baseline_map.find(port_name);
+ if (it != baseline_map.end())
+ {
+ auto& baseline_version = it->second;
+ if (baseline_version == version)
+ {
+ if (print_success)
+ {
+ System::printf(System::Color::success,
+ "Version `%s` is already in `%s`\n",
+ version,
+ fs::u8string(baseline_path));
+ }
+ return;
+ }
+ baseline_version = version;
+ }
+ else
+ {
+ baseline_map.emplace(port_name, version);
+ }
+
+ write_baseline_file(fs, baseline_map, baseline_path);
+ if (print_success)
+ {
+ System::printf(System::Color::success,
+ "Added version `%s` to `%s`%s.\n",
+ version.to_string(),
+ fs::u8string(baseline_path),
+ is_new_file ? " (new file)" : "");
+ }
+ return;
+ }
+
+ static void update_version_db_file(const VcpkgPaths& paths,
+ const std::string& port_name,
+ const VersionT& version,
+ const std::string& git_tree,
+ const fs::path& version_db_file_path,
+ bool overwrite_version,
+ bool print_success,
+ bool keep_going)
+ {
+ auto& fs = paths.get_filesystem();
+ if (!fs.exists(VCPKG_LINE_INFO, version_db_file_path))
+ {
+ std::vector<VersionGitTree> new_entry{{version, git_tree}};
+ write_versions_file(fs, new_entry, version_db_file_path);
+ if (print_success)
+ {
+ System::printf(System::Color::success,
+ "Added version `%s` to `%s` (new file).\n",
+ version.to_string(),
+ fs::u8string(version_db_file_path));
+ }
+ return;
+ }
+
+ auto maybe_versions = get_builtin_versions(paths, port_name);
+ if (auto versions = maybe_versions.get())
+ {
+ const auto& versions_end = versions->end();
+
+ auto found_same_sha = std::find_if(
+ versions->begin(), versions_end, [&](auto&& entry) -> bool { return entry.second == git_tree; });
+ if (found_same_sha != versions_end)
+ {
+ if (found_same_sha->first == version)
+ {
+ if (print_success)
+ {
+ System::printf(System::Color::success,
+ "Version `%s` is already in `%s`\n",
+ version.to_string(),
+ fs::u8string(version_db_file_path));
+ }
+ return;
+ }
+ System::printf(System::Color::warning,
+ "Warning: Local port files SHA is the same as version `%s` in `%s`.\n"
+ "-- SHA: %s\n"
+ "-- Did you remember to commit your changes?\n"
+ "***No files were updated.***\n",
+ found_same_sha->first.to_string(),
+ fs::u8string(version_db_file_path),
+ git_tree);
+ if (keep_going) return;
+ Checks::exit_fail(VCPKG_LINE_INFO);
+ }
+
+ auto it = std::find_if(
+ versions->begin(), versions_end, [&](auto&& entry) -> bool { return entry.first == version; });
+
+ if (it != versions_end)
+ {
+ if (!overwrite_version)
+ {
+ System::printf(System::Color::error,
+ "Error: Local changes detected for %s but no changes to version or port version.\n"
+ "-- Version: %s\n"
+ "-- Old SHA: %s\n"
+ "-- New SHA: %s\n"
+ "-- Did you remember to update the version or port version?\n"
+ "-- Pass `--overwrite-version` to bypass this check.\n"
+ "***No files were updated.***\n",
+ port_name,
+ version.to_string(),
+ it->second,
+ git_tree);
+ if (keep_going) return;
+ Checks::exit_fail(VCPKG_LINE_INFO);
+ }
+
+ it->first = version;
+ it->second = git_tree;
+ }
+ else
+ {
+ versions->insert(versions->begin(), std::make_pair(version, git_tree));
+ }
+
+ write_versions_file(fs, *versions, version_db_file_path);
+ if (print_success)
+ {
+ System::printf(System::Color::success,
+ "Added version `%s` to `%s`.\n",
+ version.to_string(),
+ fs::u8string(version_db_file_path));
+ }
+ return;
+ }
+
+ System::printf(System::Color::error,
+ "Error: Unable to parse versions file %s.\n%s\n",
+ fs::u8string(version_db_file_path),
+ maybe_versions.error());
+ Checks::exit_fail(VCPKG_LINE_INFO);
+ }
+}
+
+namespace vcpkg::Commands::AddVersion
+{
+ static constexpr StringLiteral OPTION_ALL = "all";
+ static constexpr StringLiteral OPTION_OVERWRITE_VERSION = "overwrite-version";
+ static constexpr StringLiteral OPTION_VERBOSE = "verbose";
+
+ const CommandSwitch COMMAND_SWITCHES[] = {
+ {OPTION_ALL, "Process versions for all ports."},
+ {OPTION_OVERWRITE_VERSION, "Overwrite `git-tree` of an existing version."},
+ {OPTION_VERBOSE, "Print success messages instead of just errors."},
+ };
+
+ const CommandStructure COMMAND_STRUCTURE{
+ create_example_string(R"###(x-add-version <port name>)###"),
+ 0,
+ 1,
+ {{COMMAND_SWITCHES}, {}, {}},
+ nullptr,
+ };
+
+ void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths)
+ {
+ auto parsed_args = args.parse_arguments(COMMAND_STRUCTURE);
+ const bool add_all = Util::Sets::contains(parsed_args.switches, OPTION_ALL);
+ const bool overwrite_version = Util::Sets::contains(parsed_args.switches, OPTION_OVERWRITE_VERSION);
+ const bool verbose = Util::Sets::contains(parsed_args.switches, OPTION_VERBOSE);
+
+ auto& fs = paths.get_filesystem();
+ auto baseline_path = paths.builtin_port_versions / fs::u8path("baseline.json");
+ if (!fs.exists(VCPKG_LINE_INFO, baseline_path))
+ {
+ System::printf(
+ System::Color::error, "Error: Couldn't find required file `%s`\n.", fs::u8string(baseline_path));
+ Checks::exit_fail(VCPKG_LINE_INFO);
+ }
+
+ std::vector<std::string> port_names;
+ if (!args.command_arguments.empty())
+ {
+ if (add_all)
+ {
+ System::printf(System::Color::warning,
+ "Warning: Ignoring option `--%s` since a port name argument was provided.\n",
+ OPTION_ALL);
+ }
+ port_names.emplace_back(args.command_arguments[0]);
+ }
+ else
+ {
+ if (!add_all)
+ {
+ System::printf(System::Color::error,
+ "Error: Use option `--%s` to update version files for all ports at once.\n",
+ OPTION_ALL);
+ Checks::exit_fail(VCPKG_LINE_INFO);
+ }
+
+ for (auto&& port_dir : fs::directory_iterator(paths.builtin_ports_directory()))
+ {
+ port_names.emplace_back(fs::u8string(port_dir.path().stem()));
+ }
+ }
+
+ // Get tree-ish from local repository state.
+ auto maybe_git_tree_map = paths.git_get_local_port_treeish_map();
+ auto git_tree_map = maybe_git_tree_map.value_or_exit(VCPKG_LINE_INFO);
+
+ for (auto&& port_name : port_names)
+ {
+ // Get version information of the local port
+ auto maybe_scf = Paragraphs::try_load_port(fs, paths.builtin_ports_directory() / fs::u8path(port_name));
+ if (!maybe_scf.has_value())
+ {
+ if (add_all) continue;
+ System::printf(System::Color::error, "Error: Couldn't load port `%s`.", port_name);
+ Checks::exit_fail(VCPKG_LINE_INFO);
+ }
+
+ const auto& scf = maybe_scf.value_or_exit(VCPKG_LINE_INFO);
+ const auto& versiont = scf->to_versiont();
+
+ auto git_tree_it = git_tree_map.find(port_name);
+ if (git_tree_it == git_tree_map.end())
+ {
+ System::printf(System::Color::warning,
+ "Warning: No local Git SHA was found for port `%s`.\n"
+ "-- Did you remember to commit your changes?\n"
+ "***No files were updated.***\n",
+ port_name);
+ if (add_all) continue;
+ Checks::exit_fail(VCPKG_LINE_INFO);
+ }
+ const auto& git_tree = git_tree_it->second;
+
+ auto port_versions_path =
+ paths.builtin_port_versions / Strings::concat(port_name[0], '-') / Strings::concat(port_name, ".json");
+ update_version_db_file(
+ paths, port_name, versiont, git_tree, port_versions_path, overwrite_version, verbose, add_all);
+ update_baseline_version(paths, port_name, versiont, baseline_path, verbose);
+ }
+ Checks::exit_success(VCPKG_LINE_INFO);
+ }
+
+ void AddVersionCommand::perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) const
+ {
+ AddVersion::perform_and_exit(args, paths);
+ }
+} \ No newline at end of file
diff --git a/toolsrc/src/vcpkg/commands.civerifyversions.cpp b/toolsrc/src/vcpkg/commands.civerifyversions.cpp
new file mode 100644
index 000000000..a0de254c5
--- /dev/null
+++ b/toolsrc/src/vcpkg/commands.civerifyversions.cpp
@@ -0,0 +1,328 @@
+#include <vcpkg/base/checks.h>
+#include <vcpkg/base/files.h>
+#include <vcpkg/base/json.h>
+#include <vcpkg/base/system.debug.h>
+
+#include <vcpkg/commands.civerifyversions.h>
+#include <vcpkg/paragraphs.h>
+#include <vcpkg/registries.h>
+#include <vcpkg/sourceparagraph.h>
+#include <vcpkg/vcpkgcmdarguments.h>
+#include <vcpkg/vcpkgpaths.h>
+#include <vcpkg/versiondeserializers.h>
+
+namespace
+{
+ using namespace vcpkg;
+
+}
+
+namespace vcpkg::Commands::CIVerifyVersions
+{
+ static constexpr StringLiteral OPTION_EXCLUDE = "exclude";
+ static constexpr StringLiteral OPTION_VERBOSE = "verbose";
+ static constexpr StringLiteral OPTION_VERIFY_GIT_TREES = "verify-git-trees";
+
+ static constexpr CommandSwitch VERIFY_VERSIONS_SWITCHES[]{
+ {OPTION_VERBOSE, "Print result for each port instead of just errors."},
+ {OPTION_VERIFY_GIT_TREES, "Verify that each git tree object matches its declared version (this is very slow)"},
+ };
+
+ static constexpr CommandSetting VERIFY_VERSIONS_SETTINGS[] = {
+ {OPTION_EXCLUDE, "Comma-separated list of ports to skip"},
+ };
+
+ const CommandStructure COMMAND_STRUCTURE{
+ create_example_string(R"###(x-ci-verify-versions)###"),
+ 0,
+ SIZE_MAX,
+ {{VERIFY_VERSIONS_SWITCHES}, {VERIFY_VERSIONS_SETTINGS}, {}},
+ nullptr,
+ };
+
+ static ExpectedS<std::string> verify_version_in_db(const VcpkgPaths& paths,
+ const std::map<std::string, VersionT, std::less<>> baseline,
+ const std::string& port_name,
+ const fs::path& port_path,
+ const fs::path& versions_file_path,
+ const std::string& local_git_tree,
+ bool verify_git_trees)
+ {
+ auto maybe_versions = vcpkg::get_builtin_versions(paths, port_name);
+ if (!maybe_versions.has_value())
+ {
+ return {
+ Strings::format(
+ "Error: Cannot parse `%s`.\n\t%s", fs::u8string(versions_file_path), maybe_versions.error()),
+ expected_right_tag,
+ };
+ }
+
+ const auto& versions = maybe_versions.value_or_exit(VCPKG_LINE_INFO);
+ if (versions.empty())
+ {
+ return {
+ Strings::format("Error: File `%s` contains no versions.", fs::u8string(versions_file_path)),
+ expected_right_tag,
+ };
+ }
+
+ if (verify_git_trees)
+ {
+ for (auto&& version_entry : versions)
+ {
+ bool version_ok = false;
+ for (const std::string& control_file : {"CONTROL", "vcpkg.json"})
+ {
+ auto treeish = Strings::concat(version_entry.second, ':', control_file);
+ auto maybe_file = paths.git_show(Strings::concat(treeish), paths.root / fs::u8path(".git"));
+ if (!maybe_file.has_value()) continue;
+
+ const auto& file = maybe_file.value_or_exit(VCPKG_LINE_INFO);
+ auto maybe_scf = Paragraphs::try_load_port_text(file, treeish, control_file == "vcpkg.json");
+ if (!maybe_scf.has_value())
+ {
+ return {
+ Strings::format("Error: Unable to parse `%s` used in version `%s`.\n%s\n",
+ treeish,
+ version_entry.first.to_string(),
+ maybe_scf.error()->error),
+ expected_right_tag,
+ };
+ }
+
+ const auto& scf = maybe_scf.value_or_exit(VCPKG_LINE_INFO);
+ auto&& git_tree_version = scf.get()->to_versiont();
+ if (version_entry.first != git_tree_version)
+ {
+ return {
+ Strings::format("Error: Version in git-tree `%s` does not match version in file "
+ "`%s`.\n\tgit-tree version: %s\n\t file version: %s\n",
+ version_entry.second,
+ fs::u8string(versions_file_path),
+ git_tree_version.to_string(),
+ version_entry.first.to_string()),
+ expected_right_tag,
+ };
+ }
+ version_ok = true;
+ break;
+ }
+
+ if (!version_ok)
+ {
+ return {
+ Strings::format("Error: The git-tree `%s` for version `%s` in `%s` does not contain a "
+ "CONTROL file or vcpkg.json file.",
+ version_entry.second,
+ version_entry.first,
+ fs::u8string(versions_file_path)),
+ expected_right_tag,
+ };
+ }
+ }
+ }
+
+ const auto& top_entry = versions.front();
+
+ auto maybe_scf = Paragraphs::try_load_port(paths.get_filesystem(), port_path);
+ if (!maybe_scf.has_value())
+ {
+ return {
+ Strings::format("Error: Cannot load port `%s`.\n\t%s", port_name, maybe_scf.error()->error),
+ expected_right_tag,
+ };
+ }
+
+ const auto found_version = maybe_scf.value_or_exit(VCPKG_LINE_INFO)->to_versiont();
+ if (top_entry.first != found_version)
+ {
+ auto versions_end = versions.end();
+ auto it = std::find_if(
+ versions.begin(), versions_end, [&](auto&& entry) { return entry.first == found_version; });
+ if (it != versions_end)
+ {
+ return {
+ Strings::format("Error: Version `%s` found but is not the top entry in `%s`.",
+ found_version,
+ fs::u8string(versions_file_path)),
+ expected_right_tag,
+ };
+ }
+ else
+ {
+ return {
+ Strings::format(
+ "Error: Version `%s` not found in `%s`.", found_version, fs::u8string(versions_file_path)),
+ expected_right_tag,
+ };
+ }
+ }
+
+ if (local_git_tree != top_entry.second)
+ {
+ return {
+ Strings::format("Error: Git tree-ish object for version `%s` in `%s` does not match local port files.\n"
+ "\tLocal SHA: %s\n"
+ "\t File SHA: %s",
+ found_version,
+ fs::u8string(versions_file_path),
+ local_git_tree,
+ top_entry.second),
+ expected_right_tag,
+ };
+ }
+
+ auto maybe_baseline = baseline.find(port_name);
+ if (maybe_baseline == baseline.end())
+ {
+ return {
+ Strings::format("Error: Couldn't find baseline version for port `%s`.", port_name),
+ expected_right_tag,
+ };
+ }
+
+ auto&& baseline_version = maybe_baseline->second;
+ if (baseline_version != top_entry.first)
+ {
+ return {
+ Strings::format("Error: The baseline version for port `%s` doesn't match the latest version.\n"
+ "\tBaseline version: %s\n"
+ "\t Latest version: %s (%s)",
+ port_name,
+ baseline_version,
+ top_entry.first,
+ fs::u8string(versions_file_path)),
+ expected_right_tag,
+ };
+ }
+
+ if (local_git_tree != top_entry.second)
+ {
+ return {
+ Strings::format("Error: Git tree-ish object for version `%s` in `%s` does not match local port files.\n"
+ "\tLocal SHA: %s\n"
+ "\t File SHA: %s",
+ found_version,
+ fs::u8string(versions_file_path),
+ local_git_tree,
+ top_entry.second),
+ expected_right_tag,
+ };
+ }
+
+ return {
+ Strings::format("OK: %s\t%s -> %s\n", top_entry.second, port_name, top_entry.first),
+ expected_left_tag,
+ };
+ }
+
+ void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths)
+ {
+ auto parsed_args = args.parse_arguments(COMMAND_STRUCTURE);
+
+ bool verbose = Util::Sets::contains(parsed_args.switches, OPTION_VERBOSE);
+ bool verify_git_trees = Util::Sets::contains(parsed_args.switches, OPTION_VERIFY_GIT_TREES);
+
+ std::set<std::string> exclusion_set;
+ auto settings = parsed_args.settings;
+ auto it_exclusions = settings.find(OPTION_EXCLUDE);
+ if (it_exclusions != settings.end())
+ {
+ auto exclusions = Strings::split(it_exclusions->second, ',');
+ exclusion_set.insert(exclusions.begin(), exclusions.end());
+ }
+
+ auto maybe_port_git_tree_map = paths.git_get_local_port_treeish_map();
+ Checks::check_exit(VCPKG_LINE_INFO,
+ maybe_port_git_tree_map.has_value(),
+ "Error: Failed to obtain git treeish objects for local ports.\n%s",
+ maybe_port_git_tree_map.error());
+ auto port_git_tree_map = maybe_port_git_tree_map.value_or_exit(VCPKG_LINE_INFO);
+
+ // Baseline is required.
+ auto baseline = get_builtin_baseline(paths).value_or_exit(VCPKG_LINE_INFO);
+ auto& fs = paths.get_filesystem();
+ std::set<std::string> errors;
+ for (const auto& dir : fs::directory_iterator(paths.builtin_ports_directory()))
+ {
+ const auto& port_path = dir.path();
+
+ auto&& port_name = fs::u8string(port_path.stem());
+ if (Util::Sets::contains(exclusion_set, port_name))
+ {
+ if (verbose) System::printf("SKIP: %s\n", port_name);
+ continue;
+ }
+ auto git_tree_it = port_git_tree_map.find(port_name);
+ if (git_tree_it == port_git_tree_map.end())
+ {
+ System::printf(System::Color::error, "FAIL: %s\n", port_name);
+ errors.emplace(Strings::format("Error: Missing local git tree object for port `%s`.", port_name));
+ continue;
+ }
+ auto git_tree = git_tree_it->second;
+
+ auto control_path = port_path / fs::u8path("CONTROL");
+ auto manifest_path = port_path / fs::u8path("vcpkg.json");
+ auto manifest_exists = fs.exists(manifest_path);
+ auto control_exists = fs.exists(control_path);
+
+ if (manifest_exists && control_exists)
+ {
+ System::printf(System::Color::error, "FAIL: %s\n", port_name);
+ errors.emplace(
+ Strings::format("Error: Both a manifest file and a CONTROL file exist in port directory: %s",
+ fs::u8string(port_path)));
+ continue;
+ }
+
+ if (!manifest_exists && !control_exists)
+ {
+ System::printf(System::Color::error, "FAIL: %s\n", port_name);
+ errors.emplace(Strings::format("Error: No manifest file or CONTROL file exist in port directory: %s",
+ fs::u8string(port_path)));
+ continue;
+ }
+
+ auto versions_file_path =
+ paths.builtin_port_versions / Strings::concat(port_name[0], '-') / Strings::concat(port_name, ".json");
+ if (!fs.exists(versions_file_path))
+ {
+ System::printf(System::Color::error, "FAIL: %s\n", port_name);
+ errors.emplace(Strings::format("Error: Missing versions file for `%s`. Expected at `%s`.",
+ port_name,
+ fs::u8string(versions_file_path)));
+ continue;
+ }
+
+ auto maybe_ok = verify_version_in_db(
+ paths, baseline, port_name, port_path, versions_file_path, git_tree, verify_git_trees);
+
+ if (!maybe_ok.has_value())
+ {
+ System::printf(System::Color::error, "FAIL: %s\n", port_name);
+ errors.emplace(maybe_ok.error());
+ continue;
+ }
+
+ if (verbose) System::printf("%s", maybe_ok.value_or_exit(VCPKG_LINE_INFO));
+ }
+
+ if (!errors.empty())
+ {
+ System::print2(System::Color::error, "Found the following errors:\n");
+ for (auto&& error : errors)
+ {
+ System::printf(System::Color::error, "%s\n", error);
+ }
+ Checks::exit_fail(VCPKG_LINE_INFO);
+ }
+ Checks::exit_success(VCPKG_LINE_INFO);
+ }
+
+ void CIVerifyVersionsCommand::perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) const
+ {
+ CIVerifyVersions::perform_and_exit(args, paths);
+ }
+} \ No newline at end of file
diff --git a/toolsrc/src/vcpkg/commands.cpp b/toolsrc/src/vcpkg/commands.cpp
index 6e87c1774..19b5bdcde 100644
--- a/toolsrc/src/vcpkg/commands.cpp
+++ b/toolsrc/src/vcpkg/commands.cpp
@@ -1,11 +1,13 @@
#include <vcpkg/base/system.print.h>
#include <vcpkg/build.h>
+#include <vcpkg/commands.add-version.h>
#include <vcpkg/commands.autocomplete.h>
#include <vcpkg/commands.buildexternal.h>
#include <vcpkg/commands.cache.h>
#include <vcpkg/commands.ci.h>
#include <vcpkg/commands.ciclean.h>
+#include <vcpkg/commands.civerifyversions.h>
#include <vcpkg/commands.contact.h>
#include <vcpkg/commands.create.h>
#include <vcpkg/commands.dependinfo.h>
@@ -73,6 +75,8 @@ namespace vcpkg::Commands
static const PortHistory::PortHistoryCommand porthistory{};
static const X_VSInstances::VSInstancesCommand vsinstances{};
static const FormatManifest::FormatManifestCommand format_manifest{};
+ static const CIVerifyVersions::CIVerifyVersionsCommand ci_verify_versions{};
+ static const AddVersion::AddVersionCommand add_version{};
static std::vector<PackageNameAndFunction<const PathsCommand*>> t = {
{"/?", &help},
@@ -94,6 +98,8 @@ namespace vcpkg::Commands
{"x-history", &porthistory},
{"x-vsinstances", &vsinstances},
{"format-manifest", &format_manifest},
+ {"x-ci-verify-versions", &ci_verify_versions},
+ {"x-add-version", &add_version},
};
return t;
}
diff --git a/toolsrc/src/vcpkg/commands.porthistory.cpp b/toolsrc/src/vcpkg/commands.porthistory.cpp
index 92d782c36..894b46b6e 100644
--- a/toolsrc/src/vcpkg/commands.porthistory.cpp
+++ b/toolsrc/src/vcpkg/commands.porthistory.cpp
@@ -52,12 +52,6 @@ namespace vcpkg::Commands::PortHistory
return run_git_command_inner(paths, dot_git_dir, work_dir, cmd);
}
- bool is_date(const std::string& version_string)
- {
- std::regex re("^([0-9]{4,}[-][0-9]{2}[-][0-9]{2})$");
- return std::regex_match(version_string, re);
- }
-
vcpkg::Optional<HistoryVersion> get_version_from_text(const std::string& text,
const std::string& git_tree,
const std::string& commit_id,
diff --git a/toolsrc/src/vcpkg/portfileprovider.cpp b/toolsrc/src/vcpkg/portfileprovider.cpp
index 05c5109c4..f455c8233 100644
--- a/toolsrc/src/vcpkg/portfileprovider.cpp
+++ b/toolsrc/src/vcpkg/portfileprovider.cpp
@@ -210,7 +210,13 @@ namespace vcpkg::PortFileProvider
if (port)
{
- auto port_path = port->get_path_to_version(paths, port_version).value_or_exit(VCPKG_LINE_INFO);
+ auto maybe_port_path = port->get_path_to_version(paths, port_version);
+ if (!maybe_port_path.has_value())
+ {
+ return std::move(maybe_port_path.error());
+ }
+ auto port_path = std::move(maybe_port_path).value_or_exit(VCPKG_LINE_INFO);
+
auto maybe_scfl = Paragraphs::try_load_port(fs, port_path);
if (auto p = maybe_scfl.get())
{
diff --git a/toolsrc/src/vcpkg/registries.cpp b/toolsrc/src/vcpkg/registries.cpp
index 22ac26fd9..31f3078de 100644
--- a/toolsrc/src/vcpkg/registries.cpp
+++ b/toolsrc/src/vcpkg/registries.cpp
@@ -195,7 +195,8 @@ namespace
std::unique_ptr<RegistryEntry> BuiltinRegistry::get_port_entry(const VcpkgPaths& paths, StringView port_name) const
{
auto versions_path = paths.builtin_port_versions / relative_path_to_versions(port_name);
- if (paths.get_feature_flags().registries && paths.get_filesystem().exists(versions_path))
+ if ((paths.get_feature_flags().registries || paths.get_feature_flags().versions) &&
+ paths.get_filesystem().exists(versions_path))
{
auto maybe_version_entries =
load_versions_file(paths.get_filesystem(), VersionDbType::Git, paths.builtin_port_versions, port_name);
@@ -244,15 +245,16 @@ namespace
return nullptr;
}
- Baseline parse_builtin_baseline(const VcpkgPaths& paths, StringView baseline_identifier)
+ ExpectedS<Baseline> try_parse_builtin_baseline(const VcpkgPaths& paths, StringView baseline_identifier)
{
auto path_to_baseline = paths.builtin_port_versions / fs::u8path("baseline.json");
auto res_baseline = load_baseline_versions(paths, path_to_baseline, baseline_identifier);
if (!res_baseline.has_value())
{
- Checks::exit_with_message(VCPKG_LINE_INFO, res_baseline.error());
+ return res_baseline.error();
}
+
auto opt_baseline = res_baseline.get();
if (auto p = opt_baseline->get())
{
@@ -261,31 +263,29 @@ namespace
if (baseline_identifier.size() == 0)
{
- return {};
+ return {{}, expected_left_tag};
}
if (baseline_identifier == "default")
{
- Checks::exit_with_message(VCPKG_LINE_INFO,
- "Couldn't find explicitly specified baseline `\"default\"` in the baseline file.",
- baseline_identifier);
+ return Strings::format("Couldn't find explicitly specified baseline `\"default\"` in the baseline file.",
+ baseline_identifier);
}
// attempt to check out the baseline:
auto maybe_path = get_git_baseline_json_path(paths, baseline_identifier);
if (!maybe_path.has_value())
{
- Checks::exit_with_message(VCPKG_LINE_INFO,
- "Couldn't find explicitly specified baseline `\"%s\"` in the baseline file, "
- "and there was no baseline at that commit or the commit didn't exist.\n%s",
- baseline_identifier,
- maybe_path.error());
+ return Strings::format("Couldn't find explicitly specified baseline `\"%s\"` in the baseline file, "
+ "and there was no baseline at that commit or the commit didn't exist.\n%s",
+ baseline_identifier,
+ maybe_path.error());
}
res_baseline = load_baseline_versions(paths, *maybe_path.get());
if (!res_baseline.has_value())
{
- Checks::exit_with_message(VCPKG_LINE_INFO, res_baseline.error());
+ return res_baseline.error();
}
opt_baseline = res_baseline.get();
if (auto p = opt_baseline->get())
@@ -293,14 +293,19 @@ namespace
return std::move(*p);
}
- Checks::exit_with_message(VCPKG_LINE_INFO,
- "Couldn't find explicitly specified baseline `\"%s\"` in the baseline "
- "file, and the `\"default\"` baseline does not exist at that commit.",
- baseline_identifier);
+ return Strings::format("Couldn't find explicitly specified baseline `\"%s\"` in the baseline "
+ "file, and the `\"default\"` baseline does not exist at that commit.",
+ baseline_identifier);
+ }
+
+ Baseline parse_builtin_baseline(const VcpkgPaths& paths, StringView baseline_identifier)
+ {
+ auto maybe_baseline = try_parse_builtin_baseline(paths, baseline_identifier);
+ return maybe_baseline.value_or_exit(VCPKG_LINE_INFO);
}
Optional<VersionT> BuiltinRegistry::get_baseline_version(const VcpkgPaths& paths, StringView port_name) const
{
- if (paths.get_feature_flags().registries)
+ if (!m_baseline_identifier.empty())
{
const auto& baseline = m_baseline.get(
[this, &paths]() -> Baseline { return parse_builtin_baseline(paths, m_baseline_identifier); });
@@ -311,22 +316,25 @@ namespace
return it->second;
}
}
-
- // fall back to using the ports directory version
- auto maybe_scf =
- Paragraphs::try_load_port(paths.get_filesystem(), paths.builtin_ports_directory() / fs::u8path(port_name));
- if (auto pscf = maybe_scf.get())
+ else
{
- auto& scf = *pscf;
- return scf->to_versiont();
+ // fall back to using the ports directory version
+ auto maybe_scf = Paragraphs::try_load_port(paths.get_filesystem(),
+ paths.builtin_ports_directory() / fs::u8path(port_name));
+ if (auto pscf = maybe_scf.get())
+ {
+ auto& scf = *pscf;
+ return scf->to_versiont();
+ }
+ Debug::print("Failed to load port `", port_name, "` from the ports tree: ", maybe_scf.error()->error, "\n");
}
- Debug::print("Failed to load port `", port_name, "` from the ports tree: ", maybe_scf.error()->error, "\n");
return nullopt;
}
void BuiltinRegistry::get_all_port_names(std::vector<std::string>& out, const VcpkgPaths& paths) const
{
- if (paths.get_feature_flags().registries && paths.get_filesystem().exists(paths.builtin_port_versions))
+ if ((paths.get_feature_flags().registries || paths.get_feature_flags().versions) &&
+ paths.get_filesystem().exists(paths.builtin_port_versions))
{
load_all_port_names_from_port_versions(out, paths, paths.builtin_port_versions);
}
@@ -436,8 +444,9 @@ namespace
auto& name = scfl->source_control_file->core_paragraph->name;
return Strings::format(
"Error: no version entry for %s at version %s.\n"
- "We are currently using the version in the ports tree, since no %s.json was found in port_versions.",
+ "We are currently using the version in the ports tree (%s), since no %s.json was found in /port_versions.",
name,
+ version.to_string(),
scfl->to_versiont().to_string(),
name);
}
@@ -925,7 +934,7 @@ namespace vcpkg
System::print2(System::Color::warning,
"Warning: when using the registries feature, one should not use `\"$x-default-baseline\"` "
"to set the baseline.\n",
- " Instead, use the \"baseline\" field of the registry.");
+ " Instead, use the \"baseline\" field of the registry.\n");
}
for (auto& reg : registries_)
@@ -954,4 +963,23 @@ namespace vcpkg
// default_registry_ is not a BuiltinRegistry
return true;
}
+
+ ExpectedS<std::vector<std::pair<VersionT, std::string>>> get_builtin_versions(const VcpkgPaths& paths,
+ StringView port_name)
+ {
+ auto maybe_versions =
+ load_versions_file(paths.get_filesystem(), VersionDbType::Git, paths.builtin_port_versions, port_name);
+ if (auto pversions = maybe_versions.get())
+ {
+ return Util::fmap(
+ *pversions, [](auto&& entry) -> auto { return std::make_pair(entry.version, entry.git_tree); });
+ }
+
+ return maybe_versions.error();
+ }
+
+ ExpectedS<std::map<std::string, VersionT, std::less<>>> get_builtin_baseline(const VcpkgPaths& paths)
+ {
+ return try_parse_builtin_baseline(paths, "default");
+ }
}
diff --git a/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp
index 0310a01ae..648b2065c 100644
--- a/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp
+++ b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp
@@ -628,6 +628,10 @@ 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(BUILTIN_PORTS_ROOT_DIR_ARG, "=", "<path>"),
+ "(Experimental) Specify the packages root directory");
+ table.format(opt(BUILTIN_PORT_VERSIONS_DIR_ARG, "=", "<path>"),
+ "(Experimental) Specify the versions root directory");
table.format(opt(JSON_SWITCH, "", ""), "(Experimental) Request JSON output");
}
@@ -916,6 +920,7 @@ namespace vcpkg
constexpr StringLiteral VcpkgCmdArguments::PACKAGES_ROOT_DIR_ARG;
constexpr StringLiteral VcpkgCmdArguments::SCRIPTS_ROOT_DIR_ARG;
constexpr StringLiteral VcpkgCmdArguments::BUILTIN_PORTS_ROOT_DIR_ARG;
+ constexpr StringLiteral VcpkgCmdArguments::BUILTIN_PORT_VERSIONS_DIR_ARG;
constexpr StringLiteral VcpkgCmdArguments::DEFAULT_VISUAL_STUDIO_PATH_ENV;
diff --git a/toolsrc/src/vcpkg/vcpkgpaths.cpp b/toolsrc/src/vcpkg/vcpkgpaths.cpp
index a41258761..27b7cfe02 100644
--- a/toolsrc/src/vcpkg/vcpkgpaths.cpp
+++ b/toolsrc/src/vcpkg/vcpkgpaths.cpp
@@ -367,7 +367,6 @@ If you wish to silence this error and use classic mode, you can:
vcpkg_dir_info = vcpkg_dir / fs::u8path("info");
vcpkg_dir_updates = vcpkg_dir / fs::u8path("updates");
- // Versioning paths
const auto versioning_tmp = buildtrees / fs::u8path("versioning_tmp");
const auto versioning_output = buildtrees / fs::u8path("versioning");
@@ -564,6 +563,52 @@ If you wish to silence this error and use classic mode, you can:
}
}
+ ExpectedS<std::map<std::string, std::string, std::less<>>> VcpkgPaths::git_get_local_port_treeish_map() const
+ {
+ const auto local_repo = this->root / fs::u8path(".git");
+ const auto path_with_separator =
+ Strings::concat(fs::u8string(this->builtin_ports_directory()), Files::preferred_separator);
+ const auto git_cmd = git_cmd_builder(*this, local_repo, this->root)
+ .string_arg("ls-tree")
+ .string_arg("-d")
+ .string_arg("HEAD")
+ .string_arg("--")
+ .path_arg(path_with_separator)
+ .extract();
+
+ auto output = System::cmd_execute_and_capture_output(git_cmd);
+ if (output.exit_code != 0)
+ return Strings::format("Error: Couldn't get local treeish objects for ports.\n%s", output.output);
+
+ std::map<std::string, std::string, std::less<>> ret;
+ auto lines = Strings::split(output.output, '\n');
+ // The first line of the output is always the parent directory itself.
+ for (auto line : lines)
+ {
+ // The default output comes in the format:
+ // <mode> SP <type> SP <object> TAB <file>
+ auto split_line = Strings::split(line, '\t');
+ if (split_line.size() != 2)
+ return Strings::format(
+ "Error: Unexpected output from command `%s`. Couldn't split by `\\t`.\n%s", git_cmd, line);
+
+ auto file_info_section = Strings::split(split_line[0], ' ');
+ if (file_info_section.size() != 3)
+ return Strings::format(
+ "Error: Unexepcted output from command `%s`. Couldn't split by ` `.\n%s", git_cmd, line);
+
+ const auto index = split_line[1].find_last_of('/');
+ if (index == std::string::npos)
+ {
+ return Strings::format(
+ "Error: Unexpected output from command `%s`. Couldn't split by `/`.\n%s", git_cmd, line);
+ }
+
+ ret.emplace(split_line[1].substr(index + 1), file_info_section.back());
+ }
+ return ret;
+ }
+
void VcpkgPaths::git_checkout_object(const VcpkgPaths& paths,
StringView git_object,
const fs::path& local_repo,