diff --git a/include/vcpkg/base/hash.h b/include/vcpkg/base/hash.h index 3c3903e067..656be9bd27 100644 --- a/include/vcpkg/base/hash.h +++ b/include/vcpkg/base/hash.h @@ -17,6 +17,7 @@ namespace vcpkg::Hash struct Hasher { + void add_bytes(StringView data); virtual void add_bytes(const void* start, const void* end) noexcept = 0; // one may only call this once before calling `clear()` or the dtor diff --git a/include/vcpkg/commands.setinstalled.h b/include/vcpkg/commands.setinstalled.h index bb48b1d5e9..1ab72eb003 100644 --- a/include/vcpkg/commands.setinstalled.h +++ b/include/vcpkg/commands.setinstalled.h @@ -7,15 +7,15 @@ namespace vcpkg::Commands::SetInstalled { extern const CommandStructure COMMAND_STRUCTURE; - void perform_and_exit_ex(const VcpkgCmdArguments& args, - const VcpkgPaths& paths, - const PortFileProvider::PathsPortFileProvider& provider, - BinaryCache& binary_cache, - const CMakeVars::CMakeVarProvider& cmake_vars, - Dependencies::ActionPlan action_plan, - DryRun dry_run, - const Optional& pkgsconfig_path, - Triplet host_triplet); + void perform_ex(const VcpkgCmdArguments& args, + const VcpkgPaths& paths, + const PortFileProvider::PathsPortFileProvider& provider, + BinaryCache& binary_cache, + const CMakeVars::CMakeVarProvider& cmake_vars, + Dependencies::ActionPlan action_plan, + DryRun dry_run, + const Optional& pkgsconfig_path, + Triplet host_triplet); void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, Triplet default_triplet, diff --git a/include/vcpkg/installedpaths.h b/include/vcpkg/installedpaths.h index 94828d8d1f..1e8f9c9b5b 100644 --- a/include/vcpkg/installedpaths.h +++ b/include/vcpkg/installedpaths.h @@ -20,6 +20,7 @@ namespace vcpkg Path vcpkg_dir_info() const { return vcpkg_dir() / "info"; } Path vcpkg_dir_updates() const { return vcpkg_dir() / "updates"; } Path lockfile_path() const { return vcpkg_dir() / "vcpkg-lock.json"; } + Path hashfile_path() const { return vcpkg_dir() / "hash.txt"; } Path triplet_dir(Triplet t) const { return m_root / t.canonical_name(); } Path share_dir(const PackageSpec& p) const { return triplet_dir(p.triplet()) / "share" / p.name(); } Path usage_file(const PackageSpec& p) const { return share_dir(p) / "usage"; } diff --git a/include/vcpkg/vcpkgpaths.h b/include/vcpkg/vcpkgpaths.h index 680cda147e..7910ed0da9 100644 --- a/include/vcpkg/vcpkgpaths.h +++ b/include/vcpkg/vcpkgpaths.h @@ -159,6 +159,7 @@ namespace vcpkg const std::string& get_triplet_info(const Build::AbiInfo& abi_info) const; const Build::CompilerInfo& get_compiler_info(const Build::AbiInfo& abi_info) const; bool manifest_mode_enabled() const { return get_manifest().has_value(); } + std::string get_configuration_hash() const; const FeatureFlagSettings& get_feature_flags() const; void track_feature_flag_metrics() const; diff --git a/locales/messages.json b/locales/messages.json index 3d0a404e1d..207f69c999 100644 --- a/locales/messages.json +++ b/locales/messages.json @@ -54,6 +54,7 @@ "ProcessorArchitectureMissing": "The required environment variable %PROCESSOR_ARCHITECTURE% is missing.", "ProcessorArchitectureW6432Malformed": "Failed to parse %PROCESSOR_ARCHITEW6432% ({value}) as a valid CPU architecture. Falling back to %PROCESSOR_ARCHITECTURE%.", "SeeURL": "See {url} for more information.", + "StampNotChanged": "Installation skipped. Everything seems to be installed. To disable this check, don't pass the option `--{value}` to vcpkg.", "UnsupportedSystemName": "Error: Could not map VCPKG_CMAKE_SYSTEM_NAME '{value}' to a vcvarsall platform. Supported system names are '', 'Windows' and 'WindowsStore'.", "UnsupportedToolchain": "Error: in triplet {triplet}: Unable to find a valid toolchain combination.\n The requested target architecture was {value}\n The selected Visual Studio instance is at {path}\n The available toolchain combinations are {list}\n", "UsingManifestAt": "Using manifest file at {path}.", diff --git a/src/vcpkg/base/hash.cpp b/src/vcpkg/base/hash.cpp index a9f640ba00..112e8d92c9 100644 --- a/src/vcpkg/base/hash.cpp +++ b/src/vcpkg/base/hash.cpp @@ -43,6 +43,8 @@ namespace vcpkg::Hash } } + void Hasher::add_bytes(StringView data) { add_bytes(data.data(), data.data() + data.size()); } + template auto top_bits(UIntTy x) -> std::enable_if_t::value, uchar> { diff --git a/src/vcpkg/commands.setinstalled.cpp b/src/vcpkg/commands.setinstalled.cpp index b792014607..97f851f61e 100644 --- a/src/vcpkg/commands.setinstalled.cpp +++ b/src/vcpkg/commands.setinstalled.cpp @@ -35,15 +35,15 @@ namespace vcpkg::Commands::SetInstalled nullptr, }; - void perform_and_exit_ex(const VcpkgCmdArguments& args, - const VcpkgPaths& paths, - const PortFileProvider::PathsPortFileProvider& provider, - BinaryCache& binary_cache, - const CMakeVars::CMakeVarProvider& cmake_vars, - Dependencies::ActionPlan action_plan, - DryRun dry_run, - const Optional& maybe_pkgsconfig, - Triplet host_triplet) + void perform_ex(const VcpkgCmdArguments& args, + const VcpkgPaths& paths, + const PortFileProvider::PathsPortFileProvider& provider, + BinaryCache& binary_cache, + const CMakeVars::CMakeVarProvider& cmake_vars, + Dependencies::ActionPlan action_plan, + DryRun dry_run, + const Optional& maybe_pkgsconfig, + Triplet host_triplet) { auto& fs = paths.get_filesystem(); @@ -136,8 +136,6 @@ namespace vcpkg::Commands::SetInstalled Install::print_usage_information(it->get()->package, printed_usages, fs, paths.installed()); } } - - Checks::exit_success(VCPKG_LINE_INFO); } void perform_and_exit(const VcpkgCmdArguments& args, @@ -178,15 +176,16 @@ namespace vcpkg::Commands::SetInstalled action.build_options = Build::default_build_package_options; } - perform_and_exit_ex(args, - paths, - provider, - binary_cache, - *cmake_vars, - std::move(action_plan), - dry_run ? DryRun::Yes : DryRun::No, - pkgsconfig, - host_triplet); + perform_ex(args, + paths, + provider, + binary_cache, + *cmake_vars, + std::move(action_plan), + dry_run ? DryRun::Yes : DryRun::No, + pkgsconfig, + host_triplet); + Checks::exit_success(VCPKG_LINE_INFO); } void SetInstalledCommand::perform_and_exit(const VcpkgCmdArguments& args, diff --git a/src/vcpkg/install.cpp b/src/vcpkg/install.cpp index 592c018458..0d43a72065 100644 --- a/src/vcpkg/install.cpp +++ b/src/vcpkg/install.cpp @@ -510,6 +510,7 @@ namespace vcpkg::Install static constexpr StringLiteral OPTION_RECURSE = "recurse"; static constexpr StringLiteral OPTION_KEEP_GOING = "keep-going"; static constexpr StringLiteral OPTION_EDITABLE = "editable"; + static constexpr StringLiteral OPTION_CHECK_STAMP = "check-stamp"; static constexpr StringLiteral OPTION_XUNIT = "x-xunit"; static constexpr StringLiteral OPTION_USE_ARIA2 = "x-use-aria2"; static constexpr StringLiteral OPTION_CLEAN_AFTER_BUILD = "clean-after-build"; @@ -523,7 +524,7 @@ namespace vcpkg::Install static constexpr StringLiteral OPTION_ENFORCE_PORT_CHECKS = "enforce-port-checks"; static constexpr StringLiteral OPTION_ALLOW_UNSUPPORTED_PORT = "allow-unsupported"; - static constexpr std::array INSTALL_SWITCHES = {{ + static constexpr std::array INSTALL_SWITCHES = {{ {OPTION_DRY_RUN, "Do not actually build or install"}, {OPTION_USE_HEAD_VERSION, "Install the libraries on the command line using the latest upstream sources (classic mode)"}, @@ -534,6 +535,9 @@ namespace vcpkg::Install {OPTION_KEEP_GOING, "Continue installing packages on failure"}, {OPTION_EDITABLE, "Disable source re-extraction and binary caching for libraries on the command line (classic mode)"}, + {OPTION_CHECK_STAMP, + "If the configuration (triplets, manifest file, registries, selected features) is the same as the last time " + "install is skipped. Local port modifications are not detected anymore."}, {OPTION_USE_ARIA2, "Use aria2 to perform download tasks"}, {OPTION_CLEAN_AFTER_BUILD, "Clean buildtrees, packages and downloads after building each package"}, @@ -795,6 +799,12 @@ namespace vcpkg::Install "", "Error: The option {value} is not supported in manifest mode."); + DECLARE_AND_REGISTER_MESSAGE(StampNotChanged, + (msg::value), + "", + "Installation skipped. Everything seems to be installed. To disable this check, don't " + "pass the option `--{value}` to vcpkg."); + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, Triplet default_triplet, @@ -810,6 +820,7 @@ namespace vcpkg::Install const bool no_build_missing = Util::Sets::contains(options.switches, OPTION_ONLY_BINARYCACHING); const bool is_recursive = Util::Sets::contains(options.switches, (OPTION_RECURSE)); const bool is_editable = Util::Sets::contains(options.switches, (OPTION_EDITABLE)) || !args.cmake_args.empty(); + const bool check_stamp = Util::Sets::contains(options.switches, (OPTION_CHECK_STAMP)); const bool use_aria2 = Util::Sets::contains(options.switches, (OPTION_USE_ARIA2)); const bool clean_after_build = Util::Sets::contains(options.switches, (OPTION_CLEAN_AFTER_BUILD)); const bool clean_buildtrees_after_build = @@ -873,6 +884,13 @@ namespace vcpkg::Install msg::value = Strings::concat("--", OPTION_MANIFEST_NO_DEFAULT_FEATURES)); failure = true; } + if (Util::Sets::contains(options.switches, OPTION_CHECK_STAMP)) + { + msg::println(Color::error, + msgErrorInvalidClassicModeOption, + msg::value = Strings::concat("--", OPTION_CHECK_STAMP)); + failure = true; + } if (Util::Sets::contains(options.multisettings, OPTION_MANIFEST_FEATURE)) { msg::println(Color::error, @@ -1009,6 +1027,50 @@ namespace vcpkg::Install { extended_overlay_ports.push_back(paths.builtin_ports_directory().native()); } + + std::string hash; + { + auto timer = ElapsedTimer::create_started(); + auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha256); + for (auto&& path : args.overlay_ports) + { + for (auto&& file : fs.get_regular_files_recursive(path, VCPKG_LINE_INFO)) + { + hasher->add_bytes(fs.read_contents(file, VCPKG_LINE_INFO)); + } + } + hasher->add_bytes(paths.get_configuration_hash()); + hasher->add_bytes(Json::stringify(*manifest, Json::JsonStyle{}.with_spaces(0))); + hasher->add_bytes(default_triplet.to_string()); + hasher->add_bytes(host_triplet.to_string()); + hasher->add_bytes(fs.read_contents(paths.get_triplet_file_path(default_triplet), VCPKG_LINE_INFO)); + hasher->add_bytes(fs.read_contents(paths.get_triplet_file_path(host_triplet), VCPKG_LINE_INFO)); + hasher->add_bytes(fs.read_contents(paths.get_triplet_file_path(host_triplet), VCPKG_LINE_INFO)); + for (const auto& feature : features) + { + hasher->add_bytes(feature); + } + if (paths.get_registry_set().is_default_builtin_registry()) + { + auto maybe_git_sha = paths.get_current_git_sha(); + if (maybe_git_sha) + { + hasher->add_bytes(maybe_git_sha.value_or_exit(VCPKG_LINE_INFO)); + } + } + Debug::print("Time needed to compute hash for stamp: ", timer.elapsed().to_string(), "\n"); + hash = hasher->get_hash(); + if (fs.exists(paths.installed().hashfile_path(), VCPKG_LINE_INFO)) + { + if (check_stamp && fs.read_contents(paths.installed().hashfile_path(), VCPKG_LINE_INFO) == hash) + { + msg::println(msgStampNotChanged, msg::value = OPTION_CHECK_STAMP); + Checks::exit_success(VCPKG_LINE_INFO); + } + fs.remove(paths.installed().hashfile_path(), VCPKG_LINE_INFO); + } + } + auto oprovider = PortFileProvider::make_overlay_provider(paths, extended_overlay_ports); PackageSpec toplevel{manifest_scf.core_paragraph->name, default_triplet}; auto install_plan = Dependencies::create_versioned_install_plan(*verprovider, @@ -1021,6 +1083,7 @@ namespace vcpkg::Install host_triplet, unsupported_port_action) .value_or_exit(VCPKG_LINE_INFO); + for (const auto& warning : install_plan.warnings) { print2(Color::warning, warning, '\n'); @@ -1038,15 +1101,18 @@ namespace vcpkg::Install PortFileProvider::PathsPortFileProvider provider(paths, extended_overlay_ports); - Commands::SetInstalled::perform_and_exit_ex(args, - paths, - provider, - binary_cache, - var_provider, - std::move(install_plan), - dry_run ? Commands::DryRun::Yes : Commands::DryRun::No, - pkgsconfig, - host_triplet); + Commands::SetInstalled::perform_ex(args, + paths, + provider, + binary_cache, + var_provider, + std::move(install_plan), + dry_run ? Commands::DryRun::Yes : Commands::DryRun::No, + pkgsconfig, + host_triplet); + + fs.write_contents_and_dirs(paths.installed().hashfile_path(), hash, VCPKG_LINE_INFO); + Checks::exit_success(VCPKG_LINE_INFO); } PortFileProvider::PathsPortFileProvider provider(paths, args.overlay_ports); diff --git a/src/vcpkg/vcpkgpaths.cpp b/src/vcpkg/vcpkgpaths.cpp index 80055b4366..3a2fb42261 100644 --- a/src/vcpkg/vcpkgpaths.cpp +++ b/src/vcpkg/vcpkgpaths.cpp @@ -1440,6 +1440,12 @@ namespace vcpkg return m_pimpl->m_env_cache.get_compiler_info(*this, abi_info); } + std::string VcpkgPaths::get_configuration_hash() const + { + return Hash::get_string_hash(Json::stringify(m_pimpl->m_config.serialize(), Json::JsonStyle{}.with_spaces(0)), + Hash::Algorithm::Sha512); + } + Filesystem& VcpkgPaths::get_filesystem() const { return m_pimpl->m_fs; } bool VcpkgPaths::use_git_default_registry() const { return m_pimpl->m_bundle.m_usegitregistry; }