diff --git a/include/vcpkg/archives.h b/include/vcpkg/archives.h index 52b51f5a19..dab843cbf5 100644 --- a/include/vcpkg/archives.h +++ b/include/vcpkg/archives.h @@ -28,30 +28,36 @@ namespace vcpkg }; // Extract `archive` to `to_path` using `tar_tool`. - void extract_tar(const Path& tar_tool, const Path& archive, const Path& to_path); + bool extract_tar(DiagnosticContext& context, const Path& tar_tool, const Path& archive, const Path& to_path); // Extract `archive` to `to_path` using `cmake_tool`. (CMake's built in tar) - void extract_tar_cmake(const Path& cmake_tool, const Path& archive, const Path& to_path); - void extract_archive(const Filesystem& fs, + bool extract_tar_cmake(DiagnosticContext& context, + const Path& cmake_tool, + const Path& archive, + const Path& to_path); + bool extract_archive(DiagnosticContext& context, + const Filesystem& fs, const ToolCache& tools, - MessageSink& status_sink, const Path& archive, const Path& to_path); // extract `archive` to a sibling temporary subdirectory of `to_path` and returns that path - Path extract_archive_to_temp_subdirectory(const Filesystem& fs, - const ToolCache& tools, - MessageSink& status_sink, - const Path& archive, - const Path& to_path); + Optional extract_archive_to_temp_subdirectory(DiagnosticContext& context, + const Filesystem& fs, + const ToolCache& tools, + const Path& archive, + const Path& to_path); ExtractionType guess_extraction_type(const Path& archive); #ifdef _WIN32 // Extract the 7z archive part of a self extracting 7z installer - void win32_extract_self_extracting_7z(const Filesystem& fs, const Path& archive, const Path& to_path); + bool win32_extract_self_extracting_7z(DiagnosticContext& context, + const Filesystem& fs, + const Path& archive, + const Path& to_path); #endif struct ZipTool { - void setup(const ToolCache& tools, MessageSink& status_sink); + bool setup(DiagnosticContext& context, const Filesystem& fs, const ToolCache& tools); // Compress the source directory into the destination file. bool compress_directory_to_zip(DiagnosticContext& context, diff --git a/include/vcpkg/base/diagnostics.h b/include/vcpkg/base/diagnostics.h index 7a0b90b413..7a3a980c7c 100644 --- a/include/vcpkg/base/diagnostics.h +++ b/include/vcpkg/base/diagnostics.h @@ -151,20 +151,13 @@ namespace vcpkg MessageSink& sink; }; - // Stores all diagnostics into a vector, while passing through status lines to an underlying MessageSink. - struct BufferedDiagnosticContext final : DiagnosticContext + struct BasicBufferedDiagnosticContext : DiagnosticContext { - BufferedDiagnosticContext(MessageSink& status_sink) : status_sink(status_sink) { } - virtual void report(const DiagnosticLine& line) override; virtual void report(DiagnosticLine&& line) override; - virtual void statusln(const LocalizedString& message) override; - virtual void statusln(LocalizedString&& message) override; - virtual void statusln(const MessageLine& message) override; - virtual void statusln(MessageLine&& message) override; + // statusln intentionally unimplemented - MessageSink& status_sink; std::vector lines; // Prints all diagnostics to the supplied sink. @@ -178,6 +171,60 @@ namespace vcpkg bool empty() const noexcept; }; + // Stores all diagnostics into a vector, while passing through status lines to an underlying MessageSink. + struct SinkBufferedDiagnosticContext final : BasicBufferedDiagnosticContext + { + SinkBufferedDiagnosticContext(MessageSink& status_sink) : status_sink(status_sink) { } + + virtual void statusln(const LocalizedString& message) override; + virtual void statusln(LocalizedString&& message) override; + virtual void statusln(const MessageLine& message) override; + virtual void statusln(MessageLine&& message) override; + + MessageSink& status_sink; + }; + + struct ContextBufferedDiagnosticContext final : BasicBufferedDiagnosticContext + { + ContextBufferedDiagnosticContext(DiagnosticContext& status_context) : status_context(status_context) { } + + virtual void statusln(const LocalizedString& message) override; + virtual void statusln(LocalizedString&& message) override; + virtual void statusln(const MessageLine& message) override; + virtual void statusln(MessageLine&& message) override; + + DiagnosticContext& status_context; + }; + + struct DiagnosticOrMessageLine + { + DiagnosticOrMessageLine(const DiagnosticLine& dl); + DiagnosticOrMessageLine(DiagnosticLine&& dl); + DiagnosticOrMessageLine(const MessageLine& ml); + DiagnosticOrMessageLine(MessageLine&& ml); + + DiagnosticOrMessageLine(const DiagnosticOrMessageLine& other); + DiagnosticOrMessageLine(DiagnosticOrMessageLine&& other); + + DiagnosticOrMessageLine& operator=(const DiagnosticOrMessageLine&) = delete; + + ~DiagnosticOrMessageLine(); + + std::string to_string() const; + void to_string(std::string& target) const; + + private: + friend FullyBufferedDiagnosticContext; + + union + { + DiagnosticLine dl; + MessageLine ml; + }; + + bool is_diagnostic; + }; + // Stores all diagnostics and status messages into a vector. This is generally used for background thread or similar // scenarios where even status messages can't be immediately printed. struct FullyBufferedDiagnosticContext final : DiagnosticContext @@ -190,28 +237,29 @@ namespace vcpkg virtual void statusln(const MessageLine& message) override; virtual void statusln(MessageLine&& message) override; - std::vector lines; - // Prints all diagnostics to the supplied sink. void print_to(MessageSink& sink) const; // Converts this message into a string // Prefer print() if possible because it applies color std::string to_string() const; void to_string(std::string& target) const; + // Emits all status messages and reports all diagnostics to the supplied context + void report_to(DiagnosticContext& context) const&; + void report_to(DiagnosticContext& context) &&; bool empty() const noexcept; + + private: + std::vector lines; }; // DiagnosticContext for attempted operations that may be recovered. // Stores all diagnostics and passes through all status messages. Afterwards, call commit() to report all // diagnostics to the outer DiagnosticContext, or handle() to forget them. - struct AttemptDiagnosticContext final : DiagnosticContext + struct AttemptDiagnosticContext final : BasicBufferedDiagnosticContext { AttemptDiagnosticContext(DiagnosticContext& inner_context) : inner_context(inner_context) { } - virtual void report(const DiagnosticLine& line) override; - virtual void report(DiagnosticLine&& line) override; - virtual void statusln(const LocalizedString& message) override; virtual void statusln(LocalizedString&& message) override; virtual void statusln(const MessageLine& message) override; @@ -223,7 +271,6 @@ namespace vcpkg ~AttemptDiagnosticContext(); DiagnosticContext& inner_context; - std::vector lines; }; // Wraps another DiagnosticContext and reduces the severity of any reported diagnostics to warning from error. @@ -312,11 +359,11 @@ namespace vcpkg // The overload for functors that return Optional template auto adapt_context_to_expected(Fn functor, Args&&... args) -> ExpectedL< - typename AdaptContextUnwrapOptional>::type> + typename AdaptContextUnwrapOptional>::type> { - using Unwrapper = AdaptContextUnwrapOptional>; + using Unwrapper = AdaptContextUnwrapOptional>; using ReturnType = ExpectedL; - BufferedDiagnosticContext bdc{out_sink}; + SinkBufferedDiagnosticContext bdc{out_sink}; decltype(auto) maybe_result = functor(bdc, std::forward(args)...); if (auto result = maybe_result.get()) { @@ -381,11 +428,11 @@ namespace vcpkg // The overload for functors that return std::unique_ptr template auto adapt_context_to_expected(Fn functor, Args&&... args) -> ExpectedL< - typename AdaptContextDetectUniquePtr>::type> + typename AdaptContextDetectUniquePtr>::type> { - using ReturnType = ExpectedL< - typename AdaptContextDetectUniquePtr>::type>; - BufferedDiagnosticContext bdc{out_sink}; + using ReturnType = ExpectedL>::type>; + SinkBufferedDiagnosticContext bdc{out_sink}; decltype(auto) maybe_result = functor(bdc, std::forward(args)...); if (maybe_result) { diff --git a/include/vcpkg/base/files.h b/include/vcpkg/base/files.h index 2c4ee81e3f..d9b08a46fc 100644 --- a/include/vcpkg/base/files.h +++ b/include/vcpkg/base/files.h @@ -140,6 +140,7 @@ namespace vcpkg std::string read_contents(const Path& file_path, LineInfo li) const; ExpectedL try_read_contents(const Path& file_path) const; + Optional try_read_contents(DiagnosticContext& context, const Path& file_path) const; // Tries to read `file_path`, and if the file starts with a shebang sequence #!, returns the contents of the // file. If an I/O error occurs or the file does not start with a shebang sequence, returns an empty string. @@ -163,6 +164,7 @@ namespace vcpkg virtual std::vector get_files_non_recursive(const Path& dir, std::error_code& ec) const = 0; std::vector get_files_non_recursive(const Path& dir, LineInfo li) const; ExpectedL> try_get_files_non_recursive(const Path& dir) const; + Optional> try_get_files_non_recursive(DiagnosticContext& context, const Path& dir) const; virtual std::vector get_directories_recursive(const Path& dir, std::error_code& ec) const = 0; std::vector get_directories_recursive(const Path& dir, LineInfo li) const; @@ -176,6 +178,8 @@ namespace vcpkg virtual std::vector get_directories_non_recursive(const Path& dir, std::error_code& ec) const = 0; std::vector get_directories_non_recursive(const Path& dir, LineInfo li) const; ExpectedL> try_get_directories_non_recursive(const Path& dir) const; + Optional> try_get_directories_non_recursive(DiagnosticContext& context, + const Path& dir) const; virtual std::vector get_regular_files_recursive(const Path& dir, std::error_code& ec) const = 0; std::vector get_regular_files_recursive(const Path& dir, LineInfo li) const; @@ -243,6 +247,7 @@ namespace vcpkg virtual void write_contents(const Path& file_path, StringView data, std::error_code& ec) const = 0; void write_contents(const Path& file_path, StringView data, LineInfo li) const; + bool write_contents(DiagnosticContext& context, const Path& file_path, StringView data) const; void write_rename_contents(const Path& file_path, const Path& temp_name, StringView data, LineInfo li) const; void write_contents_and_dirs(const Path& file_path, StringView data, LineInfo li) const; @@ -250,6 +255,7 @@ namespace vcpkg virtual void rename(const Path& old_path, const Path& new_path, std::error_code& ec) const = 0; void rename(const Path& old_path, const Path& new_path, LineInfo li) const; + bool rename(DiagnosticContext& context, const Path& old_path, const Path& new_path) const; void rename_with_retry(const Path& old_path, const Path& new_path, std::error_code& ec) const; void rename_with_retry(const Path& old_path, const Path& new_path, LineInfo li) const; @@ -268,6 +274,7 @@ namespace vcpkg virtual bool remove(const Path& target, std::error_code& ec) const = 0; bool remove(const Path& target, LineInfo li) const; + Optional remove(DiagnosticContext& context, const Path& target) const; virtual void remove_all(const Path& base, std::error_code& ec, Path& failure_point) const = 0; void remove_all(const Path& base, std::error_code& ec) const; @@ -338,20 +345,12 @@ namespace vcpkg // however, if `/a/b` doesn't exist, then the functions will fail. // waits forever for the file lock - virtual std::unique_ptr take_exclusive_file_lock(const Path& lockfile, - MessageSink& status_sink, - std::error_code&) const = 0; - std::unique_ptr take_exclusive_file_lock(const Path& lockfile, - MessageSink& status_sink, - LineInfo li) const; + virtual std::unique_ptr take_exclusive_file_lock(DiagnosticContext& context, + const Path& lockfile) const = 0; // waits, at most, 1.5 seconds, for the file lock - virtual std::unique_ptr try_take_exclusive_file_lock(const Path& lockfile, - MessageSink& status_sink, - std::error_code&) const = 0; - std::unique_ptr try_take_exclusive_file_lock(const Path& lockfile, - MessageSink& status_sink, - LineInfo li) const; + virtual Optional> try_take_exclusive_file_lock( + DiagnosticContext& context, const Path& lockfile) const = 0; virtual WriteFilePointer open_for_write(const Path& file_path, Append append, std::error_code& ec) const = 0; WriteFilePointer open_for_write(const Path& file_path, Append append, LineInfo li) const; @@ -360,6 +359,7 @@ namespace vcpkg }; extern const Filesystem& real_filesystem; + extern const Filesystem& always_failing_filesystem; extern const StringLiteral FILESYSTEM_INVALID_CHARACTERS; diff --git a/include/vcpkg/base/fwd/diagnostics.h b/include/vcpkg/base/fwd/diagnostics.h index d081df483a..cb6f72d33c 100644 --- a/include/vcpkg/base/fwd/diagnostics.h +++ b/include/vcpkg/base/fwd/diagnostics.h @@ -16,12 +16,15 @@ namespace vcpkg struct DiagnosticLine; struct DiagnosticContext; struct PrintingDiagnosticContext; - struct BufferedDiagnosticContext; + struct BasicBufferedDiagnosticContext; + struct SinkBufferedDiagnosticContext; + struct ContextBufferedDiagnosticContext; struct FullyBufferedDiagnosticContext; struct AttemptDiagnosticContext; struct WarningDiagnosticContext; + extern DiagnosticContext& null_diagnostic_context; extern DiagnosticContext& console_diagnostic_context; + extern DiagnosticContext& stderr_diagnostic_context; extern DiagnosticContext& status_only_diagnostic_context; - extern DiagnosticContext& null_diagnostic_context; } diff --git a/include/vcpkg/base/json.h b/include/vcpkg/base/json.h index e7b741cd4f..da7f60cf5f 100644 --- a/include/vcpkg/base/json.h +++ b/include/vcpkg/base/json.h @@ -335,7 +335,9 @@ namespace vcpkg::Json }; ExpectedL parse(StringView text, StringView origin); + Optional parse(DiagnosticContext& context, StringView text, StringView origin); ExpectedL parse_object(StringView text, StringView origin); + Optional parse_object(DiagnosticContext& context, StringView text, StringView origin); std::string stringify(const Value&); std::string stringify(const Value&, JsonStyle style); diff --git a/include/vcpkg/base/lazy.h b/include/vcpkg/base/lazy.h index 4867172b86..9d1d343d56 100644 --- a/include/vcpkg/base/lazy.h +++ b/include/vcpkg/base/lazy.h @@ -1,25 +1,24 @@ #pragma once +#include + namespace vcpkg { template struct Lazy { - Lazy() : value(T()), initialized(false) { } - template T const& get_lazy(const F& f) const { - if (!initialized) + if (auto existing = value.get()) { - value = f(); - initialized = true; + return *existing; } - return value; + + return value.emplace(f()); } private: - mutable T value; - mutable bool initialized; + mutable Optional value; }; } diff --git a/include/vcpkg/base/message-data.inc.h b/include/vcpkg/base/message-data.inc.h index cf2d292fb8..04aa4b5535 100644 --- a/include/vcpkg/base/message-data.inc.h +++ b/include/vcpkg/base/message-data.inc.h @@ -173,6 +173,8 @@ DECLARE_MESSAGE(APackagePatternArray, (), "", "a package pattern array") DECLARE_MESSAGE(APath, (), "", "a path") DECLARE_MESSAGE(AppliedUserIntegration, (), "", "Applied user-wide integration for this vcpkg root.") DECLARE_MESSAGE(ApplocalProcessing, (), "", "deploying dependencies") +DECLARE_MESSAGE(ArchiverFailedToExtractExitCode, (msg::exit_code), "", "failed to extract with exit code {exit_code}") +DECLARE_MESSAGE(ArchiveHere, (), "", "the archive is here") DECLARE_MESSAGE(ARegistry, (), "", "a registry") DECLARE_MESSAGE(ARegistryImplementationKind, (), "", "a registry implementation kind") DECLARE_MESSAGE(ARegistryPath, (), "", "a registry path") @@ -967,10 +969,7 @@ DECLARE_MESSAGE(CorruptedDatabase, "the contents of the 'installed' directory in an unexpected way. You may be able to fix this by " "deleting the 'installed' directory and reinstalling what you want to use. If this problem happens " "consistently, please file a bug at https://github.com/microsoft/vcpkg .") -DECLARE_MESSAGE(CouldNotDeduceNuGetIdAndVersion, - (msg::path), - "", - "Could not deduce NuGet id and version from filename: {path}") +DECLARE_MESSAGE(CouldNotDeduceNuGetIdAndVersion2, (), "", "could not deduce NuGet id and version from filename") DECLARE_MESSAGE(CouldNotFindBaselineInCommit, (msg::url, msg::commit_sha, msg::package_name), "", @@ -1272,7 +1271,10 @@ DECLARE_MESSAGE(ExpectedOneSetOfTags, "{old_value} is a left tag and {new_value} is the right tag. {value} is the input.", "Found {count} sets of {old_value}.*{new_value} but expected exactly 1, in block:\n{value}") DECLARE_MESSAGE(ExpectedOneVersioningField, (), "", "expected only one versioning field") -DECLARE_MESSAGE(ExpectedPathToExist, (msg::path), "", "Expected {path} to exist after fetching") +DECLARE_MESSAGE(ExpectedPathToExistAfterExtractingTool, + (msg::tool_name), + "", + "expected this path to exist after extracting {tool_name}") DECLARE_MESSAGE(ExpectedPortName, (), "", "expected a port name here (must be lowercase, digits, '-')") DECLARE_MESSAGE(ExpectedReadWriteReadWrite, (), "", "unexpected argument: expected 'read', readwrite', or 'write'") DECLARE_MESSAGE(ExpectedStatusField, (), "", "Expected 'status' field in status paragraph") @@ -1290,6 +1292,7 @@ DECLARE_MESSAGE(ExportedZipArchive, (msg::path), "", "Zip archive exported at: { DECLARE_MESSAGE(ExportingAlreadyBuiltPackages, (), "", "The following packages are already built and will be exported:") DECLARE_MESSAGE(ExportingPackage, (msg::package_name), "", "Exporting {package_name}...") DECLARE_MESSAGE(ExtendedDocumentationAtUrl, (msg::url), "", "Extended documentation available at '{url}'.") +DECLARE_MESSAGE(ExtractedHere, (), "", "extracted here") DECLARE_MESSAGE(ExtractedInto, (msg::path), "", "extracted into {path}") DECLARE_MESSAGE(ExtractHelp, (), "", "Extracts an archive.") DECLARE_MESSAGE(ExtractingTool, (msg::tool_name), "", "Extracting {tool_name}...") @@ -1322,7 +1325,6 @@ DECLARE_MESSAGE(MissingShaVariable, (), "{{sha}} should not be translated", "The {{sha}} variable must be used in the template if other variables are used.") -DECLARE_MESSAGE(FailedToExtract, (msg::path), "", "Failed to extract \"{path}\":") DECLARE_MESSAGE(FailedToFetchRepo, (msg::url), "", "Failed to fetch {url}.") DECLARE_MESSAGE(FailedToFindPortFeature, (msg::feature, msg::package_name), @@ -1358,11 +1360,6 @@ DECLARE_MESSAGE(FailedToParseSerializedBinParagraph, "[sanity check] Failed to parse a serialized binary paragraph.\nPlease open an issue at " "https://github.com/microsoft/vcpkg, " "with the following output:\n{error_msg}\nSerialized Binary Paragraph:") -DECLARE_MESSAGE(FailedToRunToolToDetermineVersion, - (msg::tool_name, msg::path), - "Additional information, such as the command line output, if any, will be appended on " - "the line after this message", - "Failed to run \"{path}\" to determine the {tool_name} version.") DECLARE_MESSAGE(FailedToStoreBackToMirror, (msg::path, msg::url), "", "Failed to store {path} to {url}.") DECLARE_MESSAGE(FailedToStoreBinaryCache, (msg::path), "", "Failed to store binary cache {path}") DECLARE_MESSAGE(FailedToTakeFileSystemLock, (), "", "Failed to take the filesystem lock") @@ -1458,16 +1455,10 @@ DECLARE_MESSAGE(GhaBinaryCacheDeprecated, "The term 'x-gha' is a vcpkg configuration option", "The 'x-gha' binary caching backend has been removed. Consider using a NuGet-based binary caching " "provider instead, see extended documentation at {url}.") -DECLARE_MESSAGE(GitCommandFailed, (msg::command_line), "", "failed to execute: {command_line}") DECLARE_MESSAGE(GitCommitUpdateVersionDatabase, (), "This is a command line; only the 'update version database' part should be localized", "git commit -m \"Update version database\"") -DECLARE_MESSAGE(GitFailedToFetch, - (msg::value, msg::url), - "{value} is a git ref like 'origin/main'", - "failed to fetch ref {value} from repository {url}") -DECLARE_MESSAGE(GitFailedToInitializeLocalRepository, (msg::path), "", "failed to initialize local repository {path}") DECLARE_MESSAGE( GitRegistryMustHaveBaseline, (msg::url, msg::commit_sha), @@ -2040,7 +2031,6 @@ DECLARE_MESSAGE(InvalidValuePostPortfileIncludes, "Variable VCPKG_POST_PORTFILE_INCLUDES contains invalid file path: '{path}'. The value must be " "an absolute path to an existent cmake file.") DECLARE_MESSAGE(IrregularFile, (msg::path), "", "path was not a regular file: {path}") -DECLARE_MESSAGE(JsonErrorMustBeAnObject, (msg::path), "", "Expected \"{path}\" to be an object.") DECLARE_MESSAGE(JsonFieldNotObject, (msg::json_field), "", "value of [\"{json_field}\"] must be an object") DECLARE_MESSAGE(JsonFieldNotString, (msg::json_field), "", "value of [\"{json_field}\"] must be a string") DECLARE_MESSAGE(JsonFileMissingExtension, @@ -2168,7 +2158,6 @@ DECLARE_MESSAGE(MismatchedType, (msg::json_field, msg::json_type), "", "{json_field}: mismatched type: expected {json_type}") -DECLARE_MESSAGE(Missing7zHeader, (), "", "Unable to find 7z header.") DECLARE_MESSAGE(MissingArgFormatManifest, (), "", @@ -2179,7 +2168,6 @@ DECLARE_MESSAGE(MissingDependency, (msg::spec, msg::package_name), "", "Package {spec} is installed, but dependency {package_name} is not.") -DECLARE_MESSAGE(MissingExtension, (msg::extension), "", "Missing '{extension}' extension.") DECLARE_MESSAGE(MissingOption, (msg::option), "", "This command requires --{option}") DECLARE_MESSAGE(MissingOrInvalidIdentifer, (), "", "missing or invalid identifier") DECLARE_MESSAGE(MissingPortSuggestPullRequest, @@ -2340,10 +2328,6 @@ DECLARE_MESSAGE(PackageLicenseWarning, "any licenses to, third-party packages.") DECLARE_MESSAGE(PackageManipulationHeader, (), "", "Package Manipulation") DECLARE_MESSAGE(PackageInfoHelp, (), "", "Display detailed information on packages") -DECLARE_MESSAGE(PackageFailedtWhileExtracting, - (msg::value, msg::path), - "'{value}' is either a tool name or a package name.", - "'{value}' failed while extracting {path}.") DECLARE_MESSAGE(PackageInstallationHeader, (), "", "Package Installation") DECLARE_MESSAGE(PackageRootDir, (), "", "Packages directory (experimental)") DECLARE_MESSAGE(PackagesToInstall, (), "", "The following packages will be built and installed:") @@ -2766,6 +2750,14 @@ DECLARE_MESSAGE(SettingEnvVar, "An example of env_var is \"HTTP(S)_PROXY\"" "'--' at the beginning must be preserved", "-- Setting \"{env_var}\" environment variables to \"{url}\".") +DECLARE_MESSAGE(SevenZipIncorrectExtension, + (), + "", + "failed to extract self-extracting 7zip archive because it is missing the .7z.exe file extension") +DECLARE_MESSAGE(SevenZipIncorrectHeader, + (), + "", + "failed to extract self-extracting 7zip archive because a 7z header could not be found") DECLARE_MESSAGE(ShallowRepositoryDetected, (), "", @@ -3000,10 +2992,10 @@ DECLARE_MESSAGE(UnexpectedSwitch, (msg::option), "Switch is a command line switch like --switch", "unexpected switch: {option}") -DECLARE_MESSAGE(UnexpectedToolOutput, - (msg::tool_name, msg::path), +DECLARE_MESSAGE(UnexpectedToolOutput2, + (msg::tool_name), "The actual command line output will be appended after this message.", - "{tool_name} ({path}) produced unexpected output when attempting to determine the version:") + "{tool_name} produced unexpected output when attempting to determine the version:") DECLARE_MESSAGE(UnexpectedWindowsArchitecture, (msg::actual), "{actual} is the CPU kind we observed like ARM or MIPS", @@ -3376,6 +3368,7 @@ DECLARE_MESSAGE(WhileCheckingOutPortTreeIsh, (msg::package_name, msg::git_tree_sha), "", "while checking out port {package_name} with git tree {git_tree_sha}") +DECLARE_MESSAGE(WhileDeterminingVersion, (msg::tool_name), "", "failed to run to determine the {tool_name} version") DECLARE_MESSAGE(WhileExtractingThisArchive, (), "", "while extracting this archive") DECLARE_MESSAGE(WhileGettingLocalTreeIshObjectsForPorts, (), "", "while getting local treeish objects for ports") DECLARE_MESSAGE(WhileLookingForSpec, (msg::spec), "", "while looking for {spec}:") @@ -3392,6 +3385,6 @@ DECLARE_MESSAGE(WhilePackingNuGetPackage, (), "", "while packing NuGet package") DECLARE_MESSAGE(WhilePushingNuGetPackage, (), "", "while pushing NuGet package") DECLARE_MESSAGE(WhileRunningAssetCacheScriptCommandLine, (), "", "while running asset cache script command line") DECLARE_MESSAGE(WhileValidatingVersion, (msg::version), "", "while validating version: {version}") -DECLARE_MESSAGE(WindowsEnvMustAlwaysBePresent, (msg::env_var), "", "Expected {env_var} to be always set on Windows.") +DECLARE_MESSAGE(WindowsEnvMustAlwaysBePresent, (msg::env_var), "", "expected {env_var} to be always set on Windows") DECLARE_MESSAGE(WindowsOnlyCommand, (), "", "This command only supports Windows.") DECLARE_MESSAGE(WroteNuGetPkgConfInfo, (msg::path), "", "Wrote NuGet package config information to {path}") diff --git a/include/vcpkg/base/parse.h b/include/vcpkg/base/parse.h index 1290686e59..1025804bc2 100644 --- a/include/vcpkg/base/parse.h +++ b/include/vcpkg/base/parse.h @@ -30,6 +30,9 @@ namespace vcpkg LocalizedString join() const; + bool report(DiagnosticContext& context) const&; + bool report(DiagnosticContext& context) &&; + private: std::vector m_lines; bool m_good = true; diff --git a/include/vcpkg/base/system.h b/include/vcpkg/base/system.h index 3993620474..3bb2f83ccc 100644 --- a/include/vcpkg/base/system.h +++ b/include/vcpkg/base/system.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -12,27 +13,35 @@ namespace vcpkg { - Optional get_environment_variable(ZStringView varname) noexcept; + Optional get_environment_variable(ZStringView varname); void set_environment_variable(ZStringView varname, Optional value) noexcept; std::vector get_environment_variables(); - const ExpectedL& get_home_dir() noexcept; + const ExpectedL& get_home_dir(); + const Path* get_home_dir(DiagnosticContext& context); - const ExpectedL& get_platform_cache_root() noexcept; + const ExpectedL& get_platform_cache_root(); + const Path* get_platform_cache_root(DiagnosticContext& context); - const ExpectedL& get_platform_cache_vcpkg() noexcept; + const ExpectedL& get_platform_cache_vcpkg(); + const Path* get_platform_cache_vcpkg(DiagnosticContext& context); - const ExpectedL& get_user_configuration_home() noexcept; + const ExpectedL& get_user_configuration_home(); + const Path* get_user_configuration_home(DiagnosticContext& context); #ifdef _WIN32 - const ExpectedL& get_appdata_local() noexcept; + const ExpectedL& get_appdata_local(); + const Path* get_appdata_local(DiagnosticContext&); - const ExpectedL& get_system_drive() noexcept; + const ExpectedL& get_system_drive(); + const Path* get_system_drive(DiagnosticContext&); - const ExpectedL& get_system_root() noexcept; + const ExpectedL& get_system_root(); + const Path* get_system_root(DiagnosticContext&); - const ExpectedL& get_system32() noexcept; + const ExpectedL& get_system32(); + const Path* get_system32(DiagnosticContext&); std::wstring get_username(); diff --git a/include/vcpkg/base/system.process.h b/include/vcpkg/base/system.process.h index 14db566fd8..45ef53e2b2 100644 --- a/include/vcpkg/base/system.process.h +++ b/include/vcpkg/base/system.process.h @@ -201,16 +201,6 @@ namespace vcpkg Optional cmd_execute_and_stream_data(DiagnosticContext& context, const Command& cmd, const std::function& data_cb); - inline ExpectedL cmd_execute_and_stream_data(const Command& cmd, - const std::function& data_cb) - { - return adapt_context_to_expected( - static_cast (*)( - DiagnosticContext&, const Command&, const std::function&)>( - cmd_execute_and_stream_data), - cmd, - data_cb); - } Optional cmd_execute_and_stream_data(DiagnosticContext& context, const Command& cmd, diff --git a/include/vcpkg/binarycaching.h b/include/vcpkg/binarycaching.h index bcf2f13654..49bfb2b329 100644 --- a/include/vcpkg/binarycaching.h +++ b/include/vcpkg/binarycaching.h @@ -82,7 +82,11 @@ namespace vcpkg /// Called upon a successful build of `action` to store those contents in the binary cache. /// returns the number of successful uploads - virtual size_t push_success(const BinaryPackageWriteInfo& request, MessageSink& msg_sink) = 0; + /// + /// Note that as this is considered non-fatal, only warnings or lower will be emitted to `context`. + virtual size_t push_success(DiagnosticContext& context, + const Filesystem& fs, + const BinaryPackageWriteInfo& request) = 0; virtual bool needs_nuspec_data() const = 0; virtual bool needs_zip_file() const = 0; @@ -93,20 +97,27 @@ namespace vcpkg virtual ~IReadBinaryProvider() = default; /// Gives the IBinaryProvider an opportunity to batch any downloading or server communication for executing - /// `actions`. + /// `actions`. Note that as this API can't fail, only warnings or lower will be emitted to `context`. /// /// IBinaryProvider should set out_status[i] to RestoreResult::restored for each fetched package. /// /// Prerequisites: actions[i].package_abi(), out_status.size() == actions.size() - virtual void fetch(View actions, Span out_status) const = 0; + virtual void fetch(DiagnosticContext& context, + const Filesystem& fs, + View actions, + Span out_status) const = 0; /// Checks whether the `actions` are present in the cache, without restoring them. + /// Note that as this API can't fail, only warnings or lower will be emitted to `context`. /// /// Used by CI to determine missing packages. For each `i`, out_status[i] should be set to /// CacheAvailability::available or CacheAvailability::unavailable /// /// Prerequisites: actions[i].package_abi(), out_status.size() == actions.size() - virtual void precheck(View actions, Span out_status) const = 0; + virtual void precheck(DiagnosticContext& context, + const Filesystem& fs, + View actions, + Span out_status) const = 0; virtual LocalizedString restored_message(size_t count, std::chrono::high_resolution_clock::duration elapsed) const = 0; @@ -209,7 +220,7 @@ namespace vcpkg /// Gives the IBinaryProvider an opportunity to batch any downloading or server communication for /// executing `actions`. - void fetch(View actions); + void fetch(DiagnosticContext& context, const Filesystem& fs, View actions); bool is_restored(const InstallPlanAction& ipa) const; @@ -218,7 +229,9 @@ namespace vcpkg /// Checks whether the `actions` are present in the cache, without restoring them. Used by CI to determine /// missing packages. /// Returns a vector where each index corresponds to the matching index in `actions`. - std::vector precheck(View actions); + std::vector precheck(DiagnosticContext& context, + const Filesystem& fs, + View actions); // Informs the binary cache that the packages directory has been reset. Used when the same port-name is built // more than once in a single invocation of vcpkg. @@ -276,8 +289,9 @@ namespace vcpkg // upload is no longer being actively written by the foreground thread. struct BinaryCache : ReadOnlyBinaryCache { - bool install_providers(const VcpkgCmdArguments& args, const VcpkgPaths& paths, MessageSink& status_sink); + bool install_providers(DiagnosticContext& context, const VcpkgCmdArguments& args, const VcpkgPaths& paths); + // fs must outlive the BinaryCache, and will be accessed from the background thread that does pushes explicit BinaryCache(const Filesystem& fs); BinaryCache(const BinaryCache&) = delete; BinaryCache& operator=(const BinaryCache&) = delete; diff --git a/include/vcpkg/tools.h b/include/vcpkg/tools.h index f59741bb7d..c008943e85 100644 --- a/include/vcpkg/tools.h +++ b/include/vcpkg/tools.h @@ -1,14 +1,21 @@ #pragma once +#include #include #include #include #include +#include +#include #include +#include +#include #include +#include +#include namespace vcpkg { @@ -41,27 +48,81 @@ namespace vcpkg { virtual ~ToolCache() = default; - virtual const Path& get_tool_path(StringView tool, MessageSink& status_sink) const = 0; - virtual const std::string& get_tool_version(StringView tool, MessageSink& status_sink) const = 0; + virtual const Path* get_tool_path(DiagnosticContext& context, const Filesystem& fs, StringView tool) const = 0; + virtual const std::string* get_tool_version(DiagnosticContext& context, + const Filesystem& fs, + StringView tool) const = 0; }; - ExpectedL extract_prefixed_nonquote(StringLiteral prefix, - StringLiteral tool_name, - std::string&& output, - const Path& exe_path); + void extract_prefixed_nonquote(DiagnosticContext& context, + StringLiteral prefix, + StringLiteral tool_name, + Optional& maybe_output, + const Path& exe_path); - ExpectedL extract_prefixed_nonwhitespace(StringLiteral prefix, - StringLiteral tool_name, - std::string&& output, - const Path& exe_path); + void extract_prefixed_nonwhitespace(DiagnosticContext& context, + StringLiteral prefix, + StringLiteral tool_name, + Optional& maybe_output, + const Path& exe_path); - ExpectedL find_system_tar(const ReadOnlyFilesystem& fs); - ExpectedL find_system_cmake(const ReadOnlyFilesystem& fs); + Optional find_system_tar(DiagnosticContext& context, const ReadOnlyFilesystem& fs); + Optional find_system_cmake(DiagnosticContext& context, const ReadOnlyFilesystem& fs); - std::unique_ptr get_tool_cache(const Filesystem& fs, - const AssetCachingSettings& asset_cache_settings, + std::unique_ptr get_tool_cache(const AssetCachingSettings& asset_cache_settings, Path downloads, Path config_path, Path tools, RequireExactVersions abiToolVersionHandling); + + template> + struct ContextCache + { + template && + detail::is_callable::value, + int> = 0> + const Value* get_lazy(DiagnosticContext& context, const KeyIsh& k, F&& f) const + { + auto it = m_cache.lower_bound(k); + // lower_bound returns the first iterator such that it->first is greater than or equal to than k, so k must + // be less than or equal to it->first. If k is greater than it->first, then it must be non-equal so we + // have a cache miss. + if (it == m_cache.end() || m_cache.key_comp()(k, it->first)) + { + ContextBufferedDiagnosticContext cbdc{context}; + auto maybe_result = f(cbdc); + if (auto success = maybe_result.get()) + { + it = m_cache.emplace_hint(it, + std::piecewise_construct, + std::forward_as_tuple(k), + std::forward_as_tuple(std::move(*success), expected_left_tag)); + } + else + { + it = m_cache.emplace_hint(it, + std::piecewise_construct, + std::forward_as_tuple(k), + std::forward_as_tuple(std::move(cbdc.lines), expected_right_tag)); + } + } + + if (auto success = it->second.get()) + { + return success; + } + + for (const auto& line : it->second.error()) + { + context.report(line); + } + + return nullptr; + } + + private: + mutable std::map>, Compare> m_cache; + }; } diff --git a/include/vcpkg/tools.test.h b/include/vcpkg/tools.test.h index 3fe16649d0..9f0b360040 100644 --- a/include/vcpkg/tools.test.h +++ b/include/vcpkg/tools.test.h @@ -67,7 +67,9 @@ namespace vcpkg std::string archiveName; }; - ExpectedL> parse_tool_data(StringView contents, StringView origin); + Optional> parse_tool_data(DiagnosticContext& context, + StringView contents, + StringView origin); const ToolDataEntry* get_raw_tool_data(const std::vector& tool_data_table, StringView toolname, diff --git a/include/vcpkg/vcpkgpaths.h b/include/vcpkg/vcpkgpaths.h index 150193326c..e4dae8ef44 100644 --- a/include/vcpkg/vcpkgpaths.h +++ b/include/vcpkg/vcpkgpaths.h @@ -104,15 +104,15 @@ namespace vcpkg public: OverlayPortPaths overlay_ports; + Optional get_scripts_version(DiagnosticContext& context) const; std::string get_toolver_diagnostics() const; const Filesystem& get_filesystem() const; const AssetCachingSettings& get_asset_cache_settings() const; const ToolCache& get_tool_cache() const; - const Path& get_tool_exe(StringView tool, MessageSink& status_messages) const; - const std::string& get_tool_version(StringView tool, MessageSink& status_messages) const; - - Command git_cmd_builder(const Path& dot_git_dir, const Path& work_tree) const; + const Path* get_tool_path(DiagnosticContext& context, StringView tool) const; + const Path& get_tool_path_required(StringView tool) const; + const std::string& get_tool_version_required(StringView tool) const; // Git manipulation in the vcpkg directory ExpectedL get_current_git_sha() const; diff --git a/locales/messages.json b/locales/messages.json index 7598020abd..1ab05a9291 100644 --- a/locales/messages.json +++ b/locales/messages.json @@ -143,6 +143,9 @@ "AnotherInstallationInProgress": "Another installation is in progress on the machine, sleeping 6s before retrying.", "AppliedUserIntegration": "Applied user-wide integration for this vcpkg root.", "ApplocalProcessing": "deploying dependencies", + "ArchiveHere": "the archive is here", + "ArchiverFailedToExtractExitCode": "failed to extract with exit code {exit_code}", + "_ArchiverFailedToExtractExitCode.comment": "An example of {exit_code} is 127.", "ArtifactsBootstrapFailed": "vcpkg-artifacts is not installed and could not be bootstrapped.", "ArtifactsOptionIncompatibility": "--{option} has no effect on find artifact.", "_ArtifactsOptionIncompatibility.comment": "An example of {option} is editable.", @@ -551,8 +554,7 @@ "ControlSupportsMustBeAPlatformExpression": "\"Supports\" must be a platform expression", "CopyrightIsDir": "this port sets ${{CURRENT_PACKAGES_DIR}}/share/${{PORT}}/copyright to a directory, but it should be a file. Consider combining separate copyright files into one using vcpkg_install_copyright. To suppress this message, add set(VCPKG_POLICY_SKIP_COPYRIGHT_CHECK enabled)", "CorruptedDatabase": "vcpkg's installation database corrupted. This is either a bug in vcpkg or something else has modified the contents of the 'installed' directory in an unexpected way. You may be able to fix this by deleting the 'installed' directory and reinstalling what you want to use. If this problem happens consistently, please file a bug at https://github.com/microsoft/vcpkg .", - "CouldNotDeduceNuGetIdAndVersion": "Could not deduce NuGet id and version from filename: {path}", - "_CouldNotDeduceNuGetIdAndVersion.comment": "An example of {path} is /foo/bar.", + "CouldNotDeduceNuGetIdAndVersion2": "could not deduce NuGet id and version from filename", "CouldNotFindBaselineInCommit": "Couldn't find baseline in {url} at {commit_sha} for {package_name}.", "_CouldNotFindBaselineInCommit.comment": "An example of {url} is https://github.com/microsoft/vcpkg. An example of {commit_sha} is 7cfad47ae9f68b183983090afd6337cd60fd4949. An example of {package_name} is zlib.", "CouldNotFindGitTreeAtCommit": "could not find the git tree for `versions` in repo {package_name} at commit {commit_sha}", @@ -747,8 +749,8 @@ "ExpectedOneSetOfTags": "Found {count} sets of {old_value}.*{new_value} but expected exactly 1, in block:\n{value}", "_ExpectedOneSetOfTags.comment": "{old_value} is a left tag and {new_value} is the right tag. {value} is the input. An example of {count} is 42.", "ExpectedOneVersioningField": "expected only one versioning field", - "ExpectedPathToExist": "Expected {path} to exist after fetching", - "_ExpectedPathToExist.comment": "An example of {path} is /foo/bar.", + "ExpectedPathToExistAfterExtractingTool": "expected this path to exist after extracting {tool_name}", + "_ExpectedPathToExistAfterExtractingTool.comment": "An example of {tool_name} is signtool.", "ExpectedPortName": "expected a port name here (must be lowercase, digits, '-')", "ExpectedReadWriteReadWrite": "unexpected argument: expected 'read', readwrite', or 'write'", "ExpectedStatusField": "Expected 'status' field in status paragraph", @@ -767,6 +769,7 @@ "ExtendedDocumentationAtUrl": "Extended documentation available at '{url}'.", "_ExtendedDocumentationAtUrl.comment": "An example of {url} is https://github.com/microsoft/vcpkg.", "ExtractHelp": "Extracts an archive.", + "ExtractedHere": "extracted here", "ExtractedInto": "extracted into {path}", "_ExtractedInto.comment": "An example of {path} is /foo/bar.", "ExtractingTool": "Extracting {tool_name}...", @@ -784,8 +787,6 @@ "FailedToDeleteInsideDueToFile": "failed to remove_all_inside({value}) due to {path}: ", "_FailedToDeleteInsideDueToFile.comment": "{value} is the parent path of {path} we tried to delete; the underlying Windows error message is printed after this An example of {path} is /foo/bar.", "FailedToDetermineCurrentCommit": "Failed to determine the current commit:", - "FailedToExtract": "Failed to extract \"{path}\":", - "_FailedToExtract.comment": "An example of {path} is /foo/bar.", "FailedToFetchRepo": "Failed to fetch {url}.", "_FailedToFetchRepo.comment": "An example of {url} is https://github.com/microsoft/vcpkg.", "FailedToFindPortFeature": "{package_name} has no feature named {feature}.", @@ -809,8 +810,6 @@ "_FailedToParseNoVersionsArray.comment": "An example of {path} is /foo/bar.", "FailedToParseSerializedBinParagraph": "[sanity check] Failed to parse a serialized binary paragraph.\nPlease open an issue at https://github.com/microsoft/vcpkg, with the following output:\n{error_msg}\nSerialized Binary Paragraph:", "_FailedToParseSerializedBinParagraph.comment": "'{error_msg}' is the error message for failing to parse the Binary Paragraph. An example of {error_msg} is File Not Found.", - "FailedToRunToolToDetermineVersion": "Failed to run \"{path}\" to determine the {tool_name} version.", - "_FailedToRunToolToDetermineVersion.comment": "Additional information, such as the command line output, if any, will be appended on the line after this message An example of {tool_name} is signtool. An example of {path} is /foo/bar.", "FailedToStoreBackToMirror": "Failed to store {path} to {url}.", "_FailedToStoreBackToMirror.comment": "An example of {path} is /foo/bar. An example of {url} is https://github.com/microsoft/vcpkg.", "FailedToStoreBinaryCache": "Failed to store binary cache {path}", @@ -869,14 +868,8 @@ "GetParseFailureInfo": "Use '--debug' to get more information about the parse failures.", "GhaBinaryCacheDeprecated": "The 'x-gha' binary caching backend has been removed. Consider using a NuGet-based binary caching provider instead, see extended documentation at {url}.", "_GhaBinaryCacheDeprecated.comment": "The term 'x-gha' is a vcpkg configuration option An example of {url} is https://github.com/microsoft/vcpkg.", - "GitCommandFailed": "failed to execute: {command_line}", - "_GitCommandFailed.comment": "An example of {command_line} is vcpkg install zlib.", "GitCommitUpdateVersionDatabase": "git commit -m \"Update version database\"", "_GitCommitUpdateVersionDatabase.comment": "This is a command line; only the 'update version database' part should be localized", - "GitFailedToFetch": "failed to fetch ref {value} from repository {url}", - "_GitFailedToFetch.comment": "{value} is a git ref like 'origin/main' An example of {url} is https://github.com/microsoft/vcpkg.", - "GitFailedToInitializeLocalRepository": "failed to initialize local repository {path}", - "_GitFailedToInitializeLocalRepository.comment": "An example of {path} is /foo/bar.", "GitRegistryMustHaveBaseline": "The git registry \"{url}\" must have a \"baseline\" field that is a valid git commit SHA (40 hexadecimal characters).\nTo use the current latest versions, set baseline to that repo's HEAD, \"{commit_sha}\".", "_GitRegistryMustHaveBaseline.comment": "An example of {url} is https://github.com/microsoft/vcpkg. An example of {commit_sha} is 7cfad47ae9f68b183983090afd6337cd60fd4949.", "GitUnexpectedCommandOutputCmd": "git produced unexpected output when running {command_line}", @@ -1122,8 +1115,6 @@ "_InvalidValuePostPortfileIncludes.comment": "An example of {path} is /foo/bar.", "IrregularFile": "path was not a regular file: {path}", "_IrregularFile.comment": "An example of {path} is /foo/bar.", - "JsonErrorMustBeAnObject": "Expected \"{path}\" to be an object.", - "_JsonErrorMustBeAnObject.comment": "An example of {path} is /foo/bar.", "JsonFieldNotObject": "value of [\"{json_field}\"] must be an object", "_JsonFieldNotObject.comment": "An example of {json_field} is identifer.", "JsonFieldNotString": "value of [\"{json_field}\"] must be a string", @@ -1187,13 +1178,10 @@ "_MismatchedSpec.comment": "{expected} and {actual} are package specs like 'zlib:x64-windows' An example of {path} is /foo/bar.", "MismatchedType": "{json_field}: mismatched type: expected {json_type}", "_MismatchedType.comment": "An example of {json_field} is identifer. An example of {json_type} is an array of identifiers.", - "Missing7zHeader": "Unable to find 7z header.", "MissingArgFormatManifest": "format-manifest was passed --convert-control without '--all'.\nThis doesn't do anything: control files passed explicitly are converted automatically.", "MissingClosingParen": "missing closing )", "MissingDependency": "Package {spec} is installed, but dependency {package_name} is not.", "_MissingDependency.comment": "An example of {spec} is zlib:x64-windows. An example of {package_name} is zlib.", - "MissingExtension": "Missing '{extension}' extension.", - "_MissingExtension.comment": "An example of {extension} is .exe.", "MissingOption": "This command requires --{option}", "_MissingOption.comment": "An example of {option} is editable.", "MissingOrInvalidIdentifer": "missing or invalid identifier", @@ -1292,8 +1280,6 @@ "PackageAlreadyRemoved": "unable to remove {spec}: already removed", "_PackageAlreadyRemoved.comment": "An example of {spec} is zlib:x64-windows.", "PackageDiscoveryHeader": "Package Discovery", - "PackageFailedtWhileExtracting": "'{value}' failed while extracting {path}.", - "_PackageFailedtWhileExtracting.comment": "'{value}' is either a tool name or a package name. An example of {path} is /foo/bar.", "PackageInfoHelp": "Display detailed information on packages", "PackageInstallationHeader": "Package Installation", "PackageLicenseSpdx": "Installed packages declare the following licenses:", @@ -1445,6 +1431,8 @@ "SerializedBinParagraphHeader": "\nSerialized Binary Paragraph", "SettingEnvVar": "-- Setting \"{env_var}\" environment variables to \"{url}\".", "_SettingEnvVar.comment": "An example of env_var is \"HTTP(S)_PROXY\"'--' at the beginning must be preserved An example of {env_var} is VCPKG_DEFAULT_TRIPLET. An example of {url} is https://github.com/microsoft/vcpkg.", + "SevenZipIncorrectExtension": "failed to extract self-extracting 7zip archive because it is missing the .7z.exe file extension", + "SevenZipIncorrectHeader": "failed to extract self-extracting 7zip archive because a 7z header could not be found", "ShaPassedAsArgAndOption": "SHA512 passed as both an argument and as an option. Only pass one of these.", "ShaPassedWithConflict": "SHA512 passed, but --skip-sha512 was also passed; only do one or the other.", "ShallowRepositoryDetected": "vcpkg was cloned as a shallow repository. Try again with a full vcpkg clone.", @@ -1582,8 +1570,8 @@ "_UnexpectedStatePassPortMarkedFail.comment": "An example of {feature_spec} is zlib[featurea,featureb].", "UnexpectedSwitch": "unexpected switch: {option}", "_UnexpectedSwitch.comment": "Switch is a command line switch like --switch An example of {option} is editable.", - "UnexpectedToolOutput": "{tool_name} ({path}) produced unexpected output when attempting to determine the version:", - "_UnexpectedToolOutput.comment": "The actual command line output will be appended after this message. An example of {tool_name} is signtool. An example of {path} is /foo/bar.", + "UnexpectedToolOutput2": "{tool_name} produced unexpected output when attempting to determine the version:", + "_UnexpectedToolOutput2.comment": "The actual command line output will be appended after this message. An example of {tool_name} is signtool.", "UnexpectedWindowsArchitecture": "unexpected Windows host architecture: {actual}", "_UnexpectedWindowsArchitecture.comment": "{actual} is the CPU kind we observed like ARM or MIPS", "UnknownBaselineFileContent": "unrecognizable baseline entry; expected 'port:triplet=(fail|skip|pass)'", @@ -1763,6 +1751,8 @@ "WhileCheckingOutPortTreeIsh": "while checking out port {package_name} with git tree {git_tree_sha}", "_WhileCheckingOutPortTreeIsh.comment": "An example of {package_name} is zlib. An example of {git_tree_sha} is 7cfad47ae9f68b183983090afd6337cd60fd4949.", "WhileClearingThis": "while clearing this directory", + "WhileDeterminingVersion": "failed to run to determine the {tool_name} version", + "_WhileDeterminingVersion.comment": "An example of {tool_name} is signtool.", "WhileExtractingThisArchive": "while extracting this archive", "WhileGettingLocalTreeIshObjectsForPorts": "while getting local treeish objects for ports", "WhileLoadingBaselineVersionForPort": "while loading baseline version for {package_name}", @@ -1778,7 +1768,7 @@ "WhileRunningAssetCacheScriptCommandLine": "while running asset cache script command line", "WhileValidatingVersion": "while validating version: {version}", "_WhileValidatingVersion.comment": "An example of {version} is 1.3.8.", - "WindowsEnvMustAlwaysBePresent": "Expected {env_var} to be always set on Windows.", + "WindowsEnvMustAlwaysBePresent": "expected {env_var} to be always set on Windows", "_WindowsEnvMustAlwaysBePresent.comment": "An example of {env_var} is VCPKG_DEFAULT_TRIPLET.", "WindowsOnlyCommand": "This command only supports Windows.", "WroteNuGetPkgConfInfo": "Wrote NuGet package config information to {path}", diff --git a/src/vcpkg-test/binarycaching.cpp b/src/vcpkg-test/binarycaching.cpp index 4c0af9b384..f19711ca8b 100644 --- a/src/vcpkg-test/binarycaching.cpp +++ b/src/vcpkg-test/binarycaching.cpp @@ -14,8 +14,12 @@ using namespace vcpkg; struct KnowNothingBinaryProvider : IReadBinaryProvider { - void fetch(View actions, Span out_status) const override + void fetch(DiagnosticContext&, + const Filesystem& fs, + View actions, + Span out_status) const override { + REQUIRE(&fs == &always_failing_filesystem); REQUIRE(actions.size() == out_status.size()); for (size_t idx = 0; idx < out_status.size(); ++idx) { @@ -23,8 +27,12 @@ struct KnowNothingBinaryProvider : IReadBinaryProvider CHECK(out_status[idx] == RestoreResult::unavailable); } } - void precheck(View actions, Span out_status) const override + void precheck(DiagnosticContext&, + const Filesystem& fs, + View actions, + Span out_status) const override { + REQUIRE(&fs == &always_failing_filesystem); REQUIRE(actions.size() == out_status.size()); for (const auto c : out_status) { @@ -357,7 +365,9 @@ Version: 1.5 ipa_without_abi.package_dir = "pkgs/someheadpackage"; // test that the binary cache does the right thing. See also CHECKs etc. in KnowNothingBinaryProvider - uut.fetch(install_plan); // should have no effects + FullyBufferedDiagnosticContext fbdc; + uut.fetch(fbdc, always_failing_filesystem, install_plan); // should have no effects + REQUIRE(fbdc.empty()); } TEST_CASE ("XmlSerializer", "[XmlSerializer]") diff --git a/src/vcpkg-test/tools.cpp b/src/vcpkg-test/tools.cpp index 204684c07d..472cfaf2b5 100644 --- a/src/vcpkg-test/tools.cpp +++ b/src/vcpkg-test/tools.cpp @@ -45,27 +45,58 @@ Copyright (C) 2006, 2019 Tatsuhiro Tsujikawa)"); TEST_CASE ("extract_prefixed_nonwhitespace", "[tools]") { - CHECK(extract_prefixed_nonwhitespace("fooutil version ", "fooutil", "fooutil version 1.2", "fooutil.exe") - .value_or_exit(VCPKG_LINE_INFO) == "1.2"); - CHECK(extract_prefixed_nonwhitespace("fooutil version ", "fooutil", "fooutil version 1.2 ", "fooutil.exe") - .value_or_exit(VCPKG_LINE_INFO) == "1.2"); - auto error_result = - extract_prefixed_nonwhitespace("fooutil version ", "fooutil", "malformed output", "fooutil.exe"); - CHECK(!error_result.has_value()); - CHECK(error_result.error() == "error: fooutil (fooutil.exe) produced unexpected output when attempting to " - "determine the version:\nmalformed output"); + { + Optional maybe_output = "fooutil version 1.2"; + FullyBufferedDiagnosticContext fbdc; + extract_prefixed_nonwhitespace(fbdc, "fooutil version ", "fooutil", maybe_output, "fooutil.exe"); + CHECK(fbdc.empty()); + CHECK(maybe_output.value_or_exit(VCPKG_LINE_INFO) == "1.2"); + } + + { + Optional maybe_output = "fooutil version 1.2 "; + FullyBufferedDiagnosticContext fbdc; + extract_prefixed_nonwhitespace(fbdc, "fooutil version ", "fooutil", maybe_output, "fooutil.exe"); + CHECK(fbdc.empty()); + CHECK(maybe_output.value_or_exit(VCPKG_LINE_INFO) == "1.2"); + } + + { + Optional maybe_output = "malformed output"; + FullyBufferedDiagnosticContext fbdc; + extract_prefixed_nonwhitespace(fbdc, "fooutil version ", "fooutil", maybe_output, "fooutil.exe"); + CHECK(!maybe_output); + CHECK(fbdc.to_string() == "fooutil.exe: error: fooutil produced unexpected output when attempting to determine " + "the version:\nmalformed output"); + } } TEST_CASE ("extract_prefixed_nonquote", "[tools]") { - CHECK(extract_prefixed_nonquote("fooutil version ", "fooutil", "fooutil version 1.2\"", "fooutil.exe") - .value_or_exit(VCPKG_LINE_INFO) == "1.2"); - CHECK(extract_prefixed_nonquote("fooutil version ", "fooutil", "fooutil version 1.2 \" ", "fooutil.exe") - .value_or_exit(VCPKG_LINE_INFO) == "1.2 "); - auto error_result = extract_prefixed_nonquote("fooutil version ", "fooutil", "malformed output", "fooutil.exe"); - CHECK(!error_result.has_value()); - CHECK(error_result.error() == "error: fooutil (fooutil.exe) produced unexpected output when attempting to " - "determine the version:\nmalformed output"); + { + Optional maybe_output = "fooutil version 1.2\""; + FullyBufferedDiagnosticContext fbdc; + extract_prefixed_nonquote(fbdc, "fooutil version ", "fooutil", maybe_output, "fooutil.exe"); + CHECK(fbdc.empty()); + CHECK(maybe_output.value_or_exit(VCPKG_LINE_INFO) == "1.2"); + } + + { + Optional maybe_output = "fooutil version 1.2 \""; + FullyBufferedDiagnosticContext fbdc; + extract_prefixed_nonquote(fbdc, "fooutil version ", "fooutil", maybe_output, "fooutil.exe"); + CHECK(fbdc.empty()); + CHECK(maybe_output.value_or_exit(VCPKG_LINE_INFO) == "1.2 "); + } + + { + Optional maybe_output = "malformed output"; + FullyBufferedDiagnosticContext fbdc; + extract_prefixed_nonquote(fbdc, "fooutil version ", "fooutil", maybe_output, "fooutil.exe"); + CHECK(!maybe_output); + CHECK(fbdc.to_string() == "fooutil.exe: error: fooutil produced unexpected output when attempting to determine " + "the version:\nmalformed output"); + } } TEST_CASE ("parse_tool_data", "[tools]") @@ -109,7 +140,9 @@ TEST_CASE ("parse_tool_data", "[tools]") ] })"; - auto maybe_data = parse_tool_data(tool_doc, "vcpkgTools.json"); + FullyBufferedDiagnosticContext fbdc; + auto maybe_data = parse_tool_data(fbdc, tool_doc, "vcpkgTools.json"); + REQUIRE(fbdc.empty()); REQUIRE(maybe_data.has_value()); auto data = maybe_data.value_or_exit(VCPKG_LINE_INFO); @@ -210,24 +243,39 @@ TEST_CASE ("parse_tool_data", "[tools]") TEST_CASE ("parse_tool_data errors", "[tools]") { - auto empty = parse_tool_data("", "empty.json"); - REQUIRE(!empty.has_value()); - CHECK(Strings::starts_with(empty.error(), "empty.json:1:1: error: Unexpected EOF")); - - auto top_level_json = parse_tool_data("[]", "top_level.json"); - REQUIRE(!top_level_json.has_value()); - CHECK("Expected \"top_level.json\" to be an object." == top_level_json.error()); - - auto missing_required = - parse_tool_data(R"({ "schema-version": 1, "tools": [{ "executable": "git.exe" }]})", "missing_required.json"); - REQUIRE(!missing_required.has_value()); - CHECK("missing_required.json: error: $.tools[0] (tool metadata): missing required field 'name' (a string)\n" - "missing_required.json: error: $.tools[0] (tool metadata): missing required field 'os' (a tool data " - "operating system)\n" - "missing_required.json: error: $.tools[0] (tool metadata): missing required field 'version' (a tool data " - "version)" == missing_required.error()); - - auto uexpected_field = parse_tool_data(R"( + { + FullyBufferedDiagnosticContext fbdc; + auto empty = parse_tool_data(fbdc, "", "empty.json"); + REQUIRE(!empty.has_value()); + CHECK(fbdc.to_string() == R"(empty.json:1:1: error: Unexpected EOF; expected value + on expression: + ^)"); + } + + { + FullyBufferedDiagnosticContext fbdc; + auto top_level_json = parse_tool_data(fbdc, "[]", "top_level.json"); + REQUIRE(!top_level_json.has_value()); + CHECK(fbdc.to_string() == "top_level.json: error: expected an object"); + } + + { + FullyBufferedDiagnosticContext fbdc; + auto missing_required = parse_tool_data( + fbdc, R"({ "schema-version": 1, "tools": [{ "executable": "git.exe" }]})", "missing_required.json"); + REQUIRE(!missing_required.has_value()); + CHECK(fbdc.to_string() == + "missing_required.json: error: $.tools[0] (tool metadata): missing required field 'name' (a string)\n" + "missing_required.json: error: $.tools[0] (tool metadata): missing required field 'os' (a tool data " + "operating system)\n" + "missing_required.json: error: $.tools[0] (tool metadata): missing required field 'version' (a tool data " + "version)"); + } + + { + FullyBufferedDiagnosticContext fbdc; + auto uexpected_field = parse_tool_data(fbdc, + R"( { "schema-version": 1, "tools": [{ @@ -237,12 +285,16 @@ TEST_CASE ("parse_tool_data errors", "[tools]") "arc": "x64" }] })", - "uexpected_field.json"); - REQUIRE(!uexpected_field.has_value()); - CHECK("uexpected_field.json: error: $.tools[0] (tool metadata): unexpected field 'arc', did you mean 'arch'?" == - uexpected_field.error()); - - auto invalid_os = parse_tool_data(R"( + "uexpected_field.json"); + REQUIRE(!uexpected_field.has_value()); + CHECK(fbdc.to_string() == + "uexpected_field.json: error: $.tools[0] (tool metadata): unexpected field 'arc', did you mean 'arch'?"); + } + + { + FullyBufferedDiagnosticContext fbdc; + auto invalid_os = parse_tool_data(fbdc, + R"( { "schema-version": 1, "tools": [{ @@ -251,13 +303,18 @@ TEST_CASE ("parse_tool_data errors", "[tools]") "version": "2.7.4" }] })", - "invalid_os.json"); - REQUIRE(!invalid_os.has_value()); - CHECK( - "invalid_os.json: error: $.tools[0].os (a tool data operating system): Invalid tool operating system: notanos. " - "Expected one of: windows, osx, linux, freebsd, openbsd, netbsd, solaris" == invalid_os.error()); - - auto invalid_version = parse_tool_data(R"( + "invalid_os.json"); + REQUIRE(!invalid_os.has_value()); + CHECK(fbdc.to_string() == + "invalid_os.json: error: $.tools[0].os (a tool data operating system): Invalid tool operating system: " + "notanos. " + "Expected one of: windows, osx, linux, freebsd, openbsd, netbsd, solaris"); + } + + { + FullyBufferedDiagnosticContext fbdc; + auto invalid_version = parse_tool_data(fbdc, + R"( { "schema-version": 1, "tools": [{ @@ -266,12 +323,17 @@ TEST_CASE ("parse_tool_data errors", "[tools]") "version": "abc" }] })", - "invalid_version.json"); - REQUIRE(!invalid_version.has_value()); - CHECK("invalid_version.json: error: $.tools[0].version (a tool data version): Invalid tool version; expected a " - "string containing a substring of between 1 and 3 numbers separated by dots." == invalid_version.error()); - - auto invalid_arch = parse_tool_data(R"( + "invalid_version.json"); + REQUIRE(!invalid_version.has_value()); + CHECK(fbdc.to_string() == + "invalid_version.json: error: $.tools[0].version (a tool data version): Invalid tool version; expected a " + "string containing a substring of between 1 and 3 numbers separated by dots."); + } + + { + FullyBufferedDiagnosticContext fbdc; + auto invalid_arch = parse_tool_data(fbdc, + R"( { "schema-version": 1, "tools": [{ @@ -281,13 +343,19 @@ TEST_CASE ("parse_tool_data errors", "[tools]") "arch": "notanarchitecture" }] })", - "invalid_arch.json"); - REQUIRE(!invalid_arch.has_value()); - CHECK("invalid_arch.json: error: $.tools[0].arch (a CPU architecture): Invalid architecture: notanarchitecture. " - "Expected one of: x86, x64, amd64, arm, arm64, arm64ec, s390x, ppc64le, riscv32, riscv64, loongarch32, " - "loongarch64, mips64" == invalid_arch.error()); - - auto invalid_sha512 = parse_tool_data(R"( + "invalid_arch.json"); + REQUIRE(!invalid_arch.has_value()); + CHECK( + fbdc.to_string() == + "invalid_arch.json: error: $.tools[0].arch (a CPU architecture): Invalid architecture: notanarchitecture. " + "Expected one of: x86, x64, amd64, arm, arm64, arm64ec, s390x, ppc64le, riscv32, riscv64, loongarch32, " + "loongarch64, mips64"); + } + + { + FullyBufferedDiagnosticContext fbdc; + auto invalid_sha512 = parse_tool_data(fbdc, + R"( { "schema-version": 1, "tools": [{ @@ -298,9 +366,83 @@ TEST_CASE ("parse_tool_data errors", "[tools]") "sha512": "notasha512" }] })", - "invalid_sha512.json"); + "invalid_sha512.json"); - REQUIRE(!invalid_sha512.has_value()); - CHECK("invalid_sha512.json: error: $.tools[0].sha512 (a SHA-512 hash): invalid SHA-512 hash: notasha512\n" - "SHA-512 hash must be 128 characters long and contain only hexadecimal digits" == invalid_sha512.error()); + REQUIRE(!invalid_sha512.has_value()); + CHECK(fbdc.to_string() == + "invalid_sha512.json: error: $.tools[0].sha512 (a SHA-512 hash): invalid SHA-512 hash: notasha512\n" + "SHA-512 hash must be 128 characters long and contain only hexadecimal digits"); + } +} + +TEST_CASE ("ContextCache", "[tools]") +{ + struct JustOpt + { + int value; + Optional operator()(DiagnosticContext& context) const + { + context.statusln(LocalizedString::from_raw(fmt::format("The value is {}", value))); + return value; + } + }; + + ContextCache cache; + + // success miss then hit (middle key) + FullyBufferedDiagnosticContext fbdc1; + const auto* first_ptr = cache.get_lazy(fbdc1, "durian", JustOpt{42}); + REQUIRE(first_ptr != nullptr); + CHECK(*first_ptr == 42); + CHECK(fbdc1.to_string() == "The value is 42"); + // functor should NOT run, status lines not replayed: + FullyBufferedDiagnosticContext fbdc1_hit; + const auto* hit_ptr = cache.get_lazy(fbdc1_hit, "durian", JustOpt{12345}); + REQUIRE(hit_ptr == first_ptr); + CHECK(fbdc1_hit.empty()); + + // insert before existing element + FullyBufferedDiagnosticContext fbdc2; + const auto* below_ptr = cache.get_lazy(fbdc2, "apple", JustOpt{1729}); + REQUIRE(below_ptr != nullptr); + CHECK(*below_ptr == 1729); + CHECK(fbdc2.to_string() == "The value is 1729"); + FullyBufferedDiagnosticContext fbdc2_hit; + const auto* below_hit_ptr = cache.get_lazy(fbdc2_hit, "apple", JustOpt{1}); + REQUIRE(below_hit_ptr == below_ptr); + CHECK(fbdc2_hit.empty()); + + // insert above existing element + FullyBufferedDiagnosticContext fbdc3; + const auto* above_ptr = cache.get_lazy(fbdc3, "melon", JustOpt{1234}); + REQUIRE(above_ptr != nullptr); + CHECK(*above_ptr == 1234); + CHECK(fbdc3.to_string() == "The value is 1234"); + FullyBufferedDiagnosticContext fbdc3_hit; + const auto* above_hit_ptr = cache.get_lazy(fbdc3_hit, "melon", JustOpt{2}); + REQUIRE(above_hit_ptr == above_ptr); + CHECK(fbdc3_hit.empty()); + + // error case: provider returns empty optional and reports diagnostic; ensure cached error re-reports but provider + // not re-run + int error_call_count = 0; + auto failing_provider = [&error_call_count](DiagnosticContext& ctx) -> Optional { + ++error_call_count; + ctx.statusln(LocalizedString::from_raw(fmt::format("The number of calls is {}", error_call_count))); + ctx.report_error(LocalizedString::from_raw("bad")); + return nullopt; // failure + }; + FullyBufferedDiagnosticContext fbdc_err1; + const auto* err1 = cache.get_lazy(fbdc_err1, "kiwi", failing_provider); + REQUIRE(err1 == nullptr); + CHECK(!fbdc_err1.empty()); + CHECK(fbdc_err1.to_string() == "The number of calls is 1\nerror: bad"); + + FullyBufferedDiagnosticContext fbdc_err2; + const auto* err2 = cache.get_lazy(fbdc_err2, "kiwi", failing_provider); // should not invoke provider again + REQUIRE(err2 == nullptr); + CHECK(!fbdc_err2.empty()); + CHECK(fbdc_err2.to_string() == "error: bad"); // status line not replayed + const auto second_error_output = fbdc_err2.to_string(); + CHECK(error_call_count == 1); } diff --git a/src/vcpkg/archives.cpp b/src/vcpkg/archives.cpp index c50370be93..77a3277e2a 100644 --- a/src/vcpkg/archives.cpp +++ b/src/vcpkg/archives.cpp @@ -13,10 +13,45 @@ namespace { using namespace vcpkg; + bool postprocess_extract_archive(DiagnosticContext& context, + Optional&& maybe_exit_and_output, + const Path& tool, + const Path& archive) + { + if (auto* exit_and_output = maybe_exit_and_output.get()) + { + if (exit_and_output->exit_code == 0) + { + return true; + } + + auto error_text = + msg::format(msgArchiverFailedToExtractExitCode, msg::exit_code = exit_and_output->exit_code); + error_text.append_raw('\n'); + error_text.append_raw(std::move(exit_and_output->output)); + context.report(DiagnosticLine{DiagKind::Error, tool, std::move(error_text)}); + context.report(DiagnosticLine{DiagKind::Note, archive, msg::format(msgArchiveHere)}); + } + else + { + context.report(DiagnosticLine{DiagKind::Note, archive, msg::format(msgWhileExtractingThisArchive)}); + } + + return false; + } + #if defined(_WIN32) - void win32_extract_nupkg(const ToolCache& tools, MessageSink& status_sink, const Path& archive, const Path& to_path) + bool win32_extract_nupkg(DiagnosticContext& context, + const Filesystem& fs, + const ToolCache& tools, + const Path& archive, + const Path& to_path) { - const auto& nuget_exe = tools.get_tool_path(Tools::NUGET, status_sink); + const auto* nuget_exe = tools.get_tool_path(context, fs, Tools::NUGET); + if (!nuget_exe) + { + return false; + } const auto stem = archive.stem(); @@ -27,13 +62,14 @@ namespace auto is_digit_or_dot = [](char ch) { return ch == '.' || ParserBase::is_ascii_digit(ch); }; if (dot_after_name == stem.end() || !std::all_of(dot_after_name, stem.end(), is_digit_or_dot)) { - Checks::msg_exit_with_message(VCPKG_LINE_INFO, msgCouldNotDeduceNuGetIdAndVersion, msg::path = archive); + context.report(DiagnosticLine{DiagKind::Error, archive, msg::format(msgCouldNotDeduceNuGetIdAndVersion2)}); + return false; } StringView nugetid{stem.begin(), dot_after_name}; StringView version{dot_after_name + 1, stem.end()}; - auto cmd = Command{nuget_exe} + auto cmd = Command{*nuget_exe} .string_arg("install") .string_arg(nugetid) .string_arg("-Version") @@ -49,80 +85,85 @@ namespace .string_arg("-PackageSaveMode") .string_arg("nuspec"); - const auto result = flatten(cmd_execute_and_capture_output(cmd), Tools::NUGET); - if (!result) - { - Checks::msg_exit_with_message( - VCPKG_LINE_INFO, - msg::format(msgFailedToExtract, msg::path = archive).append_raw('\n').append(result.error())); - } + Optional maybe_exit_and_output = cmd_execute_and_capture_output(context, cmd); + return postprocess_extract_archive(context, std::move(maybe_exit_and_output), *nuget_exe, archive); } - void win32_extract_msi(const Path& archive, const Path& to_path) + bool win32_extract_msi(DiagnosticContext& context, const Path& archive, const Path& to_path) { + // msiexec is a WIN32/GUI application, not a console application and so needs special attention to wait + // until it finishes (wrap in cmd /c). + auto cmd = Command{"cmd"} + .string_arg("/c") + .string_arg("msiexec") + // "/a" is administrative mode, which unpacks without modifying the system + .string_arg("/a") + .string_arg(archive) + .string_arg("/qn") + // msiexec requires quotes to be after "TARGETDIR=": + // TARGETDIR="C:\full\path\to\dest" + .raw_arg(Strings::concat("TARGETDIR=", Command{to_path}.extract())); + + RedirectedProcessLaunchSettings settings; + settings.encoding = Encoding::Utf16; + // MSI installation sometimes requires a global lock and fails if another installation is concurrent. Loop // to enable retries. + + AttemptDiagnosticContext adc{context}; for (unsigned int i = 0;; ++i) { - // msiexec is a WIN32/GUI application, not a console application and so needs special attention to wait - // until it finishes (wrap in cmd /c). - auto cmd = Command{"cmd"} - .string_arg("/c") - .string_arg("msiexec") - // "/a" is administrative mode, which unpacks without modifying the system - .string_arg("/a") - .string_arg(archive) - .string_arg("/qn") - // msiexec requires quotes to be after "TARGETDIR=": - // TARGETDIR="C:\full\path\to\dest" - .raw_arg(Strings::concat("TARGETDIR=", Command{to_path}.extract())); - - RedirectedProcessLaunchSettings settings; - settings.encoding = Encoding::Utf16; - const auto maybe_code_and_output = cmd_execute_and_capture_output(cmd, settings); + auto maybe_code_and_output = cmd_execute_and_capture_output(adc, cmd, settings); if (auto code_and_output = maybe_code_and_output.get()) { if (code_and_output->exit_code == 0) { // Success - break; + adc.handle(); + return true; } if (i < 19 && code_and_output->exit_code == 1618) { // ERROR_INSTALL_ALREADY_RUNNING - msg::println(msgAnotherInstallationInProgress); + adc.statusln(msg::format(msgAnotherInstallationInProgress)); std::this_thread::sleep_for(std::chrono::seconds(6)); continue; } + + auto error_text = + msg::format(msgArchiverFailedToExtractExitCode, msg::exit_code = code_and_output->exit_code); + error_text.append_raw('\n'); + error_text.append_raw(std::move(code_and_output->output)); + adc.report(DiagnosticLine{DiagKind::Error, "msiexec", std::move(error_text)}); + adc.report(DiagnosticLine{DiagKind::Note, archive, msg::format(msgArchiveHere)}); + } + else + { + adc.report(DiagnosticLine{DiagKind::Note, archive, msg::format(msgWhileExtractingThisArchive)}); } - Checks::msg_exit_with_message(VCPKG_LINE_INFO, flatten(maybe_code_and_output, "msiexec").error()); + adc.commit(); + return false; } } - void win32_extract_with_seven_zip(const Path& seven_zip, const Path& archive, const Path& to_path) + bool win32_extract_with_seven_zip(DiagnosticContext& context, + const Path& seven_zip, + const Path& archive, + const Path& to_path) { static bool recursion_limiter_sevenzip = false; Checks::check_exit(VCPKG_LINE_INFO, !recursion_limiter_sevenzip); recursion_limiter_sevenzip = true; - - const auto maybe_output = flatten(cmd_execute_and_capture_output(Command{seven_zip} - .string_arg("x") - .string_arg(archive) - .string_arg(fmt::format("-o{}", to_path)) - .string_arg("-y")), - Tools::SEVEN_ZIP); - if (!maybe_output) - { - Checks::msg_exit_with_message( - VCPKG_LINE_INFO, - msg::format(msgPackageFailedtWhileExtracting, msg::value = "7zip", msg::path = archive) - .append_raw('\n') - .append(maybe_output.error())); - } - + auto maybe_exit_and_output = cmd_execute_and_capture_output(context, + Command{seven_zip} + .string_arg("x") + .string_arg(archive) + .string_arg(fmt::format("-o{}", to_path)) + .string_arg("-y")); recursion_limiter_sevenzip = false; + return postprocess_extract_archive(context, std::move(maybe_exit_and_output), seven_zip, archive); } #endif // ^^^ _WIN32 @@ -173,9 +214,9 @@ namespace vcpkg } } - void extract_archive(const Filesystem& fs, + bool extract_archive(DiagnosticContext& context, + const Filesystem& fs, const ToolCache& tools, - MessageSink& status_sink, const Path& archive, const Path& to_path) { @@ -184,62 +225,98 @@ namespace vcpkg #if defined(_WIN32) switch (ext_type) { - case ExtractionType::Unknown: break; - case ExtractionType::Nupkg: win32_extract_nupkg(tools, status_sink, archive, to_path); break; - case ExtractionType::Msi: win32_extract_msi(archive, to_path); break; + case ExtractionType::Unknown: break; // try cmake for unkown extensions, i.e., vsix => zip, below + case ExtractionType::Nupkg: return win32_extract_nupkg(context, fs, tools, archive, to_path); + case ExtractionType::Msi: return win32_extract_msi(context, archive, to_path); break; case ExtractionType::SevenZip: - win32_extract_with_seven_zip(tools.get_tool_path(Tools::SEVEN_ZIP_R, status_sink), archive, to_path); - break; + if (const auto* tool = tools.get_tool_path(context, fs, Tools::SEVEN_ZIP_R)) + { + return win32_extract_with_seven_zip(context, *tool, archive, to_path); + } + + return false; case ExtractionType::Zip: - win32_extract_with_seven_zip(tools.get_tool_path(Tools::SEVEN_ZIP, status_sink), archive, to_path); - break; + if (const auto* tool = tools.get_tool_path(context, fs, Tools::SEVEN_ZIP)) + { + return win32_extract_with_seven_zip(context, *tool, archive, to_path); + } + + return false; case ExtractionType::Tar: - extract_tar(tools.get_tool_path(Tools::TAR, status_sink), archive, to_path); - break; + if (const auto* tool = tools.get_tool_path(context, fs, Tools::TAR)) + { + return extract_tar(context, *tool, archive, to_path); + } + + return false; case ExtractionType::Exe: - win32_extract_with_seven_zip(tools.get_tool_path(Tools::SEVEN_ZIP, status_sink), archive, to_path); - break; + if (const auto* tool = tools.get_tool_path(context, fs, Tools::SEVEN_ZIP)) + { + return win32_extract_with_seven_zip(context, *tool, archive, to_path); + } + + return false; case ExtractionType::SelfExtracting7z: + { const Path filename = archive.filename(); const Path stem = filename.stem(); const Path to_archive = Path(archive.parent_path()) / stem; - win32_extract_self_extracting_7z(fs, archive, to_archive); - extract_archive(fs, tools, status_sink, to_archive, to_path); - break; + if (!win32_extract_self_extracting_7z(context, fs, archive, to_archive)) + { + return false; + } + + return extract_archive(context, fs, tools, to_archive, to_path); + } + + default: Checks::unreachable(VCPKG_LINE_INFO); } #else - (void)fs; if (ext_type == ExtractionType::Tar) { - extract_tar(tools.get_tool_path(Tools::TAR, status_sink), archive, to_path); + if (const auto* tool = tools.get_tool_path(context, fs, Tools::TAR)) + { + return extract_tar(context, *tool, archive, to_path); + } + + return false; } if (ext_type == ExtractionType::Zip) { - ProcessLaunchSettings settings; + auto cmd = Command{"unzip"}.string_arg("-qqo").string_arg(archive); + RedirectedProcessLaunchSettings settings; settings.working_directory = to_path; - const auto code = cmd_execute(Command{"unzip"}.string_arg("-qqo").string_arg(archive), settings) - .value_or_exit(VCPKG_LINE_INFO); - Checks::msg_check_exit(VCPKG_LINE_INFO, - code == 0, - msgPackageFailedtWhileExtracting, - msg::value = "unzip", - msg::path = archive); - } + auto maybe_code_and_output = cmd_execute_and_capture_output(context, cmd, settings); + if (!check_zero_exit_code(context, cmd, maybe_code_and_output)) + { + context.report(DiagnosticLine{DiagKind::Note, archive, msg::format(msgWhileExtractingThisArchive)}); + return false; + } + return true; + } #endif - // Try cmake for unkown extensions, i.e., vsix => zip + + // try cmake for unkown extensions, i.e., vsix => zip if (ext_type == ExtractionType::Unknown) { - extract_tar_cmake(tools.get_tool_path(Tools::CMAKE, status_sink), archive, to_path); + if (const auto* tool = tools.get_tool_path(context, fs, Tools::CMAKE)) + { + return extract_tar_cmake(context, *tool, archive, to_path); + } + + return false; } + + return false; } - Path extract_archive_to_temp_subdirectory(const Filesystem& fs, - const ToolCache& tools, - MessageSink& status_sink, - const Path& archive, - const Path& to_path) + Optional extract_archive_to_temp_subdirectory(DiagnosticContext& context, + const Filesystem& fs, + const ToolCache& tools, + const Path& archive, + const Path& to_path) { Path to_path_partial = to_path + ".partial"; #if defined(_WIN32) @@ -248,30 +325,39 @@ namespace vcpkg fs.remove_all(to_path_partial, VCPKG_LINE_INFO); fs.create_directories(to_path_partial, VCPKG_LINE_INFO); - extract_archive(fs, tools, status_sink, archive, to_path_partial); - return to_path_partial; + if (extract_archive(context, fs, tools, archive, to_path_partial)) + { + return to_path_partial; + } + + return nullopt; } #ifdef _WIN32 - void win32_extract_self_extracting_7z(const Filesystem& fs, const Path& archive, const Path& to_path) + bool win32_extract_self_extracting_7z(DiagnosticContext& context, + const Filesystem& fs, + const Path& archive, + const Path& to_path) { static constexpr StringLiteral header_7z = "7z\xBC\xAF\x27\x1C"; const Path stem = archive.stem(); const auto subext = stem.extension(); - Checks::msg_check_exit(VCPKG_LINE_INFO, - Strings::case_insensitive_ascii_equals(subext, ".7z"), - msg::format(msgPackageFailedtWhileExtracting, msg::value = "7zip", msg::path = archive) - .append(msgMissingExtension, msg::extension = ".7z.exe")); + if (!Strings::case_insensitive_ascii_equals(subext, ".7z")) + { + context.report(DiagnosticLine{DiagKind::Error, archive, msg::format(msgSevenZipIncorrectExtension)}); + return false; + } auto contents = fs.read_contents(archive, VCPKG_LINE_INFO); - // try to chop off the beginning of the self extractor before the embedded 7z archive // some 7z self extractors, such as PortableGit-2.43.0-32-bit.7z.exe have 1 header // some 7z self extractors, such as 7z2408-x64.exe, have 2 headers auto pos = contents.find(header_7z.data(), 0, header_7z.size()); - Checks::msg_check_exit(VCPKG_LINE_INFO, - pos != std::string::npos, - msg::format(msgPackageFailedtWhileExtracting, msg::value = "7zip", msg::path = archive) - .append(msgMissing7zHeader)); + if (pos == std::string::npos) + { + context.report(DiagnosticLine{DiagKind::Error, archive, msg::format(msgSevenZipIncorrectHeader)}); + return false; + } + // no bounds check necessary because header_7z is nonempty: auto pos2 = contents.find(header_7z.data(), pos + 1, header_7z.size()); if (pos2 != std::string::npos) @@ -281,33 +367,29 @@ namespace vcpkg StringView contents_sv = contents; fs.write_contents(to_path, contents_sv.substr(pos), VCPKG_LINE_INFO); + return true; } #endif - void extract_tar(const Path& tar_tool, const Path& archive, const Path& to_path) + bool extract_tar(DiagnosticContext& context, const Path& tar_tool, const Path& archive, const Path& to_path) { - ProcessLaunchSettings settings; + RedirectedProcessLaunchSettings settings; settings.working_directory = to_path; - const auto code = cmd_execute(Command{tar_tool}.string_arg("xzf").string_arg(archive), settings); - Checks::msg_check_exit(VCPKG_LINE_INFO, - succeeded(code), - msgPackageFailedtWhileExtracting, - msg::value = "tar", - msg::path = archive); + auto maybe_exit_and_output = + cmd_execute_and_capture_output(context, Command{tar_tool}.string_arg("xzf").string_arg(archive), settings); + return postprocess_extract_archive(context, std::move(maybe_exit_and_output), tar_tool, archive); } - void extract_tar_cmake(const Path& cmake_tool, const Path& archive, const Path& to_path) + bool extract_tar_cmake(DiagnosticContext& context, const Path& cmake_tool, const Path& archive, const Path& to_path) { // Note that CMake's built in tar can extract more archive types than many system tars; e.g. 7z - ProcessLaunchSettings settings; + RedirectedProcessLaunchSettings settings; settings.working_directory = to_path; - const auto code = cmd_execute( - Command{cmake_tool}.string_arg("-E").string_arg("tar").string_arg("xzf").string_arg(archive), settings); - Checks::msg_check_exit(VCPKG_LINE_INFO, - succeeded(code), - msgPackageFailedtWhileExtracting, - msg::value = "CMake", - msg::path = archive); + auto maybe_exit_and_output = cmd_execute_and_capture_output( + context, + Command{cmake_tool}.string_arg("-E").string_arg("tar").string_arg("xzf").string_arg(archive), + settings); + return postprocess_extract_archive(context, std::move(maybe_exit_and_output), cmake_tool, archive); } bool ZipTool::compress_directory_to_zip(DiagnosticContext& context, @@ -340,14 +422,23 @@ namespace vcpkg #endif } - void ZipTool::setup(const ToolCache& cache, MessageSink& status_sink) + bool ZipTool::setup(DiagnosticContext& context, const Filesystem& fs, const ToolCache& cache) { #if defined(_WIN32) - seven_zip.emplace(cache.get_tool_path(Tools::SEVEN_ZIP, status_sink)); -#endif + if (const auto* tool = cache.get_tool_path(context, fs, Tools::SEVEN_ZIP)) + { + seven_zip.emplace(*tool); + return true; + } + + return false; +#else // Unused on non-Windows + (void)context; + (void)fs; (void)cache; - (void)status_sink; + return true; +#endif } Command ZipTool::decompress_zip_archive_cmd(const Path& dst, const Path& archive_path) const diff --git a/src/vcpkg/base/diagnostics.cpp b/src/vcpkg/base/diagnostics.cpp index ef40c2a4b1..b31f8aa93c 100644 --- a/src/vcpkg/base/diagnostics.cpp +++ b/src/vcpkg/base/diagnostics.cpp @@ -196,9 +196,9 @@ namespace vcpkg void PrintingDiagnosticContext::statusln(const MessageLine& message) { sink.println(message); } void PrintingDiagnosticContext::statusln(MessageLine&& message) { sink.println(std::move(message)); } - void BufferedDiagnosticContext::report(const DiagnosticLine& line) { lines.push_back(line); } - void BufferedDiagnosticContext::report(DiagnosticLine&& line) { lines.push_back(std::move(line)); } - void BufferedDiagnosticContext::print_to(MessageSink& sink) const + void BasicBufferedDiagnosticContext::report(const DiagnosticLine& line) { lines.push_back(line); } + void BasicBufferedDiagnosticContext::report(DiagnosticLine&& line) { lines.push_back(std::move(line)); } + void BasicBufferedDiagnosticContext::print_to(MessageSink& sink) const { for (auto&& line : lines) { @@ -209,25 +209,90 @@ namespace vcpkg // Converts this message into a string // Prefer print() if possible because it applies color // Not safe to use in the face of concurrent calls to report() - std::string BufferedDiagnosticContext::to_string() const { return adapt_to_string(*this); } - void BufferedDiagnosticContext::to_string(std::string& target) const { joined_line_to_string(lines, target); } + std::string BasicBufferedDiagnosticContext::to_string() const { return adapt_to_string(*this); } + void BasicBufferedDiagnosticContext::to_string(std::string& target) const { joined_line_to_string(lines, target); } - bool BufferedDiagnosticContext::any_errors() const noexcept { return diagnostic_lines_any_errors(lines); } - bool BufferedDiagnosticContext::empty() const noexcept { return lines.empty(); } + bool BasicBufferedDiagnosticContext::any_errors() const noexcept { return diagnostic_lines_any_errors(lines); } + bool BasicBufferedDiagnosticContext::empty() const noexcept { return lines.empty(); } - void BufferedDiagnosticContext::statusln(const LocalizedString& message) { status_sink.println(message); } - void BufferedDiagnosticContext::statusln(LocalizedString&& message) { status_sink.println(std::move(message)); } - void BufferedDiagnosticContext::statusln(const MessageLine& message) { status_sink.println(message); } - void BufferedDiagnosticContext::statusln(MessageLine&& message) { status_sink.println(std::move(message)); } + void SinkBufferedDiagnosticContext::statusln(const LocalizedString& message) { status_sink.println(message); } + void SinkBufferedDiagnosticContext::statusln(LocalizedString&& message) { status_sink.println(std::move(message)); } + void SinkBufferedDiagnosticContext::statusln(const MessageLine& message) { status_sink.println(message); } + void SinkBufferedDiagnosticContext::statusln(MessageLine&& message) { status_sink.println(std::move(message)); } - void FullyBufferedDiagnosticContext::report(const DiagnosticLine& line) { lines.push_back(line.to_message_line()); } - void FullyBufferedDiagnosticContext::report(DiagnosticLine&& line) + void ContextBufferedDiagnosticContext::statusln(const LocalizedString& message) { - lines.push_back(std::move(line).to_message_line()); + status_context.statusln(message); + } + void ContextBufferedDiagnosticContext::statusln(LocalizedString&& message) + { + status_context.statusln(std::move(message)); + } + void ContextBufferedDiagnosticContext::statusln(const MessageLine& message) { status_context.statusln(message); } + void ContextBufferedDiagnosticContext::statusln(MessageLine&& message) + { + status_context.statusln(std::move(message)); + } + + DiagnosticOrMessageLine::DiagnosticOrMessageLine(const DiagnosticLine& dl) : dl(dl), is_diagnostic(true) { } + DiagnosticOrMessageLine::DiagnosticOrMessageLine(DiagnosticLine&& dl) : dl(std::move(dl)), is_diagnostic(true) { } + DiagnosticOrMessageLine::DiagnosticOrMessageLine(const MessageLine& ml) : ml(ml), is_diagnostic(false) { } + DiagnosticOrMessageLine::DiagnosticOrMessageLine(MessageLine&& ml) : ml(std::move(ml)), is_diagnostic(false) { } + + DiagnosticOrMessageLine::DiagnosticOrMessageLine(const DiagnosticOrMessageLine& other) + : is_diagnostic(other.is_diagnostic) + { + if (is_diagnostic) + new (&dl) DiagnosticLine(other.dl); + else + new (&ml) MessageLine(other.ml); } - void FullyBufferedDiagnosticContext::statusln(const LocalizedString& message) { lines.emplace_back(message); } - void FullyBufferedDiagnosticContext::statusln(LocalizedString&& message) { lines.emplace_back(std::move(message)); } + DiagnosticOrMessageLine::DiagnosticOrMessageLine(DiagnosticOrMessageLine&& other) + : is_diagnostic(other.is_diagnostic) + { + if (is_diagnostic) + new (&dl) DiagnosticLine(std::move(other.dl)); + else + new (&ml) MessageLine(std::move(other.ml)); + } + + DiagnosticOrMessageLine::~DiagnosticOrMessageLine() + { + if (is_diagnostic) + { + dl.~DiagnosticLine(); + } + else + { + ml.~MessageLine(); + } + } + + std::string DiagnosticOrMessageLine::to_string() const { return adapt_to_string(*this); } + void DiagnosticOrMessageLine::to_string(std::string& target) const + { + if (is_diagnostic) + { + dl.to_string(target); + } + else + { + ml.to_string(target); + } + } + + void FullyBufferedDiagnosticContext::report(const DiagnosticLine& line) { lines.push_back(line); } + void FullyBufferedDiagnosticContext::report(DiagnosticLine&& line) { lines.push_back(std::move(line)); } + + void FullyBufferedDiagnosticContext::statusln(const LocalizedString& message) + { + lines.emplace_back(MessageLine{message}); + } + void FullyBufferedDiagnosticContext::statusln(LocalizedString&& message) + { + lines.emplace_back(MessageLine{std::move(message)}); + } void FullyBufferedDiagnosticContext::statusln(const MessageLine& message) { lines.emplace_back(message); } void FullyBufferedDiagnosticContext::statusln(MessageLine&& message) { lines.emplace_back(std::move(message)); } @@ -235,7 +300,14 @@ namespace vcpkg { for (auto&& line : lines) { - sink.println(line); + if (line.is_diagnostic) + { + line.dl.print_to(sink); + } + else + { + sink.println(line.ml); + } } } @@ -244,8 +316,35 @@ namespace vcpkg bool FullyBufferedDiagnosticContext::empty() const noexcept { return lines.empty(); } - void AttemptDiagnosticContext::report(const DiagnosticLine& line) { lines.push_back(line); } - void AttemptDiagnosticContext::report(DiagnosticLine&& line) { lines.push_back(std::move(line)); } + void FullyBufferedDiagnosticContext::report_to(DiagnosticContext& context) const& + { + for (auto&& line : lines) + { + if (line.is_diagnostic) + { + context.report(line.dl); + } + else + { + context.statusln(line.ml); + } + } + } + + void FullyBufferedDiagnosticContext::report_to(DiagnosticContext& context) && + { + for (auto&& line : lines) + { + if (line.is_diagnostic) + { + context.report(std::move(line.dl)); + } + else + { + context.statusln(std::move(line.ml)); + } + } + } void AttemptDiagnosticContext::statusln(const LocalizedString& message) { inner_context.statusln(message); } void AttemptDiagnosticContext::statusln(LocalizedString&& message) { inner_context.statusln(std::move(message)); } @@ -307,17 +406,8 @@ namespace }; NullDiagnosticContext null_diagnostic_context_instance; - - struct ConsoleDiagnosticContext final : DiagnosticContext - { - virtual void report(const DiagnosticLine& line) override { line.print_to(out_sink); } - virtual void statusln(const LocalizedString& message) override { out_sink.println(message); } - virtual void statusln(LocalizedString&& message) override { out_sink.println(std::move(message)); } - virtual void statusln(const MessageLine& message) override { out_sink.println(message); } - virtual void statusln(MessageLine&& message) override { out_sink.println(std::move(message)); } - }; - - ConsoleDiagnosticContext console_diagnostic_context_instance; + PrintingDiagnosticContext console_diagnostic_context_instance{out_sink}; + PrintingDiagnosticContext stderr_diagnostic_context_instance{stderr_sink}; struct StatusOnlyDiagnosticContext final : DiagnosticContext { @@ -335,5 +425,6 @@ namespace vcpkg { DiagnosticContext& null_diagnostic_context = null_diagnostic_context_instance; DiagnosticContext& console_diagnostic_context = console_diagnostic_context_instance; + DiagnosticContext& stderr_diagnostic_context = stderr_diagnostic_context_instance; DiagnosticContext& status_only_diagnostic_context = status_only_diagnostic_context_instance; } diff --git a/src/vcpkg/base/files.cpp b/src/vcpkg/base/files.cpp index 452710c855..617250284b 100644 --- a/src/vcpkg/base/files.cpp +++ b/src/vcpkg/base/files.cpp @@ -1626,12 +1626,29 @@ namespace vcpkg } ExpectedL ReadOnlyFilesystem::try_read_contents(const Path& file_path) const + { + return adapt_context_to_expected( + [](DiagnosticContext& context, const ReadOnlyFilesystem* this_, const Path& file_path) { + return this_->try_read_contents(context, file_path); + }, + this, + file_path); + } + + Optional ReadOnlyFilesystem::try_read_contents(DiagnosticContext& context, + const Path& file_path) const { std::error_code ec; auto maybe_contents = this->read_contents(file_path, ec); if (ec) { - return format_filesystem_call_error(ec, __func__, {file_path}); + context.report(DiagnosticLine{DiagKind::Error, + file_path, + msg::format(msgSystemApiErrorMessage, + msg::system_api = "read_contents", + msg::exit_code = ec.value(), + msg::error_msg = ec.message())}); + return nullopt; } return FileContents{std::move(maybe_contents), file_path.native()}; @@ -1720,6 +1737,25 @@ namespace vcpkg return maybe_files; } + Optional> ReadOnlyFilesystem::try_get_files_non_recursive(DiagnosticContext& context, + const Path& dir) const + { + std::error_code ec; + auto maybe_files = this->get_files_non_recursive(dir, ec); + if (ec) + { + context.report(DiagnosticLine{DiagKind::Error, + dir, + msg::format(msgSystemApiErrorMessage, + msg::system_api = "get_files_non_recursive", + msg::exit_code = ec.value(), + msg::error_msg = ec.message())}); + return nullopt; + } + + return maybe_files; + } + std::vector ReadOnlyFilesystem::get_directories_recursive(const Path& dir, LineInfo li) const { return this->try_get_directories_recursive(dir).value_or_exit(li); @@ -1773,6 +1809,25 @@ namespace vcpkg return maybe_directories; } + Optional> ReadOnlyFilesystem::try_get_directories_non_recursive(DiagnosticContext& context, + const Path& dir) const + { + std::error_code ec; + auto maybe_files = this->get_directories_non_recursive(dir, ec); + if (ec) + { + context.report(DiagnosticLine{DiagKind::Error, + dir, + msg::format(msgSystemApiErrorMessage, + msg::system_api = "get_directories_non_recursive", + msg::exit_code = ec.value(), + msg::error_msg = ec.message())}); + return nullopt; + } + + return maybe_files; + } + std::vector ReadOnlyFilesystem::get_regular_files_recursive(const Path& dir, LineInfo li) const { return this->try_get_regular_files_recursive(dir).value_or_exit(li); @@ -1991,6 +2046,20 @@ namespace vcpkg exit_filesystem_call_error(li, ec, __func__, {file_path}); } } + + bool Filesystem::write_contents(DiagnosticContext& context, const Path& file_path, StringView data) const + { + std::error_code ec; + this->write_contents(file_path, data, ec); + if (ec) + { + context.report_error(format_filesystem_call_error(ec, __func__, {file_path})); + return false; + } + + return true; + } + void Filesystem::write_rename_contents(const Path& file_path, const Path& temp_name, StringView data, @@ -2001,6 +2070,7 @@ namespace vcpkg this->write_contents(temp_path, data, li); this->rename(temp_path, file_path, li); } + void Filesystem::write_contents_and_dirs(const Path& file_path, StringView data, LineInfo li) const { std::error_code ec; @@ -2010,6 +2080,7 @@ namespace vcpkg exit_filesystem_call_error(li, ec, __func__, {file_path}); } } + void Filesystem::rename(const Path& old_path, const Path& new_path, LineInfo li) const { std::error_code ec; @@ -2019,6 +2090,20 @@ namespace vcpkg exit_filesystem_call_error(li, ec, __func__, {old_path, new_path}); } } + + bool Filesystem::rename(DiagnosticContext& context, const Path& old_path, const Path& new_path) const + { + std::error_code ec; + this->rename(old_path, new_path, ec); + if (ec) + { + context.report_error(format_filesystem_call_error(ec, __func__, {old_path, new_path})); + return false; + } + + return true; + } + void Filesystem::rename_with_retry(const Path& old_path, const Path& new_path, LineInfo li) const { std::error_code ec; @@ -2115,6 +2200,24 @@ namespace vcpkg return r; } + Optional Filesystem::remove(DiagnosticContext& context, const Path& target) const + { + std::error_code ec; + auto r = this->remove(target, ec); + if (ec) + { + context.report(DiagnosticLine{DiagKind::Error, + target, + msg::format(msgSystemApiErrorMessage, + msg::system_api = "remove", + msg::exit_code = ec.value(), + msg::error_msg = ec.message())}); + return nullopt; + } + + return r; + } + bool Filesystem::create_directory(const Path& new_directory, LineInfo li) const { std::error_code ec; @@ -2400,34 +2503,6 @@ namespace vcpkg } } - std::unique_ptr Filesystem::take_exclusive_file_lock(const Path& lockfile, - MessageSink& status_sink, - LineInfo li) const - { - std::error_code ec; - auto sh = this->take_exclusive_file_lock(lockfile, status_sink, ec); - if (ec) - { - exit_filesystem_call_error(li, ec, __func__, {lockfile}); - } - - return sh; - } - - std::unique_ptr Filesystem::try_take_exclusive_file_lock(const Path& lockfile, - MessageSink& status_sink, - LineInfo li) const - { - std::error_code ec; - auto sh = this->try_take_exclusive_file_lock(lockfile, status_sink, ec); - if (ec) - { - exit_filesystem_call_error(li, ec, __func__, {lockfile}); - } - - return sh; - } - WriteFilePointer Filesystem::open_for_write(const Path& file_path, Append append, LineInfo li) const { std::error_code ec; @@ -4026,7 +4101,7 @@ namespace vcpkg stdfs::path native; ExclusiveFileLock(const Path& path, std::error_code& ec) : native(to_stdfs_path(path)) { ec.clear(); } - bool lock_attempt(std::error_code& ec) + Optional lock_attempt(DiagnosticContext& context) { Checks::check_exit(VCPKG_LINE_INFO, handle == INVALID_HANDLE_VALUE); handle = CreateFileW(native.c_str(), @@ -4038,19 +4113,22 @@ namespace vcpkg nullptr /* no template file */); if (handle != INVALID_HANDLE_VALUE) { - ec.clear(); return true; } const auto err = GetLastError(); if (err == ERROR_SHARING_VIOLATION) { - ec.clear(); return false; } - ec.assign(err, std::system_category()); - return false; + context.report(DiagnosticLine{DiagKind::Error, + from_stdfs_path(native), + msg::format(msgSystemApiErrorMessage, + msg::system_api = "CreateFileW", + msg::exit_code = err, + msg::error_msg = std::system_category().message(err))}); + return nullopt; } ~ExclusiveFileLock() override @@ -4063,29 +4141,34 @@ namespace vcpkg } #else // ^^^ _WIN32 / !_WIN32 vvv PosixFd fd; + Path p; bool locked = false; ExclusiveFileLock(const Path& path, std::error_code& ec) - : fd(path.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, ec) + : fd(path.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, ec), p(path) { } - bool lock_attempt(std::error_code& ec) + Optional lock_attempt(DiagnosticContext& context) { if (fd.flock(LOCK_EX | LOCK_NB) == 0) { - ec.clear(); locked = true; return true; } - if (errno == EWOULDBLOCK) + auto err = errno; + if (err == EWOULDBLOCK) { - ec.clear(); return false; } - ec.assign(errno, std::generic_category()); - return false; + context.report(DiagnosticLine{DiagKind::Error, + p, + msg::format(msgSystemApiErrorMessage, + msg::system_api = "flock", + msg::exit_code = err, + msg::error_msg = std::system_category().message(err))}); + return nullopt; }; ~ExclusiveFileLock() override @@ -4098,54 +4181,83 @@ namespace vcpkg #endif }; - virtual std::unique_ptr take_exclusive_file_lock(const Path& lockfile, - MessageSink& status_sink, - std::error_code& ec) const override + virtual std::unique_ptr take_exclusive_file_lock(DiagnosticContext& context, + const Path& lockfile) const override { + std::error_code ec; auto result = std::make_unique(lockfile, ec); - if (!ec && !result->lock_attempt(ec) && !ec) + if (ec) { - status_sink.println(msgWaitingToTakeFilesystemLock, msg::path = lockfile); - do + context.report_error(format_filesystem_call_error(ec, "take_exclusive_file_lock", {lockfile})); + result.reset(); + return result; + } + + for (;;) + { + auto maybe_attempt = result->lock_attempt(context); + if (const auto* attempt = maybe_attempt.get()) { + if (*attempt) + { + return result; + } + + context.statusln(msg::format(msgWaitingToTakeFilesystemLock, msg::path = lockfile)); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - } while (!result->lock_attempt(ec) && !ec); + } + else + { + result.reset(); + return result; + } } - - return std::move(result); } - virtual std::unique_ptr try_take_exclusive_file_lock(const Path& lockfile, - MessageSink& status_sink, - std::error_code& ec) const override + virtual Optional> try_take_exclusive_file_lock( + DiagnosticContext& context, const Path& lockfile) const override { + std::error_code ec; auto result = std::make_unique(lockfile, ec); - if (!ec && !result->lock_attempt(ec) && !ec) + if (ec) + { + context.report_error(format_filesystem_call_error(ec, "try_take_exclusive_file_lock", {lockfile})); + return nullopt; + } + + // waits, at most, a second and a half. + auto wait = std::chrono::milliseconds(200); + for (;;) { - if (Debug::g_debugging) + auto maybe_attempt = result->lock_attempt(context); + auto attempt = maybe_attempt.get(); + if (!attempt) { - status_sink.println(msg::format(msgWaitingToTakeFilesystemLock, msg::path = lockfile)); + return nullopt; } - // waits, at most, a second and a half. - for (auto wait = std::chrono::milliseconds(100);;) + if (*attempt) { - std::this_thread::sleep_for(wait); - if (result->lock_attempt(ec) || ec) - { - break; - } + return result; + } - wait *= 2; - if (wait >= std::chrono::milliseconds(1000)) - { - ec.assign(EBUSY, std::generic_category()); - break; - } + if (wait >= std::chrono::milliseconds(1000)) + { + context.report( + DiagnosticLine{DiagKind::Error, + lockfile, + msg::format(msgSystemApiErrorMessage, + msg::system_api = "try_take_exclusive_file_lock", + msg::exit_code = EBUSY, + msg::error_msg = std::generic_category().message(EBUSY))}); + result.reset(); + return result; } - } - return std::move(result); + context.statusln(msg::format(msgWaitingToTakeFilesystemLock, msg::path = lockfile)); + std::this_thread::sleep_for(wait); + wait *= 2; + } } virtual WriteFilePointer open_for_write(const Path& file_path, @@ -4159,6 +4271,269 @@ namespace vcpkg static constexpr RealFilesystem real_filesystem_instance; constexpr const Filesystem& real_filesystem = real_filesystem_instance; + static void assign_not_supported(std::error_code& ec) + { + ec.assign(static_cast(std::errc::not_supported), std::generic_category()); + } + + struct AlwaysFailingFilesystem final : Filesystem + { + ExpectedL> read_lines(const Path& file_path) const override + { + return format_filesystem_call_error( + std::make_error_code(std::errc::not_supported), "read_lines", {file_path}); + } + + std::uint64_t file_size(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return 0; + } + + std::string read_contents(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return std::string(); + } + + std::string best_effort_read_contents_if_shebang(const Path&) const override { return std::string(); } + + Path find_file_recursively_up(const Path&, const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return Path(); + } + + std::vector get_files_recursive(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return std::vector(); + } + + std::vector get_files_recursive_lexically_proximate(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return std::vector(); + } + + std::vector get_files_non_recursive(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return std::vector(); + } + + std::vector get_directories_recursive(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return std::vector(); + } + + std::vector get_directories_recursive_lexically_proximate(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return std::vector(); + } + + std::vector get_directories_non_recursive(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return std::vector(); + } + + std::vector get_regular_files_recursive(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return std::vector(); + } + + std::vector get_regular_files_recursive_lexically_proximate(const Path&, + std::error_code& ec) const override + { + assign_not_supported(ec); + return std::vector(); + } + + std::vector get_regular_files_non_recursive(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return std::vector(); + } + + bool is_directory(const Path&) const override { return false; } + bool is_regular_file(const Path&) const override { return false; } + bool is_empty(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return false; + } + + FileType status(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return FileType::unknown; + } + + FileType symlink_status(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return FileType::unknown; + } + + Path almost_canonical(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return Path(); + } + + Path current_path(std::error_code& ec) const override + { + assign_not_supported(ec); + return Path(); + } + + Path absolute(const Path& p, std::error_code& ec) const override + { + assign_not_supported(ec); + return p; + } + + std::vector find_from_PATH(View) const override { return std::vector{}; } + ReadFilePointer open_for_read(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return ReadFilePointer{}; + } + + void write_lines(const Path&, const std::vector&, std::error_code& ec) const override + { + assign_not_supported(ec); + } + + void write_contents(const Path&, StringView, std::error_code& ec) const override { assign_not_supported(ec); } + + void write_contents_and_dirs(const Path&, StringView, std::error_code& ec) const override + { + assign_not_supported(ec); + } + + void rename(const Path&, const Path&, std::error_code& ec) const override { assign_not_supported(ec); } + + bool remove(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return false; + } + + void remove_all(const Path& base, std::error_code& ec, Path& failure_point) const override + { + assign_not_supported(ec); + failure_point = base; + } + + bool create_directory(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return false; + } + + bool create_directories(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return false; + } + + Path create_or_get_temp_directory(std::error_code& ec) const override + { + assign_not_supported(ec); + return Path(); + } + + void create_symlink(const Path&, const Path&, std::error_code& ec) const override { assign_not_supported(ec); } + + void create_directory_symlink(const Path&, const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + } + + void create_hard_link(const Path&, const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + } + + void copy_regular_recursive(const Path&, const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + } + + bool copy_file(const Path&, const Path&, CopyOptions, std::error_code& ec) const override + { + assign_not_supported(ec); + return false; + } + + void copy_symlink(const Path&, const Path&, std::error_code& ec) const override { assign_not_supported(ec); } + + int64_t file_time_now() const override { return 0; } + + int64_t last_write_time(const Path&, std::error_code& ec) const override + { + assign_not_supported(ec); + return 0; + } + + bool last_write_time(DiagnosticContext& context, const Path&, int64_t) const override + { + context.report_system_error("last_write_time", +#if defined(_WIN32) + ERROR_NOT_SUPPORTED +#else + ENOTSUP +#endif + ); + + return false; + } + + void current_path(const Path&, std::error_code& ec) const override { assign_not_supported(ec); } + + std::unique_ptr take_exclusive_file_lock(DiagnosticContext& context, + const Path&) const override + { + context.report_system_error("take_exclusive_file_lock", +#if defined(_WIN32) + ERROR_NOT_SUPPORTED +#else + ENOTSUP +#endif + ); + + return nullptr; + } + + Optional> try_take_exclusive_file_lock(DiagnosticContext& context, + const Path&) const override + { + context.report_system_error("try_take_exclusive_file_lock", +#if defined(_WIN32) + ERROR_NOT_SUPPORTED +#else + ENOTSUP +#endif + ); + + return nullopt; + } + + WriteFilePointer open_for_write(const Path&, Append, std::error_code& ec) const override + { + assign_not_supported(ec); + return WriteFilePointer{}; + } + }; + + static constexpr AlwaysFailingFilesystem always_failing_filesystem_instance; + constexpr const Filesystem& always_failing_filesystem = always_failing_filesystem_instance; + constexpr StringLiteral FILESYSTEM_INVALID_CHARACTERS = R"(\/:*?"<>|)"; bool has_invalid_chars_for_filesystem(const std::string& s) diff --git a/src/vcpkg/base/json.cpp b/src/vcpkg/base/json.cpp index 657ce30a5d..1ef4fbb132 100644 --- a/src/vcpkg/base/json.cpp +++ b/src/vcpkg/base/json.cpp @@ -1084,6 +1084,31 @@ namespace vcpkg::Json return ParsedJson{std::move(val), parser.style()}; } + static Optional parse(DiagnosticContext& context, StringView json, StringView origin) + { + StatsTimer t(g_json_parsing_stats); + + json.remove_bom(); + + auto parser = Parser(json, origin, {1, 1}); + + auto val = parser.parse_value(); + + parser.skip_whitespace(); + if (!parser.at_eof()) + { + parser.add_error(msg::format(msgUnexpectedEOFExpectedChar)); + } + + if (parser.messages().any_errors()) + { + std::move(parser).messages().report(context); + return nullopt; + } + + return ParsedJson{std::move(val), parser.style()}; + } + JsonStyle style() const noexcept { return style_; } private: @@ -1170,6 +1195,11 @@ namespace vcpkg::Json ExpectedL parse(StringView json, StringView origin) { return Parser::parse(json, origin); } + Optional parse(DiagnosticContext& context, StringView text, StringView origin) + { + return Parser::parse(context, text, origin); + } + ExpectedL parse_object(StringView text, StringView origin) { return parse(text, origin).then([&](ParsedJson&& mabeValueIsh) -> ExpectedL { @@ -1179,7 +1209,22 @@ namespace vcpkg::Json return std::move(*as_object); } - return msg::format(msgJsonErrorMustBeAnObject, msg::path = origin); + return LocalizedString::from_raw( + DiagnosticLine{DiagKind::Error, origin, msg::format(msgExpectedAnObject)}.to_string()); + }); + } + + Optional parse_object(DiagnosticContext& context, StringView text, StringView origin) + { + return parse(context, text, origin).then([&](ParsedJson&& mabeValueIsh) -> Optional { + auto& asValue = mabeValueIsh.value; + if (auto as_object = asValue.maybe_object()) + { + return std::move(*as_object); + } + + context.report(DiagnosticLine{DiagKind::Error, origin, msg::format(msgExpectedAnObject)}); + return nullopt; }); } // } auto parse() diff --git a/src/vcpkg/base/parse.cpp b/src/vcpkg/base/parse.cpp index 3ea56506b8..bced711a9b 100644 --- a/src/vcpkg/base/parse.cpp +++ b/src/vcpkg/base/parse.cpp @@ -136,6 +136,26 @@ namespace vcpkg return LocalizedString::from_raw(std::move(combined_messages)); } + bool ParseMessages::report(DiagnosticContext& context) const& + { + for (const auto& line : m_lines) + { + context.report(line); + } + + return !any_errors(); + } + + bool ParseMessages::report(DiagnosticContext& context) && + { + for (auto&& line : m_lines) + { + context.report(std::move(line)); + } + + return !any_errors(); + } + ParserBase::ParserBase(StringView text, Optional origin, TextRowCol init_rowcol) : m_it(text.begin(), text.end()) , m_start_of_line(m_it) diff --git a/src/vcpkg/base/system.cpp b/src/vcpkg/base/system.cpp index acd759a1dc..2bf064f5d1 100644 --- a/src/vcpkg/base/system.cpp +++ b/src/vcpkg/base/system.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -336,7 +337,7 @@ namespace vcpkg return supported_architectures; } - Optional get_environment_variable(ZStringView varname) noexcept + Optional get_environment_variable(ZStringView varname) { #if defined(_WIN32) const auto w_varname = Strings::to_utf16(varname); @@ -411,7 +412,18 @@ namespace vcpkg return result; } - const ExpectedL& get_home_dir() noexcept + static const Path* get_cached_environment_variable(DiagnosticContext& context, const ExpectedL& cached) + { + if (auto p = cached.get()) + { + return p; + } + + context.report_error(cached.error()); + return nullptr; + } + + const ExpectedL& get_home_dir() { static ExpectedL s_home = []() -> ExpectedL { #ifdef _WIN32 @@ -441,8 +453,13 @@ namespace vcpkg #undef HOMEVAR } + const Path* get_home_dir(DiagnosticContext& context) + { + return get_cached_environment_variable(context, get_home_dir()); + } + #ifdef _WIN32 - const ExpectedL& get_appdata_local() noexcept + const ExpectedL& get_appdata_local() { static ExpectedL s_home = []() -> ExpectedL { auto maybe_home = get_environment_variable(EnvironmentVariableLocalAppData); @@ -481,6 +498,11 @@ namespace vcpkg return s_home; } + const Path* get_appdata_local(DiagnosticContext& context) + { + return get_cached_environment_variable(context, get_appdata_local()); + } + static ExpectedL get_windows_forced_environment_variable(StringLiteral environment_variable) { auto env = get_environment_variable(environment_variable); @@ -493,28 +515,43 @@ namespace vcpkg msg::env_var = format_environment_variable(environment_variable)); } - const ExpectedL& get_system_drive() noexcept + const ExpectedL& get_system_drive() { static const ExpectedL s_system_drive = get_windows_forced_environment_variable(EnvironmentVariableSystemDrive); return s_system_drive; } - const ExpectedL& get_system_root() noexcept + const Path* get_system_drive(DiagnosticContext& context) + { + return get_cached_environment_variable(context, get_system_drive()); + } + + const ExpectedL& get_system_root() { static const ExpectedL s_system_root = get_windows_forced_environment_variable(EnvironmentVariableSystemRoot); return s_system_root; } - const ExpectedL& get_system32() noexcept + const Path* get_system_root(DiagnosticContext& context) + { + return get_cached_environment_variable(context, get_system_root()); + } + + const ExpectedL& get_system32() { // This needs to be lowercase or msys-ish tools break. See https://github.com/microsoft/vcpkg-tool/pull/418/ static const ExpectedL s_system32 = get_system_root().map([](const Path& p) { return p / "system32"; }); return s_system32; } + + const Path* get_system32(DiagnosticContext& context) + { + return get_cached_environment_variable(context, get_system32()); + } #else - static const ExpectedL& get_xdg_cache_home() noexcept + static const ExpectedL& get_xdg_cache_home() { static ExpectedL s_home = []() -> ExpectedL { auto maybe_home = get_environment_variable("XDG_CACHE_HOME"); @@ -532,7 +569,7 @@ namespace vcpkg } #endif - const ExpectedL& get_platform_cache_root() noexcept + const ExpectedL& get_platform_cache_root() { static ExpectedL s_home = #if defined(_WIN32) @@ -544,13 +581,23 @@ namespace vcpkg return s_home; } - const ExpectedL& get_platform_cache_vcpkg() noexcept + const Path* get_platform_cache_root(DiagnosticContext& context) + { + return get_cached_environment_variable(context, get_platform_cache_root()); + } + + const ExpectedL& get_platform_cache_vcpkg() { static ExpectedL s_vcpkg = get_platform_cache_root().map([](const Path& p) { return p / "vcpkg"; }); return s_vcpkg; } - const ExpectedL& get_user_configuration_home() noexcept + const Path* get_platform_cache_vcpkg(DiagnosticContext& context) + { + return get_cached_environment_variable(context, get_platform_cache_vcpkg()); + } + + const ExpectedL& get_user_configuration_home() { #if defined(_WIN32) static const ExpectedL result = @@ -561,6 +608,11 @@ namespace vcpkg return result; } + const Path* get_user_configuration_home(DiagnosticContext& context) + { + return get_cached_environment_variable(context, get_user_configuration_home()); + } + #if defined(_WIN32) std::wstring get_username() { diff --git a/src/vcpkg/base/system.process.cpp b/src/vcpkg/base/system.process.cpp index a248372ef3..0b15a537b5 100644 --- a/src/vcpkg/base/system.process.cpp +++ b/src/vcpkg/base/system.process.cpp @@ -539,8 +539,18 @@ namespace vcpkg Environment get_modified_clean_environment(const std::unordered_map& extra_env, StringView prepend_to_path) { - const std::string& system_root_env = get_system_root().value_or_exit(VCPKG_LINE_INFO).native(); - const std::string& system32_env = get_system32().value_or_exit(VCPKG_LINE_INFO).native(); + const Path* system_root_env = get_system_root(console_diagnostic_context); + if (!system_root_env) + { + Checks::exit_fail(VCPKG_LINE_INFO); + } + + const Path* system32_env = get_system32(console_diagnostic_context); + if (!system32_env) + { + Checks::exit_fail(VCPKG_LINE_INFO); + } + std::string new_path; if (!prepend_to_path.empty()) { @@ -552,15 +562,15 @@ namespace vcpkg } Strings::append(new_path, - system32_env, + *system32_env, ';', - system_root_env, + *system_root_env, ';', - system32_env, + *system32_env, "\\Wbem;", - system32_env, + *system32_env, "\\WindowsPowerShell\\v1.0\\;", - system32_env, + *system32_env, "\\OpenSSH\\"); std::vector env_strings = { @@ -1629,7 +1639,7 @@ namespace std::replace(buf, buf + bytes_read, '\0', '?'); if (settings.echo_in_debug == EchoInDebug::Show && Debug::g_debugging) { - msg::write_unlocalized_text_to_stdout(Color::none, StringView{buf, bytes_read}); + context.statusln(LocalizedString::from_raw(StringView{buf, bytes_read})); } data_cb(StringView{buf, bytes_read}); @@ -1644,7 +1654,7 @@ namespace std::replace(encoded.begin(), encoded.end(), '\0', '?'); if (settings.echo_in_debug == EchoInDebug::Show && Debug::g_debugging) { - msg::write_unlocalized_text_to_stdout(Color::none, StringView{encoded}); + context.statusln(LocalizedString::from_raw(encoded)); } data_cb(StringView{encoded}); @@ -1654,10 +1664,8 @@ namespace raw_cb = [&](char* buf, size_t bytes_read) { if (settings.echo_in_debug == EchoInDebug::Show && Debug::g_debugging) { - msg::write_unlocalized_text_to_stdout(Color::none, - Strings::replace_all(StringView{buf, bytes_read}, - StringLiteral{"\0"}, - StringLiteral{"\\0"})); + context.statusln(LocalizedString::from_raw(Strings::replace_all( + StringView{buf, bytes_read}, StringLiteral{"\0"}, StringLiteral{"\\0"}))); } data_cb(StringView{buf, bytes_read}); @@ -1829,7 +1837,7 @@ namespace data_cb(this_read_data); if (settings.echo_in_debug == EchoInDebug::Show && Debug::g_debugging) { - msg::write_unlocalized_text(Color::none, this_read_data); + context.statusln(LocalizedString::from_raw(this_read_data)); } } } @@ -1865,15 +1873,14 @@ namespace std::replace(buf, buf + read_amount, '\0', '?'); if (settings.echo_in_debug == EchoInDebug::Show && Debug::g_debugging) { - msg::write_unlocalized_text(Color::none, this_read_data); + context.statusln(LocalizedString::from_raw(this_read_data)); } break; case Encoding::Utf8WithNulls: if (settings.echo_in_debug == EchoInDebug::Show && Debug::g_debugging) { - msg::write_unlocalized_text_to_stdout( - Color::none, - Strings::replace_all(this_read_data, StringLiteral{"\0"}, StringLiteral{"\\0"})); + context.statusln(LocalizedString::from_raw( + Strings::replace_all(this_read_data, StringLiteral{"\0"}, StringLiteral{"\\0"}))); } break; diff --git a/src/vcpkg/binarycaching.cpp b/src/vcpkg/binarycaching.cpp index d70b3f00ee..39c4af661c 100644 --- a/src/vcpkg/binarycaching.cpp +++ b/src/vcpkg/binarycaching.cpp @@ -247,9 +247,11 @@ namespace struct FilesWriteBinaryProvider : IWriteBinaryProvider { - FilesWriteBinaryProvider(const Filesystem& fs, std::vector&& dirs) : m_fs(fs), m_dirs(std::move(dirs)) { } + FilesWriteBinaryProvider(std::vector&& dirs) : m_dirs(std::move(dirs)) { } - size_t push_success(const BinaryPackageWriteInfo& request, MessageSink& msg_sink) override + size_t push_success(DiagnosticContext& context, + const Filesystem& fs, + const BinaryPackageWriteInfo& request) override { const auto& zip_path = request.zip_path.value_or_exit(VCPKG_LINE_INFO); size_t count_stored = 0; @@ -259,13 +261,13 @@ namespace for (const auto& archives_root_dir : m_dirs) { const auto archive_parent_path = archives_root_dir / files_archive_parent_path(request.package_abi); - m_fs.create_directories(archive_parent_path, IgnoreErrors{}); + fs.create_directories(archive_parent_path, IgnoreErrors{}); const auto archive_path = archive_parent_path / (request.package_abi + ".zip"); const auto archive_temp_path = Path(fmt::format("{}.{}", archive_path.native(), get_process_id())); std::error_code ec; if (can_attempt_rename) { - m_fs.rename_or_delete(zip_path, archive_path, ec); + fs.rename_or_delete(zip_path, archive_path, ec); } if (!can_attempt_rename || (ec && ec == std::make_error_condition(std::errc::cross_device_link))) @@ -275,19 +277,19 @@ namespace // into place // First copy to temporary location to avoid race between different vcpkg instances trying to upload // the same archive, e.g. if 2 machines try to upload to a shared binary cache. - m_fs.copy_file(zip_path, archive_temp_path, CopyOptions::overwrite_existing, ec); + fs.copy_file(zip_path, archive_temp_path, CopyOptions::overwrite_existing, ec); if (!ec) { - m_fs.rename_or_delete(archive_temp_path, archive_path, ec); + fs.rename_or_delete(archive_temp_path, archive_path, ec); } } if (ec) { - msg_sink.println(Color::warning, - msg::format(msgFailedToStoreBinaryCache, msg::path = archive_path) - .append_raw('\n') - .append_raw(ec.message())); + context.report(DiagnosticLine{DiagKind::Warning, + msg::format(msgFailedToStoreBinaryCache, msg::path = archive_path) + .append_raw('\n') + .append_raw(ec.message())}); } else { @@ -301,7 +303,6 @@ namespace bool needs_zip_file() const override { return true; } private: - const Filesystem& m_fs; std::vector m_dirs; }; @@ -325,7 +326,7 @@ namespace // - IReadBinaryProvider::precheck() struct ZipReadBinaryProvider : IReadBinaryProvider { - ZipReadBinaryProvider(ZipTool zip, const Filesystem& fs) : m_zip(std::move(zip)), m_fs(fs) { } + ZipReadBinaryProvider(const ZipTool& zip) : m_zip(zip) { } struct UnzipJob { @@ -337,12 +338,14 @@ namespace bool success = false; }; - void fetch(View actions, Span out_status) const override + void fetch(DiagnosticContext& context, + const Filesystem& fs, + View actions, + Span out_status) const override { const ElapsedTimer timer; std::vector> zip_paths(actions.size(), nullopt); - acquire_zips(actions, zip_paths); - + acquire_zips(context, fs, actions, zip_paths); std::vector jobs; jobs.reserve(actions.size()); for (size_t i = 0; i < actions.size(); ++i) @@ -351,7 +354,7 @@ namespace { jobs.push_back({&actions[i]->package_dir.value_or_exit(VCPKG_LINE_INFO), zip_resource, - m_fs.file_size(zip_resource->path, IgnoreErrors{}), + fs.file_size(zip_resource->path, IgnoreErrors{}), i}); } } @@ -359,9 +362,9 @@ namespace std::sort( jobs.begin(), jobs.end(), [](const UnzipJob& l, const UnzipJob& r) { return l.zip_size > r.zip_size; }); - parallel_for_each(jobs, [this, &out_status](UnzipJob& job) { + parallel_for_each(jobs, [this, &fs, &out_status](UnzipJob& job) { WarningDiagnosticContext wdc{job.fbdc}; - if (clean_prepare_dir(wdc, m_fs, *job.package_dir)) + if (clean_prepare_dir(wdc, fs, *job.package_dir)) { auto cmd = m_zip.decompress_zip_archive_cmd(*job.package_dir, job.zip_resource->path); auto maybe_output = cmd_execute_and_capture_output(wdc, cmd); @@ -369,7 +372,7 @@ namespace #ifdef _WIN32 // On windows the ziptool does restore file times, we don't want that because this breaks file // time based change detection. - && directory_last_write_time(wdc, m_fs, *job.package_dir) + && directory_last_write_time(wdc, fs, *job.package_dir) #endif // ^^^ _WIN32 ) { @@ -385,7 +388,7 @@ namespace if (job.zip_resource->to_remove == RemoveWhen::always) { - m_fs.remove(job.zip_resource->path, IgnoreErrors{}); + fs.remove(job.zip_resource->path, IgnoreErrors{}); } }); @@ -406,36 +409,41 @@ namespace // the downloaded location. // // Leaving an Optional disengaged indicates that the cache does not contain the requested zip. - virtual void acquire_zips(View actions, + // + // Note that as this API can't fail, only warnings or lower will be emitted to `context`. + virtual void acquire_zips(DiagnosticContext& context, + const Filesystem& fs, + View actions, Span> out_zips) const = 0; protected: ZipTool m_zip; - const Filesystem& m_fs; }; struct FilesReadBinaryProvider : ZipReadBinaryProvider { - FilesReadBinaryProvider(ZipTool zip, const Filesystem& fs, Path&& dir) - : ZipReadBinaryProvider(std::move(zip), fs), m_dir(std::move(dir)) - { - } + FilesReadBinaryProvider(const ZipTool& zip, Path&& dir) : ZipReadBinaryProvider(zip), m_dir(std::move(dir)) { } - void acquire_zips(View actions, + void acquire_zips(DiagnosticContext&, + const Filesystem& fs, + View actions, Span> out_zip_paths) const override { for (size_t i = 0; i < actions.size(); ++i) { const auto& abi_tag = actions[i]->package_abi_or_exit(VCPKG_LINE_INFO); auto archive_path = m_dir / files_archive_subpath(abi_tag); - if (m_fs.exists(archive_path, IgnoreErrors{})) + if (fs.exists(archive_path, IgnoreErrors{})) { out_zip_paths[i].emplace(std::move(archive_path), RemoveWhen::nothing); } } } - void precheck(View actions, Span cache_status) const override + void precheck(DiagnosticContext&, + const Filesystem& fs, + View actions, + Span cache_status) const override { for (size_t idx = 0; idx < actions.size(); ++idx) { @@ -443,7 +451,7 @@ namespace const auto& abi_tag = action.package_abi_or_exit(VCPKG_LINE_INFO); bool any_available = false; - if (m_fs.exists(m_dir / files_archive_subpath(abi_tag), IgnoreErrors{})) + if (fs.exists(m_dir / files_archive_subpath(abi_tag), IgnoreErrors{})) { any_available = true; } @@ -471,7 +479,9 @@ namespace { } - size_t push_success(const BinaryPackageWriteInfo& request, MessageSink& msg_sink) override + size_t push_success(DiagnosticContext& context, + const Filesystem&, + const BinaryPackageWriteInfo& request) override { if (!request.zip_path) return 0; const auto& zip_path = *request.zip_path.get(); @@ -479,8 +489,7 @@ namespace for (auto&& templ : m_urls) { auto url = templ.instantiate_variables(request); - PrintingDiagnosticContext pdc{msg_sink}; - WarningDiagnosticContext wdc{pdc}; + WarningDiagnosticContext wdc{context}; auto maybe_success = store_to_asset_cache(wdc, url, SanitizedUrl{url, m_secrets}, "PUT", templ.headers, zip_path); if (maybe_success) @@ -502,18 +511,19 @@ namespace struct HttpGetBinaryProvider : ZipReadBinaryProvider { HttpGetBinaryProvider(ZipTool zip, - const Filesystem& fs, const Path& buildtrees, UrlTemplate&& url_template, const std::vector& secrets) - : ZipReadBinaryProvider(std::move(zip), fs) + : ZipReadBinaryProvider(std::move(zip)) , m_buildtrees(buildtrees) , m_url_template(std::move(url_template)) , m_secrets(secrets) { } - void acquire_zips(View actions, + void acquire_zips(DiagnosticContext& context, + const Filesystem&, + View actions, Span> out_zip_paths) const override { std::vector> url_paths; @@ -525,7 +535,7 @@ namespace make_temp_archive_path(m_buildtrees, read_info.spec, read_info.package_abi)); } - WarningDiagnosticContext wdc{console_diagnostic_context}; + WarningDiagnosticContext wdc{context}; auto codes = download_files_no_cache(wdc, url_paths, m_url_template.headers, m_secrets); for (size_t i = 0; i < codes.size(); ++i) { @@ -536,7 +546,10 @@ namespace } } - void precheck(View actions, Span out_status) const override + void precheck(DiagnosticContext& context, + const Filesystem&, + View actions, + Span out_status) const override { std::vector urls; for (size_t idx = 0; idx < actions.size(); ++idx) @@ -544,7 +557,7 @@ namespace urls.push_back(m_url_template.instantiate_variables(BinaryPackageReadInfo{*actions[idx]})); } - WarningDiagnosticContext wdc{console_diagnostic_context}; + WarningDiagnosticContext wdc{context}; auto codes = url_heads(wdc, urls, {}, m_secrets); for (size_t i = 0; i < codes.size(); ++i) { @@ -570,21 +583,21 @@ namespace struct AzureBlobPutBinaryProvider : IWriteBinaryProvider { - AzureBlobPutBinaryProvider(const Filesystem& fs, - std::vector&& urls, - const std::vector& secrets) - : m_fs(fs), m_urls(std::move(urls)), m_secrets(secrets) + AzureBlobPutBinaryProvider(std::vector&& urls, const std::vector& secrets) + : m_urls(std::move(urls)), m_secrets(secrets) { } - size_t push_success(const BinaryPackageWriteInfo& request, MessageSink& msg_sink) override + size_t push_success(DiagnosticContext& context, + const Filesystem& fs, + const BinaryPackageWriteInfo& request) override { if (!request.zip_path) return 0; const auto& zip_path = *request.zip_path.get(); size_t count_stored = 0; - const auto file_size = m_fs.file_size(zip_path, VCPKG_LINE_INFO); + const auto file_size = fs.file_size(zip_path, VCPKG_LINE_INFO); if (file_size == 0) return count_stored; // cf. @@ -592,8 +605,7 @@ namespace constexpr size_t max_single_write = 5000000000; bool use_azcopy = file_size > max_single_write; - PrintingDiagnosticContext pdc{msg_sink}; - WarningDiagnosticContext wdc{pdc}; + WarningDiagnosticContext wdc{context}; for (auto&& templ : m_urls) { @@ -614,7 +626,6 @@ namespace bool needs_zip_file() const override { return true; } private: - const Filesystem& m_fs; std::vector m_urls; std::vector m_secrets; }; @@ -628,17 +639,44 @@ namespace NuGetSource nuget_sources_arg(View sources) { return {"-Source", Strings::join(";", sources)}; } NuGetSource nuget_configfile_arg(const Path& config_path) { return {"-ConfigFile", config_path.native()}; } + struct NuGetToolTools + { + Path nuget_tool; +#ifndef _WIN32 + Path mono_tool; +#endif + }; + + Optional get_nuget_tool_tools(DiagnosticContext& context, + const Filesystem& fs, + const ToolCache& cache) + { + if (auto nuget_tool = cache.get_tool_path(context, fs, Tools::NUGET)) + { +#ifdef _WIN32 + return NuGetToolTools{*nuget_tool}; +#else + if (auto mono_tool = cache.get_tool_path(context, fs, Tools::MONO)) + { + return NuGetToolTools{*nuget_tool, *mono_tool}; + } +#endif + } + + return nullopt; + } + struct NuGetTool { - NuGetTool(const ToolCache& cache, MessageSink& sink, const BinaryConfigParserState& shared) + NuGetTool(NuGetToolTools&& nuget_tools, const BinaryConfigParserState& shared) : m_timeout(shared.nugettimeout) , m_interactive(shared.nuget_interactive) , m_use_nuget_cache(shared.use_nuget_cache) { #ifndef _WIN32 - m_cmd.string_arg(cache.get_tool_path(Tools::MONO, sink)); + m_cmd.string_arg(std::move(nuget_tools.mono_tool)); #endif - m_cmd.string_arg(cache.get_tool_path(Tools::NUGET, sink)); + m_cmd.string_arg(std::move(nuget_tools.nuget_tool)); } bool push(DiagnosticContext& context, const Path& nupkg_path, const NuGetSource& src) const @@ -800,20 +838,14 @@ namespace struct NugetBaseBinaryProvider { - NugetBaseBinaryProvider(const Filesystem& fs, - const NuGetTool& tool, + NugetBaseBinaryProvider(const NuGetTool& tool, const Path& packages, const Path& buildtrees, StringView nuget_prefix) - : m_fs(fs) - , m_cmd(tool) - , m_packages(packages) - , m_buildtrees(buildtrees) - , m_nuget_prefix(nuget_prefix.to_string()) + : m_cmd(tool), m_packages(packages), m_buildtrees(buildtrees), m_nuget_prefix(nuget_prefix.to_string()) { } - const Filesystem& m_fs; NuGetTool m_cmd; Path m_packages; Path m_buildtrees; @@ -849,7 +881,12 @@ namespace } // Prechecking is too expensive with NuGet, so it is not implemented - void precheck(View, Span) const override { } + void precheck(DiagnosticContext&, + const Filesystem&, + View, + Span) const override + { + } LocalizedString restored_message(size_t count, std::chrono::high_resolution_clock::duration elapsed) const override @@ -857,30 +894,42 @@ namespace return msg::format(msgRestoredPackagesFromNuGet, msg::count = count, msg::elapsed = ElapsedTime(elapsed)); } - void fetch(View actions, Span out_status) const override + void fetch(DiagnosticContext& context, + const Filesystem& fs, + View actions, + Span out_status) const override { auto packages_config = m_buildtrees / "packages.config"; auto refs = Util::fmap(actions, [this](const InstallPlanAction* p) { return make_nugetref(*p, m_nuget_prefix); }); - m_fs.write_contents(packages_config, generate_packages_config(refs), VCPKG_LINE_INFO); - WarningDiagnosticContext wdc{console_diagnostic_context}; - m_cmd.install(wdc, packages_config, m_packages, m_src); + WarningDiagnosticContext wdc{context}; + if (!fs.write_contents(wdc, packages_config, generate_packages_config(refs))) + { + return; + } + + (void)m_cmd.install(wdc, packages_config, m_packages, m_src); for (size_t i = 0; i < actions.size(); ++i) { // nuget.exe provides the nupkg file and the unpacked folder const auto nupkg_path = m_packages / refs[i].id / refs[i].id + ".nupkg"; - if (m_fs.exists(nupkg_path, IgnoreErrors{})) + if (fs.exists(nupkg_path, IgnoreErrors{})) { - m_fs.remove(nupkg_path, VCPKG_LINE_INFO); + (void)fs.remove(wdc, nupkg_path); const auto nuget_dir = actions[i]->spec.dir(); - if (nuget_dir != refs[i].id) + if (nuget_dir == refs[i].id) + { + out_status[i] = RestoreResult::restored; + } + else { const auto path_from = m_packages / refs[i].id; const auto path_to = m_packages / nuget_dir; - m_fs.rename(path_from, path_to, VCPKG_LINE_INFO); + if (fs.rename(wdc, path_from, path_to)) + { + out_status[i] = RestoreResult::restored; + } } - - out_status[i] = RestoreResult::restored; } } } @@ -901,24 +950,25 @@ namespace bool needs_nuspec_data() const override { return true; } bool needs_zip_file() const override { return false; } - size_t push_success(const BinaryPackageWriteInfo& request, MessageSink& msg_sink) override + size_t push_success(DiagnosticContext& context, + const Filesystem& fs, + const BinaryPackageWriteInfo& request) override { - PrintingDiagnosticContext pdc{msg_sink}; - WarningDiagnosticContext wdc{pdc}; auto& spec = request.spec; auto nuspec_path = m_buildtrees / spec.name() / spec.triplet().canonical_name() + ".nuspec"; auto& nuspec_contents = request.nuspec.value_or_exit(VCPKG_LINE_INFO); std::error_code ec; - m_fs.write_contents(nuspec_path, nuspec_contents, ec); + fs.write_contents(nuspec_path, nuspec_contents, ec); if (ec) { - wdc.report_error(format_filesystem_call_error(ec, "write_contents", {nuspec_path, nuspec_contents})); - wdc.report(DiagnosticLine{DiagKind::Note, msg::format(msgWhilePackingNuGetPackage)}); + context.report_error( + format_filesystem_call_error(ec, "write_contents", {nuspec_path, nuspec_contents})); + context.report(DiagnosticLine{DiagKind::Note, msg::format(msgWhilePackingNuGetPackage)}); return 0; } - auto pack_result = m_cmd.pack(wdc, nuspec_path, m_buildtrees); - m_fs.remove(nuspec_path, IgnoreErrors{}); + auto pack_result = m_cmd.pack(context, nuspec_path, m_buildtrees); + fs.remove(nuspec_path, IgnoreErrors{}); if (!pack_result) { return 0; @@ -928,72 +978,47 @@ namespace auto nupkg_path = m_buildtrees / make_feedref(request, m_nuget_prefix).nupkg_filename(); for (auto&& write_src : m_sources) { - wdc.statusln(msg::format(msgUploadingBinariesToVendor, - msg::spec = request.display_name, - msg::vendor = "NuGet", - msg::path = write_src)); - count_stored += m_cmd.push(wdc, nupkg_path, nuget_sources_arg({&write_src, 1})); + context.statusln(msg::format(msgUploadingBinariesToVendor, + msg::spec = request.display_name, + msg::vendor = "NuGet", + msg::path = write_src)); + count_stored += m_cmd.push(context, nupkg_path, nuget_sources_arg({&write_src, 1})); } for (auto&& write_cfg : m_configs) { - wdc.statusln(msg::format(msgUploadingBinariesToVendor, - msg::spec = spec, - msg::vendor = "NuGet config", - msg::path = write_cfg)); - count_stored += m_cmd.push(wdc, nupkg_path, nuget_configfile_arg(write_cfg)); + context.statusln(msg::format(msgUploadingBinariesToVendor, + msg::spec = spec, + msg::vendor = "NuGet config", + msg::path = write_cfg)); + count_stored += m_cmd.push(context, nupkg_path, nuget_configfile_arg(write_cfg)); } - m_fs.remove(nupkg_path, IgnoreErrors{}); + fs.remove(nupkg_path, IgnoreErrors{}); return count_stored; } }; - template - static ExpectedL flatten_generic(const ExpectedL& maybe_exit, - StringView tool_name, - ResultOnSuccessType result_on_success) - { - if (auto exit = maybe_exit.get()) - { - if (exit->exit_code == 0) - { - return {result_on_success}; - } - - return {msg::format_error( - msgProgramReturnedNonzeroExitCode, msg::tool_name = tool_name, msg::exit_code = exit->exit_code) - .append_raw('\n') - .append_raw(exit->output)}; - } - - return {msg::format_error(msgLaunchingProgramFailed, msg::tool_name = tool_name) - .append_raw(' ') - .append_raw(maybe_exit.error().to_string())}; - } - struct IObjectStorageTool { virtual ~IObjectStorageTool() = default; virtual LocalizedString restored_message(size_t count, std::chrono::high_resolution_clock::duration elapsed) const = 0; - virtual ExpectedL stat(StringView url) const = 0; - virtual ExpectedL download_file(StringView object, const Path& archive) const = 0; - virtual ExpectedL upload_file(StringView object, const Path& archive) const = 0; + virtual Optional stat(DiagnosticContext& context, StringView url) const = 0; + virtual Optional download_file(DiagnosticContext& context, + StringView object, + const Path& archive) const = 0; + virtual bool upload_file(DiagnosticContext& context, StringView object, const Path& archive) const = 0; }; struct ObjectStorageProvider : ZipReadBinaryProvider { - ObjectStorageProvider(ZipTool zip, - const Filesystem& fs, + ObjectStorageProvider(const ZipTool& zip, const Path& buildtrees, std::string&& prefix, const std::shared_ptr& tool) - : ZipReadBinaryProvider(std::move(zip), fs) - , m_buildtrees(buildtrees) - , m_prefix(std::move(prefix)) - , m_tool(tool) + : ZipReadBinaryProvider(zip), m_buildtrees(buildtrees), m_prefix(std::move(prefix)), m_tool(tool) { } @@ -1002,7 +1027,9 @@ namespace return Strings::concat(prefix, abi, ".zip"); } - void acquire_zips(View actions, + void acquire_zips(DiagnosticContext& context, + const Filesystem&, + View actions, Span> out_zip_paths) const override { for (size_t idx = 0; idx < actions.size(); ++idx) @@ -1010,7 +1037,8 @@ namespace auto&& action = *actions[idx]; const auto& abi = action.package_abi_or_exit(VCPKG_LINE_INFO); auto tmp = make_temp_archive_path(m_buildtrees, action.spec, abi); - auto res = m_tool->download_file(make_object_path(m_prefix, abi), tmp); + WarningDiagnosticContext wdc{context}; + auto res = m_tool->download_file(wdc, make_object_path(m_prefix, abi), tmp); if (auto cache_result = res.get()) { if (*cache_result == RestoreResult::restored) @@ -1018,20 +1046,20 @@ namespace out_zip_paths[idx].emplace(std::move(tmp), RemoveWhen::always); } } - else - { - msg::println_warning(res.error()); - } } } - void precheck(View actions, Span cache_status) const override + void precheck(DiagnosticContext& context, + const Filesystem&, + View actions, + Span cache_status) const override { for (size_t idx = 0; idx < actions.size(); ++idx) { auto&& action = *actions[idx]; const auto& abi = action.package_abi_or_exit(VCPKG_LINE_INFO); - auto maybe_res = m_tool->stat(make_object_path(m_prefix, abi)); + WarningDiagnosticContext wdc{context}; + auto maybe_res = m_tool->stat(wdc, make_object_path(m_prefix, abi)); if (auto res = maybe_res.get()) { cache_status[idx] = *res; @@ -1065,23 +1093,20 @@ namespace return Strings::concat(prefix, abi, ".zip"); } - size_t push_success(const BinaryPackageWriteInfo& request, MessageSink& msg_sink) override + size_t push_success(DiagnosticContext& context, + const Filesystem&, + const BinaryPackageWriteInfo& request) override { - if (!request.zip_path) return 0; - const auto& zip_path = *request.zip_path.get(); size_t upload_count = 0; - for (const auto& prefix : m_prefixes) + if (auto zip_path = request.zip_path.get()) { - auto res = m_tool->upload_file(make_object_path(prefix, request.package_abi), zip_path); - if (res) + WarningDiagnosticContext wdc{context}; + for (const auto& prefix : m_prefixes) { - ++upload_count; - } - else - { - msg_sink.println(warning_prefix().append(std::move(res).error())); + upload_count += m_tool->upload_file(wdc, make_object_path(prefix, request.package_abi), *zip_path); } } + return upload_count; } @@ -1094,12 +1119,8 @@ namespace struct AzCopyStorageProvider : ZipReadBinaryProvider { - AzCopyStorageProvider( - ZipTool zip, const Filesystem& fs, const Path& buildtrees, AzCopyUrl&& az_url, const Path& tool) - : ZipReadBinaryProvider(std::move(zip), fs) - , m_buildtrees(buildtrees) - , m_url(std::move(az_url)) - , m_tool(tool) + AzCopyStorageProvider(const ZipTool& zip, const Path& buildtrees, AzCopyUrl&& az_url, const Path& tool) + : ZipReadBinaryProvider(zip), m_buildtrees(buildtrees), m_url(std::move(az_url)), m_tool(tool) { } @@ -1114,53 +1135,47 @@ namespace 1); // the separator length is 1 for ';' } - std::vector azcopy_list() const + Optional> azcopy_list(DiagnosticContext& context) const { - auto maybe_output = cmd_execute_and_capture_output(Command{m_tool} - .string_arg("list") - .string_arg("--output-level") - .string_arg("ESSENTIAL") - .string_arg(m_url.make_container_path())); - - auto output = maybe_output.get(); - if (!output) - { - msg::println_warning(maybe_output.error()); - return {}; - } - - if (output->exit_code != 0) - { - msg::println_warning(LocalizedString::from_raw(output->output)); - return {}; - } - std::vector abis; - for (const auto& line : Strings::split(output->output, '\n')) - { - if (line.empty()) continue; - // `azcopy list` output uses format `; Content Length: `, we only need the filename - auto first_part_end = std::find(line.begin(), line.end(), ';'); - if (first_part_end != line.end()) - { - std::string abifile{line.begin(), first_part_end}; - - // Check file names with the format `.zip` - if (abifile.size() == ABI_LENGTH + 4 && - std::all_of(abifile.begin(), abifile.begin() + ABI_LENGTH, ParserBase::is_hex_digit) && - abifile.substr(ABI_LENGTH) == ".zip") + auto cmd = Command{m_tool} + .string_arg("list") + .string_arg("--output-level") + .string_arg("ESSENTIAL") + .string_arg(m_url.make_container_path()); + auto maybe_code_and_output = cmd_execute_and_capture_output(context, cmd); + if (auto output = check_zero_exit_code(context, cmd, maybe_code_and_output)) + { + for (const auto& line : Strings::split(*output, '\n')) + { + if (line.empty()) continue; + // `azcopy list` output uses format `; Content Length: `, we only need the filename + auto first_part_end = std::find(line.begin(), line.end(), ';'); + if (first_part_end != line.end()) { - // remove ".zip" extension - abis.emplace_back(abifile.substr(0, abifile.size() - 4)); + std::string abifile{line.begin(), first_part_end}; + + // Check file names with the format `.zip` + if (abifile.size() == ABI_LENGTH + 4 && + std::all_of(abifile.begin(), abifile.begin() + ABI_LENGTH, ParserBase::is_hex_digit) && + abifile.substr(ABI_LENGTH) == ".zip") + { + // remove ".zip" extension + abis.emplace_back(abifile.substr(0, abifile.size() - 4)); + } } } } + return abis; } - void acquire_zips(View actions, + void acquire_zips(DiagnosticContext& context, + const Filesystem& fs, + View actions, Span> out_zip_paths) const override { + WarningDiagnosticContext wdc{context}; std::vector abis; std::map abi_index_map; for (size_t idx = 0; idx < actions.size(); ++idx) @@ -1188,46 +1203,52 @@ namespace base_cmd.command_line().size() + 4; // for space + surrounding quotes + terminator for (auto&& batch : batch_azcopy_args(abis, reserved_len)) { - auto maybe_output = cmd_execute_and_capture_output(Command{base_cmd}.string_arg( - Strings::join(";", Util::fmap(batch, [](const auto& abi) { return abi + ".zip"; })))); - // We don't return on a failure because the command may have - // only failed to restore some of the requested packages. - if (!maybe_output.has_value()) - { - msg::println_warning(maybe_output.error()); - } + auto cmd = Command{base_cmd}.string_arg( + Strings::join(";", Util::fmap(batch, [](const auto& abi) { return abi + ".zip"; }))); + // note that we don't check for zero exit code because azcopy returns nonzero exit codes + // when any individual download fails, as we expect for cache misses + (void)cmd_execute_and_capture_output(wdc, cmd); } const auto& container_url = m_url.url; const auto last_slash = std::find(container_url.rbegin(), container_url.rend(), '/'); const auto container_name = std::string{last_slash.base(), container_url.end()}; - for (auto&& file : m_fs.get_files_non_recursive(tmp_downloads_location / container_name, VCPKG_LINE_INFO)) + auto maybe_files = fs.try_get_files_non_recursive(wdc, tmp_downloads_location / container_name); + if (auto files = maybe_files.get()) { - auto filename = file.stem().to_string(); - auto it = abi_index_map.find(filename); - if (it != abi_index_map.end()) + for (auto&& file : *files) { - out_zip_paths[it->second].emplace(std::move(file), RemoveWhen::always); + auto filename = file.stem().to_string(); + auto it = abi_index_map.find(filename); + if (it != abi_index_map.end()) + { + out_zip_paths[it->second].emplace(std::move(file), RemoveWhen::always); + } } } } - void precheck(View actions, Span cache_status) const override + void precheck(DiagnosticContext& context, + const Filesystem&, + View actions, + Span cache_status) const override { - auto abis = azcopy_list(); - if (abis.empty()) + WarningDiagnosticContext wdc{context}; + auto maybe_abis = azcopy_list(wdc); + if (auto abis = maybe_abis.get()) { - // If the command failed, we assume that the cache is unavailable. - std::fill(cache_status.begin(), cache_status.end(), CacheAvailability::unavailable); - return; + for (size_t idx = 0; idx < actions.size(); ++idx) + { + auto&& action = *actions[idx]; + const auto& abi = action.package_abi_or_exit(VCPKG_LINE_INFO); + cache_status[idx] = + Util::contains(*abis, abi) ? CacheAvailability::available : CacheAvailability::unavailable; + } } - - for (size_t idx = 0; idx < actions.size(); ++idx) + else { - auto&& action = *actions[idx]; - const auto& abi = action.package_abi_or_exit(VCPKG_LINE_INFO); - cache_status[idx] = - Util::contains(abis, abi) ? CacheAvailability::available : CacheAvailability::unavailable; + // If the command failed, we assume that the cache is unavailable. + std::fill(cache_status.begin(), cache_status.end(), CacheAvailability::unavailable); } } @@ -1249,36 +1270,33 @@ namespace { } - vcpkg::ExpectedL upload_file(StringView url, const Path& archive) const + bool upload_file(DiagnosticContext& context, StringView url, const Path& archive) const { - auto upload_cmd = Command{m_tool} - .string_arg("copy") - .string_arg("--from-to") - .string_arg("LocalBlob") - .string_arg("--overwrite") - .string_arg("true") - .string_arg(archive) - .string_arg(url); + auto cmd = Command{m_tool} + .string_arg("copy") + .string_arg("--from-to") + .string_arg("LocalBlob") + .string_arg("--overwrite") + .string_arg("true") + .string_arg(archive) + .string_arg(url); - return flatten(cmd_execute_and_capture_output(upload_cmd), Tools::AZCOPY); + auto maybe_code_and_output = cmd_execute_and_capture_output(context, cmd); + return check_zero_exit_code(context, cmd, maybe_code_and_output); } - size_t push_success(const BinaryPackageWriteInfo& request, MessageSink& msg_sink) override + size_t push_success(DiagnosticContext& context, + const Filesystem&, + const BinaryPackageWriteInfo& request) override { const auto& zip_path = request.zip_path.value_or_exit(VCPKG_LINE_INFO); size_t upload_count = 0; + WarningDiagnosticContext wdc{context}; for (const auto& container : m_containers) { - auto res = upload_file(container.make_object_path(request.package_abi), zip_path); - if (res) - { - ++upload_count; - } - else - { - msg_sink.println(warning_prefix().append(std::move(res).error())); - } + upload_count += upload_file(wdc, container.make_object_path(request.package_abi), zip_path); } + return upload_count; } @@ -1291,7 +1309,7 @@ namespace struct GcsStorageTool : IObjectStorageTool { - GcsStorageTool(const ToolCache& cache, MessageSink& sink) : m_tool(cache.get_tool_path(Tools::GSUTIL, sink)) { } + GcsStorageTool(const Path& tool) : m_tool(tool) { } LocalizedString restored_message(size_t count, std::chrono::high_resolution_clock::duration elapsed) const override @@ -1299,29 +1317,37 @@ namespace return msg::format(msgRestoredPackagesFromGCS, msg::count = count, msg::elapsed = ElapsedTime(elapsed)); } - ExpectedL stat(StringView url) const override + Optional stat(DiagnosticContext& context, StringView url) const override { - return flatten_generic( - cmd_execute_and_capture_output(Command{m_tool}.string_arg("-q").string_arg("stat").string_arg(url)), - Tools::GSUTIL, - CacheAvailability::available); + auto cmd = Command{m_tool}.string_arg("-q").string_arg("stat").string_arg(url); + auto maybe_code_and_output = cmd_execute_and_capture_output(context, cmd); + if (check_zero_exit_code(context, cmd, maybe_code_and_output)) + { + return CacheAvailability::available; + } + + return nullopt; } - ExpectedL download_file(StringView object, const Path& archive) const override + Optional download_file(DiagnosticContext& context, + StringView object, + const Path& archive) const override { - return flatten_generic( - cmd_execute_and_capture_output( - Command{m_tool}.string_arg("-q").string_arg("cp").string_arg(object).string_arg(archive)), - Tools::GSUTIL, - RestoreResult::restored); + auto cmd = Command{m_tool}.string_arg("-q").string_arg("cp").string_arg(object).string_arg(archive); + auto maybe_code_and_output = cmd_execute_and_capture_output(context, cmd); + if (check_zero_exit_code(context, cmd, maybe_code_and_output)) + { + return RestoreResult::restored; + } + + return nullopt; } - ExpectedL upload_file(StringView object, const Path& archive) const override + bool upload_file(DiagnosticContext& context, StringView object, const Path& archive) const override { - return flatten( - cmd_execute_and_capture_output( - Command{m_tool}.string_arg("-q").string_arg("cp").string_arg(archive).string_arg(object)), - Tools::GSUTIL); + auto cmd = Command{m_tool}.string_arg("-q").string_arg("cp").string_arg(archive).string_arg(object); + auto maybe_code_and_output = cmd_execute_and_capture_output(context, cmd); + return check_zero_exit_code(context, cmd, maybe_code_and_output); } Path m_tool; @@ -1329,10 +1355,7 @@ namespace struct AwsStorageTool : IObjectStorageTool { - AwsStorageTool(const ToolCache& cache, MessageSink& sink, bool no_sign_request) - : m_tool(cache.get_tool_path(Tools::AWSCLI, sink)), m_no_sign_request(no_sign_request) - { - } + AwsStorageTool(const Path& tool, bool no_sign_request) : m_tool(tool), m_no_sign_request(no_sign_request) { } LocalizedString restored_message(size_t count, std::chrono::high_resolution_clock::duration elapsed) const override @@ -1340,7 +1363,7 @@ namespace return msg::format(msgRestoredPackagesFromAWS, msg::count = count, msg::elapsed = ElapsedTime(elapsed)); } - ExpectedL stat(StringView url) const override + Optional stat(DiagnosticContext& context, StringView url) const override { auto cmd = Command{m_tool}.string_arg("s3").string_arg("ls").string_arg(url); if (m_no_sign_request) @@ -1348,33 +1371,41 @@ namespace cmd.string_arg("--no-sign-request"); } - auto maybe_exit = cmd_execute_and_capture_output(cmd); - + auto maybe_code_and_output = cmd_execute_and_capture_output(context, cmd); // When the file is not found, "aws s3 ls" prints nothing, and returns exit code 1. - // flatten_generic() would treat this as an error, but we want to treat it as a (silent) cache miss - // instead, so we handle this special case before calling flatten_generic(). See + // check_zero_exit_code would treat this as an error, but we want to treat it as a (silent) + // cache miss instead, so we handle this special case. See // https://github.com/aws/aws-cli/issues/5544 for the related aws-cli bug report. - if (auto exit = maybe_exit.get()) + if (auto code_and_output = maybe_code_and_output.get()) { // We want to return CacheAvailability::unavailable even if aws-cli starts to return exit code 0 // with an empty output when the file is missing. This way, both the current and possible future // behavior of aws-cli is covered. - if (exit->exit_code == 0 || exit->exit_code == 1) + if (code_and_output->exit_code == 0 || code_and_output->exit_code == 1) { - if (Strings::trim(exit->output).empty()) + if (Strings::trim(code_and_output->output).empty()) { return CacheAvailability::unavailable; } } + + if (code_and_output->exit_code == 0) + { + return CacheAvailability::available; + } + + report_nonzero_exit_code_and_output( + context, cmd, *code_and_output, RedirectedProcessLaunchSettings{}.echo_in_debug); } - // In the non-special case, simply let flatten_generic() do its job. - return flatten_generic(maybe_exit, Tools::AWSCLI, CacheAvailability::available); + return nullopt; } - ExpectedL download_file(StringView object, const Path& archive) const override + Optional download_file(DiagnosticContext& context, + StringView object, + const Path& archive) const override { - auto r = stat(object); + auto r = stat(context, object); if (auto stat_result = r.get()) { if (*stat_result != CacheAvailability::available) @@ -1384,7 +1415,7 @@ namespace } else { - return r.error(); + return nullopt; } auto cmd = Command{m_tool}.string_arg("s3").string_arg("cp").string_arg(object).string_arg(archive); @@ -1393,17 +1424,25 @@ namespace cmd.string_arg("--no-sign-request"); } - return flatten_generic(cmd_execute_and_capture_output(cmd), Tools::AWSCLI, RestoreResult::restored); + auto maybe_code_and_output = cmd_execute_and_capture_output(context, cmd); + if (check_zero_exit_code(context, cmd, maybe_code_and_output)) + { + return RestoreResult::restored; + } + + return nullopt; } - ExpectedL upload_file(StringView object, const Path& archive) const override + bool upload_file(DiagnosticContext& context, StringView object, const Path& archive) const override { auto cmd = Command{m_tool}.string_arg("s3").string_arg("cp").string_arg(archive).string_arg(object); if (m_no_sign_request) { cmd.string_arg("--no-sign-request"); } - return flatten(cmd_execute_and_capture_output(cmd), Tools::AWSCLI); + + auto maybe_code_and_output = cmd_execute_and_capture_output(context, cmd); + return check_zero_exit_code(context, cmd, maybe_code_and_output); } Path m_tool; @@ -1412,7 +1451,7 @@ namespace struct CosStorageTool : IObjectStorageTool { - CosStorageTool(const ToolCache& cache, MessageSink& sink) : m_tool(cache.get_tool_path(Tools::COSCLI, sink)) { } + CosStorageTool(const Path& tool) : m_tool(tool) { } LocalizedString restored_message(size_t count, std::chrono::high_resolution_clock::duration elapsed) const override @@ -1420,26 +1459,37 @@ namespace return msg::format(msgRestoredPackagesFromCOS, msg::count = count, msg::elapsed = ElapsedTime(elapsed)); } - ExpectedL stat(StringView url) const override + Optional stat(DiagnosticContext& context, StringView url) const override { - return flatten_generic(cmd_execute_and_capture_output(Command{m_tool}.string_arg("ls").string_arg(url)), - Tools::COSCLI, - CacheAvailability::available); + auto cmd = Command{m_tool}.string_arg("ls").string_arg(url); + auto maybe_code_and_output = cmd_execute_and_capture_output(context, cmd); + if (check_zero_exit_code(context, cmd, maybe_code_and_output)) + { + return CacheAvailability::available; + } + + return nullopt; } - ExpectedL download_file(StringView object, const Path& archive) const override + Optional download_file(DiagnosticContext& context, + StringView object, + const Path& archive) const override { - return flatten_generic( - cmd_execute_and_capture_output(Command{m_tool}.string_arg("cp").string_arg(object).string_arg(archive)), - Tools::COSCLI, - RestoreResult::restored); + auto cmd = Command{m_tool}.string_arg("cp").string_arg(object).string_arg(archive); + auto maybe_code_and_output = cmd_execute_and_capture_output(context, cmd); + if (check_zero_exit_code(context, cmd, maybe_code_and_output)) + { + return RestoreResult::restored; + } + + return nullopt; } - ExpectedL upload_file(StringView object, const Path& archive) const override + bool upload_file(DiagnosticContext& context, StringView object, const Path& archive) const override { - return flatten( - cmd_execute_and_capture_output(Command{m_tool}.string_arg("cp").string_arg(archive).string_arg(object)), - Tools::COSCLI); + auto cmd = Command{m_tool}.string_arg("cp").string_arg(archive).string_arg(object); + auto maybe_code_and_output = cmd_execute_and_capture_output(context, cmd); + return check_zero_exit_code(context, cmd, maybe_code_and_output); } Path m_tool; @@ -1447,7 +1497,7 @@ namespace struct AzureUpkgTool { - AzureUpkgTool(const ToolCache& cache, MessageSink& sink) { az_cli = cache.get_tool_path(Tools::AZCLI, sink); } + AzureUpkgTool(const Path& tool_path) : az_cli(tool_path) { } Command base_cmd(const AzureUpkgSource& src, StringView package_name, @@ -1473,82 +1523,83 @@ namespace return cmd; } - ExpectedL download(const AzureUpkgSource& src, - StringView package_name, - StringView package_version, - const Path& download_path, - MessageSink& sink) const + bool download(DiagnosticContext& context, + const AzureUpkgSource& src, + StringView package_name, + StringView package_version, + const Path& download_path) const { Command cmd = base_cmd(src, package_name, package_version, "download"); cmd.string_arg("--path").string_arg(download_path); - return run_az_artifacts_cmd(cmd, sink); + return run_az_artifacts_cmd(context, cmd); } - ExpectedL publish(const AzureUpkgSource& src, - StringView package_name, - StringView package_version, - const Path& zip_path, - StringView description, - MessageSink& sink) const + bool publish(DiagnosticContext& context, + const AzureUpkgSource& src, + StringView package_name, + StringView package_version, + const Path& zip_path, + StringView description) const { Command cmd = base_cmd(src, package_name, package_version, "publish"); cmd.string_arg("--description").string_arg(description).string_arg("--path").string_arg(zip_path); - return run_az_artifacts_cmd(cmd, sink); + return run_az_artifacts_cmd(context, cmd); } - ExpectedL run_az_artifacts_cmd(const Command& cmd, MessageSink& sink) const + bool run_az_artifacts_cmd(DiagnosticContext& context, const Command& cmd) const { RedirectedProcessLaunchSettings show_in_debug_settings; show_in_debug_settings.echo_in_debug = EchoInDebug::Show; - return cmd_execute_and_capture_output(cmd, show_in_debug_settings) - .then([&](ExitCodeAndOutput&& res) -> ExpectedL { - if (res.exit_code == 0) - { - return {Unit{}}; - } + auto maybe_code_and_output = cmd_execute_and_capture_output(context, cmd, show_in_debug_settings); + if (auto code_and_output = maybe_code_and_output.get()) + { + if (code_and_output->exit_code == 0) + { + return true; + } - // az command line error message: Before you can run Azure DevOps commands, you need to - // run the login command(az login if using AAD/MSA identity else az devops login if using PAT - // token) to setup credentials. - if (res.output.find("you need to run the login command") != std::string::npos) - { - sink.println(Color::warning, - msgFailedVendorAuthentication, - msg::vendor = "Universal Packages", - msg::url = "https://learn.microsoft.com/cli/azure/authenticate-azure-cli"); - } - return LocalizedString::from_raw(std::move(res).output); - }); + report_nonzero_exit_code_and_output( + context, cmd, *code_and_output, RedirectedProcessLaunchSettings{}.echo_in_debug); + // az command line error message: Before you can run Azure DevOps commands, you need to + // run the login command(az login if using AAD/MSA identity else az devops login if using PAT + // token) to setup credentials. + if (code_and_output->output.find("you need to run the login command") != std::string::npos) + { + context.report(DiagnosticLine{ + DiagKind::Error, + msg::format(msgFailedVendorAuthentication, + msg::vendor = "Universal Packages", + msg::url = "https://learn.microsoft.com/cli/azure/authenticate-azure-cli")}); + } + } + + return false; } + Path az_cli; }; struct AzureUpkgPutBinaryProvider : public IWriteBinaryProvider { - AzureUpkgPutBinaryProvider(const ToolCache& cache, MessageSink& sink, std::vector&& sources) - : m_azure_tool(cache, sink), m_sources(sources) + AzureUpkgPutBinaryProvider(const Path& tool_path, std::vector&& sources) + : m_azure_tool(tool_path), m_sources(std::move(sources)) { } - size_t push_success(const BinaryPackageWriteInfo& request, MessageSink& msg_sink) override + size_t push_success(DiagnosticContext& context, + const Filesystem&, + const BinaryPackageWriteInfo& request) override { size_t count_stored = 0; auto ref = make_feedref(request, ""); std::string package_description = "Cached package for " + ref.id; const Path& zip_path = request.zip_path.value_or_exit(VCPKG_LINE_INFO); + WarningDiagnosticContext wdc{context}; for (auto&& write_src : m_sources) { - auto res = - m_azure_tool.publish(write_src, ref.id, ref.version, zip_path, package_description, msg_sink); - if (res) - { - count_stored++; - } - else - { - msg_sink.println(res.error()); - } + count_stored += + m_azure_tool.publish(wdc, write_src, ref.id, ref.version, zip_path, package_description); } return count_stored; @@ -1564,22 +1615,24 @@ namespace struct AzureUpkgGetBinaryProvider : public ZipReadBinaryProvider { - AzureUpkgGetBinaryProvider(ZipTool zip, - const Filesystem& fs, - const ToolCache& cache, - MessageSink& sink, + AzureUpkgGetBinaryProvider(const ZipTool& zip, + const Path& azcli_path, AzureUpkgSource&& source, const Path& buildtrees) - : ZipReadBinaryProvider(std::move(zip), fs) - , m_azure_tool(cache, sink) - , m_sink(sink) + : ZipReadBinaryProvider(zip) + , m_azure_tool(azcli_path) , m_source(std::move(source)) , m_buildtrees(buildtrees) { } // Prechecking doesn't exist with universal packages so it's not implemented - void precheck(View, Span) const override { } + void precheck(DiagnosticContext&, + const Filesystem&, + View, + Span) const override + { + } LocalizedString restored_message(size_t count, std::chrono::high_resolution_clock::duration elapsed) const override @@ -1587,8 +1640,12 @@ namespace return msg::format(msgRestoredPackagesFromAZUPKG, msg::count = count, msg::elapsed = ElapsedTime(elapsed)); } - void acquire_zips(View actions, Span> out_zips) const override + void acquire_zips(DiagnosticContext& context, + const Filesystem& fs, + View actions, + Span> out_zips) const override { + WarningDiagnosticContext wdc{context}; for (size_t i = 0; i < actions.size(); ++i) { const auto& action = *actions[i]; @@ -1599,27 +1656,21 @@ namespace Path temp_zip_path = temp_dir / fmt::format("{}.zip", ref.id); Path final_zip_path = m_buildtrees / fmt::format("{}.zip", ref.id); - const auto result = m_azure_tool.download(m_source, ref.id, ref.version, temp_dir, m_sink); - if (result.has_value() && m_fs.exists(temp_zip_path, IgnoreErrors{})) + const auto result = m_azure_tool.download(wdc, m_source, ref.id, ref.version, temp_dir); + if (result && fs.exists(temp_zip_path, IgnoreErrors{}) && fs.rename(wdc, temp_zip_path, final_zip_path)) { - m_fs.rename(temp_zip_path, final_zip_path, VCPKG_LINE_INFO); out_zips[i].emplace(std::move(final_zip_path), RemoveWhen::always); } - else - { - msg::println_warning(result.error()); - } - if (m_fs.exists(temp_dir, IgnoreErrors{})) + if (fs.exists(temp_dir, IgnoreErrors{})) { - m_fs.remove(temp_dir, VCPKG_LINE_INFO); + fs.remove_all(temp_dir, IgnoreErrors{}); } } } private: AzureUpkgTool m_azure_tool; - MessageSink& m_sink; AzureUpkgSource m_source; const Path& m_buildtrees; }; @@ -2299,7 +2350,7 @@ namespace vcpkg { LocalizedString UrlTemplate::valid() const { - BufferedDiagnosticContext bdc{out_sink}; + SinkBufferedDiagnosticContext bdc{out_sink}; std::vector invalid_keys; auto result = api_stable_format(bdc, url_template, [&](std::string&, StringView key) { static constexpr StringLiteral valid_keys[] = {"name", "version", "sha", "triplet"}; @@ -2394,7 +2445,7 @@ namespace vcpkg get_environment_variable(EnvironmentVariableGitHubSha).value_or("")}; } - void ReadOnlyBinaryCache::fetch(View actions) + void ReadOnlyBinaryCache::fetch(DiagnosticContext& context, const Filesystem& fs, View actions) { std::vector action_ptrs; std::vector restores; @@ -2420,7 +2471,7 @@ namespace vcpkg if (action_ptrs.empty()) continue; ElapsedTimer timer; - provider->fetch(action_ptrs, restores); + provider->fetch(context, fs, action_ptrs, restores); size_t num_restored = 0; for (size_t i = 0; i < restores.size(); ++i) { @@ -2434,7 +2485,7 @@ namespace vcpkg ++num_restored; } } - msg::println(provider->restored_message( + context.statusln(provider->restored_message( num_restored, timer.elapsed().as())); } } @@ -2462,7 +2513,9 @@ namespace vcpkg } } - std::vector ReadOnlyBinaryCache::precheck(View actions) + std::vector ReadOnlyBinaryCache::precheck(DiagnosticContext& context, + const Filesystem& fs, + View actions) { const std::vector statuses = Util::fmap(actions, [this](const InstallPlanAction* action) { Checks::check_exit(VCPKG_LINE_INFO, action); @@ -2488,7 +2541,7 @@ namespace vcpkg } if (action_ptrs.empty()) continue; - provider->precheck(action_ptrs, cache_result); + provider->precheck(context, fs, action_ptrs, cache_result); for (size_t i = 0; i < action_ptrs.size(); ++i) { @@ -2567,10 +2620,12 @@ namespace vcpkg return (state & SubmittedMask) - ((state & CompletedMask) >> UpperShift); } - bool BinaryCache::install_providers(const VcpkgCmdArguments& args, - const VcpkgPaths& paths, - MessageSink& status_sink) + bool BinaryCache::install_providers(DiagnosticContext& context, + const VcpkgCmdArguments& args, + const VcpkgPaths& paths) { + auto& fs = paths.get_filesystem(); + auto& tools = paths.get_tool_cache(); if (args.binary_caching_enabled()) { if (Debug::g_debugging) @@ -2600,7 +2655,7 @@ namespace vcpkg parse_binary_provider_configs(args.env_binary_sources.value_or(""), args.cli_binary_sources); if (!sRawHolder) { - status_sink.println(Color::error, std::move(sRawHolder).error()); + context.report_error(std::move(sRawHolder).error()); return false; } auto& s = *sRawHolder.get(); @@ -2639,8 +2694,6 @@ namespace vcpkg m_config.nuget_repo = get_nuget_repo_info_from_env(args); - auto& fs = paths.get_filesystem(); - auto& tools = paths.get_tool_cache(); const auto& buildtrees = paths.buildtrees(); m_config.nuget_prefix = s.nuget_prefix; @@ -2648,22 +2701,50 @@ namespace vcpkg std::shared_ptr gcs_tool; if (!s.gcs_read_prefixes.empty() || !s.gcs_write_prefixes.empty()) { - gcs_tool = std::make_shared(tools, out_sink); + if (auto gcs_tool_path = tools.get_tool_path(context, fs, Tools::GSUTIL)) + { + gcs_tool = std::make_shared(*gcs_tool_path); + } + else + { + return false; + } } std::shared_ptr aws_tool; if (!s.aws_read_prefixes.empty() || !s.aws_write_prefixes.empty()) { - aws_tool = std::make_shared(tools, out_sink, s.aws_no_sign_request); + if (auto aws_tool_path = tools.get_tool_path(context, fs, Tools::AWSCLI)) + { + aws_tool = std::make_shared(*aws_tool_path, s.aws_no_sign_request); + } + else + { + return false; + } } std::shared_ptr cos_tool; if (!s.cos_read_prefixes.empty() || !s.cos_write_prefixes.empty()) { - cos_tool = std::make_shared(tools, out_sink); + if (auto cos_tool_path = tools.get_tool_path(context, fs, Tools::COSCLI)) + { + cos_tool = std::make_shared(*cos_tool_path); + } + else + { + return false; + } } Path azcopy_tool; if (!s.azcopy_read_templates.empty() || !s.azcopy_write_templates.empty()) { - azcopy_tool = tools.get_tool_path(Tools::AZCOPY, out_sink); + if (auto tool = tools.get_tool_path(context, fs, Tools::AZCOPY)) + { + azcopy_tool = *tool; + } + else + { + return false; + } } if (!s.archives_to_read.empty() || !s.url_templates_to_get.empty() || !s.gcs_read_prefixes.empty() || @@ -2671,62 +2752,82 @@ namespace vcpkg !s.azcopy_read_templates.empty()) { ZipTool zip_tool; - zip_tool.setup(tools, out_sink); + if (!zip_tool.setup(context, fs, tools)) + { + return false; + } + for (auto&& dir : s.archives_to_read) { - m_config.read.push_back(std::make_unique(zip_tool, fs, std::move(dir))); + m_config.read.push_back(std::make_unique(zip_tool, std::move(dir))); } for (auto&& url : s.url_templates_to_get) { m_config.read.push_back( - std::make_unique(zip_tool, fs, buildtrees, std::move(url), s.secrets)); + std::make_unique(zip_tool, buildtrees, std::move(url), s.secrets)); } for (auto&& prefix : s.gcs_read_prefixes) { m_config.read.push_back( - std::make_unique(zip_tool, fs, buildtrees, std::move(prefix), gcs_tool)); + std::make_unique(zip_tool, buildtrees, std::move(prefix), gcs_tool)); } for (auto&& prefix : s.aws_read_prefixes) { m_config.read.push_back( - std::make_unique(zip_tool, fs, buildtrees, std::move(prefix), aws_tool)); + std::make_unique(zip_tool, buildtrees, std::move(prefix), aws_tool)); } for (auto&& prefix : s.cos_read_prefixes) { m_config.read.push_back( - std::make_unique(zip_tool, fs, buildtrees, std::move(prefix), cos_tool)); + std::make_unique(zip_tool, buildtrees, std::move(prefix), cos_tool)); } - for (auto&& src : s.upkg_templates_to_get) + if (!s.upkg_templates_to_get.empty()) { - m_config.read.push_back(std::make_unique( - zip_tool, fs, tools, out_sink, std::move(src), buildtrees)); + if (const auto* azcli_tool = tools.get_tool_path(context, fs, Tools::AZCLI)) + { + for (auto&& src : s.upkg_templates_to_get) + { + m_config.read.push_back(std::make_unique( + zip_tool, *azcli_tool, std::move(src), buildtrees)); + } + } + else + { + return false; + } } for (auto&& prefix : s.azcopy_read_templates) { - m_config.read.push_back(std::make_unique( - zip_tool, fs, buildtrees, std::move(prefix), azcopy_tool)); + m_config.read.push_back( + std::make_unique(zip_tool, buildtrees, std::move(prefix), azcopy_tool)); } } if (!s.upkg_templates_to_put.empty()) { - m_config.write.push_back( - std::make_unique(tools, out_sink, std::move(s.upkg_templates_to_put))); + if (const auto* azcli_tool = tools.get_tool_path(context, fs, Tools::AZCLI)) + { + m_config.write.push_back( + std::make_unique(*azcli_tool, std::move(s.upkg_templates_to_put))); + } + else + { + return false; + } } if (!s.archives_to_write.empty()) { - m_config.write.push_back( - std::make_unique(fs, std::move(s.archives_to_write))); + m_config.write.push_back(std::make_unique(std::move(s.archives_to_write))); } if (!s.azblob_templates_to_put.empty()) { m_config.write.push_back( - std::make_unique(fs, std::move(s.azblob_templates_to_put), s.secrets)); + std::make_unique(std::move(s.azblob_templates_to_put), s.secrets)); } if (!s.url_templates_to_put.empty()) { @@ -2752,18 +2853,26 @@ namespace vcpkg if (!s.sources_to_read.empty() || !s.configs_to_read.empty() || !s.sources_to_write.empty() || !s.configs_to_write.empty()) { - NugetBaseBinaryProvider nuget_base( - fs, NuGetTool(tools, out_sink, s), paths.packages(), buildtrees, s.nuget_prefix); - if (!s.sources_to_read.empty()) - m_config.read.push_back( - std::make_unique(nuget_base, nuget_sources_arg(s.sources_to_read))); - for (auto&& config : s.configs_to_read) - m_config.read.push_back( - std::make_unique(nuget_base, nuget_configfile_arg(config))); - if (!s.sources_to_write.empty() || !s.configs_to_write.empty()) + auto maybe_nuget_tools = get_nuget_tool_tools(context, fs, tools); + if (auto* nuget_tools = maybe_nuget_tools.get()) + { + NugetBaseBinaryProvider nuget_base( + NuGetTool(std::move(*nuget_tools), s), paths.packages(), buildtrees, s.nuget_prefix); + if (!s.sources_to_read.empty()) + m_config.read.push_back(std::make_unique( + nuget_base, nuget_sources_arg(s.sources_to_read))); + for (auto&& config : s.configs_to_read) + m_config.read.push_back( + std::make_unique(nuget_base, nuget_configfile_arg(config))); + if (!s.sources_to_write.empty() || !s.configs_to_write.empty()) + { + m_config.write.push_back(std::make_unique( + nuget_base, std::move(s.sources_to_write), std::move(s.configs_to_write))); + } + } + else { - m_config.write.push_back(std::make_unique( - nuget_base, std::move(s.sources_to_write), std::move(s.configs_to_write))); + return false; } } @@ -2778,7 +2887,10 @@ namespace vcpkg m_needs_zip_file = Util::any_of(m_config.write, [](auto&& p) { return p->needs_zip_file(); }); if (m_needs_zip_file) { - m_zip_tool.setup(paths.get_tool_cache(), status_sink); + if (!m_zip_tool.setup(context, fs, tools)) + { + return false; + } } return true; @@ -2862,6 +2974,8 @@ namespace vcpkg void BinaryCache::push_thread_main() { std::vector my_tasks; + PrintingDiagnosticContext pdc{m_bg_msg_sink}; + WarningDiagnosticContext wdc{pdc}; while (m_actions_to_push.get_work(my_tasks)) { for (auto& action_to_push : my_tasks) @@ -2870,7 +2984,6 @@ namespace vcpkg if (m_needs_zip_file) { Path zip_path = action_to_push.request.package_dir + ".zip"; - PrintingDiagnosticContext pdc{m_bg_msg_sink}; if (m_zip_tool.compress_directory_to_zip(pdc, m_fs, action_to_push.request.package_dir, zip_path)) { action_to_push.request.zip_path = std::move(zip_path); @@ -2882,18 +2995,18 @@ namespace vcpkg { if (!provider->needs_zip_file() || action_to_push.request.zip_path.has_value()) { - num_destinations += provider->push_success(action_to_push.request, m_bg_msg_sink); + num_destinations += provider->push_success(pdc, m_fs, action_to_push.request); } } if (action_to_push.request.zip_path) { - m_fs.remove(*action_to_push.request.zip_path.get(), IgnoreErrors{}); + (void)m_fs.remove(wdc, *action_to_push.request.zip_path.get()); } if (action_to_push.clean_after_push == CleanPackages::Yes) { - m_fs.remove_all(action_to_push.request.package_dir, VCPKG_LINE_INFO); + (void)m_fs.remove_all(wdc, action_to_push.request.package_dir); } auto sync_state = m_synchronizer.fetch_add_completed(); diff --git a/src/vcpkg/buildenvironment.cpp b/src/vcpkg/buildenvironment.cpp index b57caf74ff..6fd204f29d 100644 --- a/src/vcpkg/buildenvironment.cpp +++ b/src/vcpkg/buildenvironment.cpp @@ -18,6 +18,6 @@ namespace vcpkg local_variables.emplace_back("_VCPKG_INSTALLED_DIR", paths.installed().root()); local_variables.emplace_back("DOWNLOADS", paths.downloads); local_variables.emplace_back("VCPKG_MANIFEST_INSTALL", "OFF"); - return make_basic_cmake_cmd(paths.get_tool_exe(Tools::CMAKE, out_sink), cmake_script, local_variables); + return make_basic_cmake_cmd(paths.get_tool_path_required(Tools::CMAKE), cmake_script, local_variables); } } diff --git a/src/vcpkg/commands.bootstrap-standalone.cpp b/src/vcpkg/commands.bootstrap-standalone.cpp index a11717e8eb..2d8cc7fde0 100644 --- a/src/vcpkg/commands.bootstrap-standalone.cpp +++ b/src/vcpkg/commands.bootstrap-standalone.cpp @@ -105,7 +105,11 @@ namespace vcpkg Checks::exit_fail(VCPKG_LINE_INFO); } - extract_tar(find_system_tar(fs).value_or_exit(VCPKG_LINE_INFO), *tarball, vcpkg_root); - Checks::exit_success(VCPKG_LINE_INFO); + Checks::exit_with_code( + VCPKG_LINE_INFO, + !extract_tar(console_diagnostic_context, + find_system_tar(console_diagnostic_context, fs).value_or_exit(VCPKG_LINE_INFO), + *tarball, + vcpkg_root)); } } diff --git a/src/vcpkg/commands.build.cpp b/src/vcpkg/commands.build.cpp index c7f7b2099b..6baf4ae20c 100644 --- a/src/vcpkg/commands.build.cpp +++ b/src/vcpkg/commands.build.cpp @@ -289,7 +289,7 @@ namespace vcpkg } BinaryCache binary_cache(fs); - if (!binary_cache.install_providers(args, paths, out_sink)) + if (!binary_cache.install_providers(console_diagnostic_context, args, paths)) { Checks::exit_fail(VCPKG_LINE_INFO); } @@ -602,7 +602,7 @@ namespace vcpkg }); return base_env.cmd_cache.get_lazy(build_env_cmd, [&]() { - const Path& powershell_exe_path = paths.get_tool_exe("powershell-core", out_sink); + const Path& powershell_exe_path = paths.get_tool_path_required("powershell-core"); auto clean_env = get_modified_clean_environment(base_env.env_map, powershell_exe_path.parent_path()); if (build_env_cmd.empty()) return clean_env; @@ -791,7 +791,7 @@ namespace vcpkg out_vars.emplace_back(CMakeVariableConcurrency, std::to_string(get_concurrency())); out_vars.emplace_back(CMakeVariablePlatformToolset, toolset.version); // Make sure GIT could be found - out_vars.emplace_back(CMakeVariableGit, paths.get_tool_exe(Tools::GIT, out_sink)); + out_vars.emplace_back(CMakeVariableGit, paths.get_tool_path_required(Tools::GIT)); } static CompilerInfo load_compiler_info(const VcpkgPaths& paths, @@ -1508,11 +1508,11 @@ namespace vcpkg Hash::get_file_hash(fs, file, Hash::Algorithm::Sha256).value_or_exit(VCPKG_LINE_INFO)); } - abi_tag_entries.emplace_back(AbiTagCMake, paths.get_tool_version(Tools::CMAKE, out_sink)); + abi_tag_entries.emplace_back(AbiTagCMake, paths.get_tool_version_required(Tools::CMAKE)); // This #ifdef is mirrored in tools.cpp's PowershellProvider #if defined(_WIN32) - abi_tag_entries.emplace_back(AbiTagPowershell, paths.get_tool_version("powershell-core", out_sink)); + abi_tag_entries.emplace_back(AbiTagPowershell, paths.get_tool_version_required("powershell-core")); #endif abi_tag_entries.emplace_back(AbiTagPortsDotCMake, paths.get_ports_cmake_hash().to_string()); @@ -1909,7 +1909,7 @@ namespace vcpkg } } fmt::format_to( - std::back_inserter(issue_body), "- CMake Version: {}\n", paths.get_tool_version(Tools::CMAKE, null_sink)); + std::back_inserter(issue_body), "- CMake Version: {}\n", paths.get_tool_version_required(Tools::CMAKE)); fmt::format_to(std::back_inserter(issue_body), "-{}\n", paths.get_toolver_diagnostics()); fmt::format_to(std::back_inserter(issue_body), diff --git a/src/vcpkg/commands.ci.cpp b/src/vcpkg/commands.ci.cpp index b5bf83cbc0..fcd569b24f 100644 --- a/src/vcpkg/commands.ci.cpp +++ b/src/vcpkg/commands.ci.cpp @@ -494,13 +494,13 @@ namespace vcpkg auto action_plan = compute_full_plan( paths, provider, var_provider, ci_specs.requested, packages_dir_assigner, create_install_plan_options); BinaryCache binary_cache(fs); - if (!binary_cache.install_providers(args, paths, out_sink)) + if (!binary_cache.install_providers(console_diagnostic_context, args, paths)) { Checks::exit_fail(VCPKG_LINE_INFO); } auto install_actions = Util::fmap(action_plan.install_actions, [](const InstallPlanAction& action) { return &action; }); - const auto precheck_results = binary_cache.precheck(install_actions); + const auto precheck_results = binary_cache.precheck(console_diagnostic_context, fs, install_actions); const auto pre_build_status = compute_pre_build_statuses(ci_specs, precheck_results, known_failure_abis, parent_hashes, action_plan); { @@ -550,7 +550,7 @@ namespace vcpkg } install_preclear_plan_packages(paths, action_plan); - binary_cache.fetch(action_plan.install_actions); + binary_cache.fetch(console_diagnostic_context, fs, action_plan.install_actions); auto summary = install_execute_plan( args, paths, host_triplet, build_options, action_plan, status_db, binary_cache, *build_logs_recorder); diff --git a/src/vcpkg/commands.export.cpp b/src/vcpkg/commands.export.cpp index 65c7566efe..2092d9ee88 100644 --- a/src/vcpkg/commands.export.cpp +++ b/src/vcpkg/commands.export.cpp @@ -166,9 +166,9 @@ namespace // -NoDefaultExcludes is needed for ".vcpkg-root" Command cmd; #ifndef _WIN32 - cmd.string_arg(paths.get_tool_exe(Tools::MONO, out_sink)); + cmd.string_arg(paths.get_tool_path_required(Tools::MONO)); #endif - cmd.string_arg(paths.get_tool_exe(Tools::NUGET, out_sink)) + cmd.string_arg(paths.get_tool_path_required(Tools::NUGET)) .string_arg("pack") .string_arg(nuspec_file_path) .string_arg("-OutputDirectory") @@ -218,7 +218,7 @@ namespace const Path& output_dir, const ArchiveFormat& format) { - const Path& cmake_exe = paths.get_tool_exe(Tools::CMAKE, out_sink); + const Path& cmake_exe = paths.get_tool_path_required(Tools::CMAKE); const auto exported_dir_filename = raw_exported_dir.filename(); const auto exported_archive_filename = fmt::format("{}.{}", exported_dir_filename, format.extension()); diff --git a/src/vcpkg/commands.fetch.cpp b/src/vcpkg/commands.fetch.cpp index f5bfc3232f..8b0372f25c 100644 --- a/src/vcpkg/commands.fetch.cpp +++ b/src/vcpkg/commands.fetch.cpp @@ -34,8 +34,15 @@ namespace vcpkg const auto parsed = args.parse_arguments(CommandFetchMetadata); const bool stderr_status = Util::Sets::contains(parsed.switches, STDERR_STATUS[0].name); const std::string tool = parsed.command_arguments[0]; - const Path& tool_path = paths.get_tool_exe(tool, stderr_status ? stderr_sink : stdout_sink); - msg::write_unlocalized_text(Color::none, tool_path.native() + '\n'); - Checks::exit_success(VCPKG_LINE_INFO); + if (const auto* tool_path = + paths.get_tool_path(stderr_status ? stderr_diagnostic_context : console_diagnostic_context, tool)) + { + msg::write_unlocalized_text_to_stdout(Color::none, tool_path->native() + '\n'); + Checks::exit_success(VCPKG_LINE_INFO); + } + else + { + Checks::exit_fail(VCPKG_LINE_INFO); + } } } // namespace vcpkg diff --git a/src/vcpkg/commands.install.cpp b/src/vcpkg/commands.install.cpp index f9c7f7ef35..701dfd4520 100644 --- a/src/vcpkg/commands.install.cpp +++ b/src/vcpkg/commands.install.cpp @@ -1446,13 +1446,13 @@ namespace vcpkg BinaryCache binary_cache(fs); if (!only_downloads) { - if (!binary_cache.install_providers(args, paths, out_sink)) + if (!binary_cache.install_providers(console_diagnostic_context, args, paths)) { Checks::exit_fail(VCPKG_LINE_INFO); } } - binary_cache.fetch(action_plan.install_actions); + binary_cache.fetch(console_diagnostic_context, fs, action_plan.install_actions); const InstallSummary summary = install_execute_plan(args, paths, host_triplet, diff --git a/src/vcpkg/commands.integrate.cpp b/src/vcpkg/commands.integrate.cpp index eb0673eb8f..651be51a4e 100644 --- a/src/vcpkg/commands.integrate.cpp +++ b/src/vcpkg/commands.integrate.cpp @@ -359,7 +359,7 @@ namespace vcpkg #if defined(WIN32) auto& fs = paths.get_filesystem(); - const Path& nuget_exe = paths.get_tool_exe(Tools::NUGET, out_sink); + const Path& nuget_exe = paths.get_tool_path_required(Tools::NUGET); const auto tmp_dir = fs.create_or_get_temp_directory(VCPKG_LINE_INFO); const auto targets_file_path = tmp_dir / "vcpkg.nuget.targets"; @@ -418,7 +418,7 @@ namespace vcpkg static constexpr StringLiteral TITLE = "PowerShell Tab-Completion"; const auto script_path = paths.scripts / "addPoshVcpkgToPowershellProfile.ps1"; - const auto& ps = paths.get_tool_exe("powershell-core", out_sink); + const auto& ps = paths.get_tool_path_required("powershell-core"); const auto rc = cmd_execute(Command{ps} .string_arg("-NoProfile") .string_arg("-ExecutionPolicy") diff --git a/src/vcpkg/commands.portsdiff.cpp b/src/vcpkg/commands.portsdiff.cpp index 82a83b0d11..c0ac7db7ff 100644 --- a/src/vcpkg/commands.portsdiff.cpp +++ b/src/vcpkg/commands.portsdiff.cpp @@ -104,15 +104,20 @@ namespace vcpkg StringView git_commit_id_for_previous_snapshot, StringView git_commit_id_for_current_snapshot) { - const auto& git_exe = paths.get_tool_exe(Tools::GIT, out_sink); - if (!check_commit_exists(context, git_exe, paths.root, git_commit_id_for_previous_snapshot) || - !check_commit_exists(context, git_exe, paths.root, git_commit_id_for_current_snapshot)) + const auto* git_exe = paths.get_tool_path(context, Tools::GIT); + if (!git_exe) + { + return nullopt; + } + + if (!check_commit_exists(context, *git_exe, paths.root, git_commit_id_for_previous_snapshot) || + !check_commit_exists(context, *git_exe, paths.root, git_commit_id_for_current_snapshot)) { return nullopt; } const auto maybe_previous = - read_ports_from_commit(context, paths, git_exe, "previous", git_commit_id_for_previous_snapshot); + read_ports_from_commit(context, paths, *git_exe, "previous", git_commit_id_for_previous_snapshot); const auto previous = maybe_previous.get(); if (!previous) { @@ -120,7 +125,7 @@ namespace vcpkg } const auto maybe_current = - read_ports_from_commit(context, paths, git_exe, "current", git_commit_id_for_current_snapshot); + read_ports_from_commit(context, paths, *git_exe, "current", git_commit_id_for_current_snapshot); const auto current = maybe_current.get(); if (!current) { diff --git a/src/vcpkg/commands.set-installed.cpp b/src/vcpkg/commands.set-installed.cpp index 36918e2d4b..6d24f85dd3 100644 --- a/src/vcpkg/commands.set-installed.cpp +++ b/src/vcpkg/commands.set-installed.cpp @@ -262,13 +262,13 @@ namespace vcpkg BinaryCache binary_cache(fs); if (build_options.only_downloads == OnlyDownloads::No) { - if (!binary_cache.install_providers(args, paths, out_sink)) + if (!binary_cache.install_providers(console_diagnostic_context, args, paths)) { Checks::exit_fail(VCPKG_LINE_INFO); } } - binary_cache.fetch(action_plan.install_actions); + binary_cache.fetch(console_diagnostic_context, fs, action_plan.install_actions); const auto summary = install_execute_plan(args, paths, host_triplet, diff --git a/src/vcpkg/commands.test-features.cpp b/src/vcpkg/commands.test-features.cpp index c5bffcf9f1..d30c3da383 100644 --- a/src/vcpkg/commands.test-features.cpp +++ b/src/vcpkg/commands.test-features.cpp @@ -58,7 +58,7 @@ namespace { auto& fs = paths.get_filesystem(); auto& builtin_ports = paths.builtin_ports_directory(); - auto git_exe = paths.get_tool_exe(Tools::GIT, out_sink); + auto git_exe = paths.get_tool_path_required(Tools::GIT); auto ports_dir_prefix = git_prefix(console_diagnostic_context, git_exe, builtin_ports).value_or_quiet_exit(VCPKG_LINE_INFO); const auto locator = GitRepoLocator{GitRepoLocatorKind::CurrentDirectory, builtin_ports}; @@ -461,7 +461,7 @@ namespace vcpkg const auto test_features_separately = !Util::Sets::contains(options.switches, SwitchNoSeparated); BinaryCache binary_cache(fs); - if (!binary_cache.install_providers(args, paths, out_sink)) + if (!binary_cache.install_providers(console_diagnostic_context, args, paths)) { Checks::exit_fail(VCPKG_LINE_INFO); } @@ -715,7 +715,7 @@ namespace vcpkg } msg::println(msgPrecheckBinaryCache); - binary_cache.precheck(actions_to_check); + binary_cache.precheck(console_diagnostic_context, fs, actions_to_check); Util::stable_sort(specs_to_test, [](const SpecToTest& left, const SpecToTest& right) noexcept { return left.plan.install_actions.size() < right.plan.install_actions.size(); @@ -807,8 +807,8 @@ namespace vcpkg { const InstallPlanAction* action = &install_plan.install_actions.back(); - if (binary_cache.precheck(View(&action, 1)).front() == - CacheAvailability::available) + if (binary_cache.precheck(console_diagnostic_context, fs, View(&action, 1)) + .front() == CacheAvailability::available) { msg::println(msgSkipTestingOfPortAlreadyInBinaryCache, msg::sha = action->package_abi_or_exit(VCPKG_LINE_INFO)); @@ -828,7 +828,7 @@ namespace vcpkg } install_clear_installed_packages(paths, install_plan.install_actions); - binary_cache.fetch(install_plan.install_actions); + binary_cache.fetch(console_diagnostic_context, fs, install_plan.install_actions); const auto summary = install_execute_plan(args, paths, host_triplet, diff --git a/src/vcpkg/commands.upgrade.cpp b/src/vcpkg/commands.upgrade.cpp index 11c9884c8b..6aead24423 100644 --- a/src/vcpkg/commands.upgrade.cpp +++ b/src/vcpkg/commands.upgrade.cpp @@ -204,13 +204,13 @@ namespace vcpkg var_provider.load_tag_vars(action_plan, host_triplet); BinaryCache binary_cache(fs); - if (!binary_cache.install_providers(args, paths, out_sink)) + if (!binary_cache.install_providers(console_diagnostic_context, args, paths)) { Checks::exit_fail(VCPKG_LINE_INFO); } compute_all_abis(paths, action_plan, var_provider, status_db); - binary_cache.fetch(action_plan.install_actions); + binary_cache.fetch(console_diagnostic_context, fs, action_plan.install_actions); const InstallSummary summary = install_execute_plan( args, paths, host_triplet, build_options, action_plan, status_db, binary_cache, null_build_logs_recorder); msg::println(msgTotalInstallTime, msg::elapsed = summary.elapsed); diff --git a/src/vcpkg/commands.z-check-tools-sha.cpp b/src/vcpkg/commands.z-check-tools-sha.cpp index 0356b3615a..91050ff6ac 100644 --- a/src/vcpkg/commands.z-check-tools-sha.cpp +++ b/src/vcpkg/commands.z-check-tools-sha.cpp @@ -41,14 +41,19 @@ namespace vcpkg auto content = fs.read_contents(file_to_check, VCPKG_LINE_INFO); - auto data = parse_tool_data(content, file_to_check).value_or_exit(VCPKG_LINE_INFO); + auto maybe_data = parse_tool_data(console_diagnostic_context, content, file_to_check); + auto data = maybe_data.get(); + if (!data) + { + Checks::exit_fail(VCPKG_LINE_INFO); + } auto only_name_iter = parsed.settings.find(SwitchOnlyWithName); std::unordered_map url_to_sha; std::vector> urlAndPaths; - for (auto& entry : data) + for (auto& entry : *data) { if (entry.url.empty()) continue; if (only_name_iter != parsed.settings.end()) diff --git a/src/vcpkg/commands.z-extract.cpp b/src/vcpkg/commands.z-extract.cpp index 0c39d69e31..1394968027 100644 --- a/src/vcpkg/commands.z-extract.cpp +++ b/src/vcpkg/commands.z-extract.cpp @@ -122,8 +122,9 @@ namespace vcpkg static void extract_and_strip( const Filesystem& fs, const VcpkgPaths& paths, StripSetting strip_setting, Path archive_path, Path output_dir) { - auto temp_dir = - extract_archive_to_temp_subdirectory(fs, paths.get_tool_cache(), null_sink, archive_path, output_dir); + auto temp_dir = extract_archive_to_temp_subdirectory( + null_diagnostic_context, fs, paths.get_tool_cache(), archive_path, output_dir) + .value_or_exit(VCPKG_LINE_INFO); ExtractedArchive archive = { temp_dir, output_dir, fs.get_regular_files_recursive_lexically_proximate(temp_dir, VCPKG_LINE_INFO)}; @@ -161,7 +162,10 @@ namespace vcpkg if (strip_setting.mode == StripMode::Manual && strip_setting.count == 0) { - extract_archive(fs, paths.get_tool_cache(), null_sink, archive_path, destination_path); + if (!extract_archive(null_diagnostic_context, fs, paths.get_tool_cache(), archive_path, destination_path)) + { + Checks::exit_fail(VCPKG_LINE_INFO); + } } else { diff --git a/src/vcpkg/configure-environment.cpp b/src/vcpkg/configure-environment.cpp index e2c65a488a..63f6a05cde 100644 --- a/src/vcpkg/configure-environment.cpp +++ b/src/vcpkg/configure-environment.cpp @@ -195,7 +195,7 @@ namespace vcpkg auto temp_directory = fs.create_or_get_temp_directory(VCPKG_LINE_INFO); - auto cmd = Command{paths.get_tool_exe(Tools::NODE, out_sink)}; + auto cmd = Command{paths.get_tool_path_required(Tools::NODE)}; cmd.string_arg(*vcpkg_artifacts_path); cmd.forwarded_args(args); if (Debug::g_debugging) diff --git a/src/vcpkg/tools.cpp b/src/vcpkg/tools.cpp index a2d405f0d8..c4f7774079 100644 --- a/src/vcpkg/tools.cpp +++ b/src/vcpkg/tools.cpp @@ -2,10 +2,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -20,6 +20,8 @@ #include #include +#include + #include namespace @@ -117,26 +119,32 @@ namespace vcpkg Strings::join(", ", all_tool_oses, [](const ToolOsEntry& entry) { return entry.name; })); } - ExpectedL> parse_tool_data(StringView contents, StringView origin) + Optional> parse_tool_data(DiagnosticContext& context, + StringView contents, + StringView origin) { - return Json::parse_object(contents, origin) - .then([&](Json::Object&& as_object) -> ExpectedL> { + return Json::parse_object(context, contents, origin) + .then([&](Json::Object&& as_object) -> Optional> { Json::Reader r(origin); auto maybe_tool_data = ToolDataFileDeserializer::instance.visit(r, as_object); if (!r.messages().good()) { - return r.messages().join(); + r.messages().report(context); + return nullopt; } return maybe_tool_data.value_or_exit(VCPKG_LINE_INFO); }); } - static ExpectedL> parse_tool_data_file(const Filesystem& fs, Path path) + static Optional> parse_tool_data_file(DiagnosticContext& context, + const ReadOnlyFilesystem& fs, + Path path) { - return fs.try_read_contents(path).then([](FileContents&& fc) -> ExpectedL> { - return parse_tool_data(fc.content, Path{fc.origin}); - }); + return fs.try_read_contents(context, path) + .then([&](FileContents&& contents) -> Optional> { + return parse_tool_data(context, contents.content, contents.origin); + }); } const ToolDataEntry* get_raw_tool_data(const std::vector& tool_data_table, @@ -211,21 +219,36 @@ namespace vcpkg data->sha512}; } + static bool download_is_available(const Optional& tool_data) noexcept + { + if (auto td = tool_data.get()) + { + return !td->url.empty(); + } + + return false; + } + struct PathAndVersion { Path p; std::string version; }; - static ExpectedL run_to_extract_version(StringLiteral tool_name, const Path& exe_path, Command&& cmd) + static Optional run_to_extract_version(DiagnosticContext& context, + StringLiteral tool_name, + const Path& exe_path, + Command&& cmd) { - return flatten_out(cmd_execute_and_capture_output({std::move(cmd)}), exe_path) - .map_error([&](LocalizedString&& output) { - return msg::format_error( - msgFailedToRunToolToDetermineVersion, msg::tool_name = tool_name, msg::path = exe_path) - .append_raw('\n') - .append(output); - }); + auto maybe_exit_code_and_output = cmd_execute_and_capture_output(context, cmd); + if (auto output = check_zero_exit_code(context, cmd, maybe_exit_code_and_output)) + { + return std::move(*output); + } + + context.report(DiagnosticLine{ + DiagKind::Note, exe_path, msg::format(msgWhileDeterminingVersion, msg::tool_name = tool_name)}); + return nullopt; } // set target to the subrange [begin_idx, end_idx) @@ -238,42 +261,61 @@ namespace vcpkg target.erase(0, begin_idx); } - ExpectedL extract_prefixed_nonquote(StringLiteral prefix, - StringLiteral tool_name, - std::string&& output, - const Path& exe_path) + static void report_unexpected_tool_output(DiagnosticContext& context, + StringLiteral tool_name, + Optional& maybe_output, + const Path& exe_path) { - auto idx = output.find(prefix.data(), 0, prefix.size()); - if (idx != std::string::npos) + auto diag = msg::format(msgUnexpectedToolOutput2, msg::tool_name = tool_name); + if (auto* output = maybe_output.get()) { - idx += prefix.size(); - const auto end_idx = output.find('"', idx); - set_string_to_subrange(output, idx, end_idx); - return {std::move(output), expected_left_tag}; + diag.append_raw('\n').append_raw(std::move(*output)); + maybe_output.clear(); } - return std::move(msg::format_error(msgUnexpectedToolOutput, msg::tool_name = tool_name, msg::path = exe_path) - .append_raw('\n') - .append_raw(std::move(output))); + context.report(DiagnosticLine{DiagKind::Error, exe_path, std::move(diag)}); } - ExpectedL extract_prefixed_nonwhitespace(StringLiteral prefix, - StringLiteral tool_name, - std::string&& output, - const Path& exe_path) + void extract_prefixed_nonquote(DiagnosticContext& context, + StringLiteral prefix, + StringLiteral tool_name, + Optional& maybe_output, + const Path& exe_path) { - auto idx = output.find(prefix.data(), 0, prefix.size()); - if (idx != std::string::npos) + if (auto* output = maybe_output.get()) { - idx += prefix.size(); - const auto end_idx = output.find_first_of(" \r\n", idx, 3); - set_string_to_subrange(output, idx, end_idx); - return {std::move(output), expected_left_tag}; + auto idx = output->find(prefix.data(), 0, prefix.size()); + if (idx != std::string::npos) + { + idx += prefix.size(); + const auto end_idx = output->find('"', idx); + set_string_to_subrange(*output, idx, end_idx); + return; + } + + report_unexpected_tool_output(context, tool_name, maybe_output, exe_path); } + } - return std::move(msg::format_error(msgUnexpectedToolOutput, msg::tool_name = tool_name, msg::path = exe_path) - .append_raw('\n') - .append_raw(std::move(output))); + void extract_prefixed_nonwhitespace(DiagnosticContext& context, + StringLiteral prefix, + StringLiteral tool_name, + Optional& maybe_output, + const Path& exe_path) + { + if (auto* output = maybe_output.get()) + { + auto idx = output->find(prefix.data(), 0, prefix.size()); + if (idx != std::string::npos) + { + idx += prefix.size(); + const auto end_idx = output->find_first_of(" \r\n", idx, 3); + set_string_to_subrange(*output, idx, end_idx); + return; + } + + report_unexpected_tool_output(context, tool_name, maybe_output, exe_path); + } } struct ToolProvider @@ -289,15 +331,19 @@ namespace vcpkg // considered. virtual bool ignore_version() const { return false; } - virtual void add_system_paths(const ReadOnlyFilesystem& fs, std::vector& out_candidate_paths) const + virtual void add_system_paths(DiagnosticContext& context, + const ReadOnlyFilesystem& fs, + std::vector& out_candidate_paths) const { + (void)context; (void)fs; (void)out_candidate_paths; } - virtual ExpectedL get_version(const ToolCache& cache, - MessageSink& status_sink, - const Path& exe_path) const = 0; + virtual Optional get_version(DiagnosticContext& context, + const Filesystem& fs, + const ToolCache& cache, + const Path& exe_path) const = 0; // returns true if and only if `exe_path` is a usable version of this tool, cheap check virtual bool cheap_is_acceptable(const Path& exe_path) const @@ -330,9 +376,12 @@ namespace vcpkg virtual std::array default_min_version() const override { return {0}; } virtual bool ignore_version() const override { return true; } - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path&) const override + virtual Optional get_version(DiagnosticContext&, + const Filesystem&, + const ToolCache&, + const Path&) const override { - return {"0", expected_left_tag}; + return {"0"}; } }; @@ -344,18 +393,18 @@ namespace vcpkg virtual std::array default_min_version() const override { return {10, 29, 1}; } #if defined(_WIN32) - virtual void add_system_paths(const ReadOnlyFilesystem&, std::vector& out_candidate_paths) const override + virtual void add_system_paths(DiagnosticContext& context, + const ReadOnlyFilesystem&, + std::vector& out_candidate_paths) const override { - const auto& maybe_appdata_local = get_appdata_local(); - if (const auto appdata_local = maybe_appdata_local.get()) + if (const auto appdata_local = get_appdata_local(context)) { // as installed by WinGet out_candidate_paths.push_back(*appdata_local / "Microsoft\\WinGet\\Links\\azcopy.exe"); } // other common installation locations - const auto& maybe_system_drive = get_system_drive(); - if (auto system_drive = maybe_system_drive.get()) + if (auto system_drive = get_system_drive(context)) { // https://devdiv.visualstudio.com/XlabImageFactory/_git/XlabImageFactory?path=/artifacts/windows-azcopy-downloadfile/windows-azcopy-downloadfile.ps1&version=GBmain&_a=contents&line=54&lineStyle=plain&lineEnd=55&lineStartColumn=1&lineEndColumn=1 out_candidate_paths.emplace_back(*system_drive / "\\AzCopy10\\azcopy.exe"); @@ -365,13 +414,16 @@ namespace vcpkg } #endif - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path& exe_path) const override + virtual Optional get_version(DiagnosticContext& context, + const Filesystem&, + const ToolCache&, + const Path& exe_path) const override { // azcopy --version outputs e.g. "azcopy version 10.13.0" - return run_to_extract_version("azcopy", exe_path, Command(exe_path).string_arg("--version")) - .then([&](std::string&& output) { - return extract_prefixed_nonwhitespace("azcopy version ", "azcopy", std::move(output), exe_path); - }); + auto maybe_output = + run_to_extract_version(context, "azcopy", exe_path, Command(exe_path).string_arg("--version")); + extract_prefixed_nonwhitespace(context, "azcopy version ", "azcopy", maybe_output, exe_path); + return maybe_output; } }; @@ -383,7 +435,9 @@ namespace vcpkg virtual std::array default_min_version() const override { return {3, 17, 1}; } #if defined(_WIN32) - virtual void add_system_paths(const ReadOnlyFilesystem&, std::vector& out_candidate_paths) const override + virtual void add_system_paths(DiagnosticContext&, + const ReadOnlyFilesystem&, + std::vector& out_candidate_paths) const override { const auto& program_files = get_program_files_platform_bitness(); if (const auto pf = program_files.get()) @@ -406,19 +460,26 @@ namespace vcpkg } #endif - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path& exe_path) const override - { - return run_to_extract_version(Tools::CMAKE, exe_path, Command(exe_path).string_arg("--version")) - .then([&](std::string&& output) { - // Sample output: - // cmake version 3.10.2 - // - // CMake suite maintained and supported by Kitware (kitware.com/cmake). - - // There are two expected output formats to handle: "cmake3 version x.x.x" and "cmake version x.x.x" - Strings::inplace_replace_all(output, "cmake3", "cmake"); - return extract_prefixed_nonwhitespace("cmake version ", Tools::CMAKE, std::move(output), exe_path); - }); + virtual Optional get_version(DiagnosticContext& context, + const Filesystem&, + const ToolCache&, + const Path& exe_path) const override + { + // Sample output: + // cmake version 3.10.2 + // + // CMake suite maintained and supported by Kitware (kitware.com/cmake). + + // There are two expected output formats to handle: "cmake3 version x.x.x" and "cmake version x.x.x" + auto maybe_output = + run_to_extract_version(context, Tools::CMAKE, exe_path, Command(exe_path).string_arg("--version")); + if (auto* output = maybe_output.get()) + { + Strings::inplace_replace_all(*output, "cmake3", "cmake"); + extract_prefixed_nonwhitespace(context, "cmake version ", Tools::CMAKE, maybe_output, exe_path); + } + + return maybe_output; } }; @@ -429,17 +490,22 @@ namespace vcpkg virtual std::vector system_exe_stems() const override { return {Tools::NINJA}; } virtual std::array default_min_version() const override { return {3, 5, 1}; } #if !defined(_WIN32) - virtual void add_system_paths(const ReadOnlyFilesystem&, std::vector& out_candidate_paths) const override + virtual void add_system_paths(DiagnosticContext&, + const ReadOnlyFilesystem&, + std::vector& out_candidate_paths) const override { // This is where Ninja goes by default on Alpine: https://github.com/microsoft/vcpkg/issues/21218 out_candidate_paths.emplace_back("/usr/lib/ninja-build/bin"); } #endif // ^^^ !defined(_WIN32) - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path& exe_path) const override + virtual Optional get_version(DiagnosticContext& context, + const Filesystem&, + const ToolCache&, + const Path& exe_path) const override { // Sample output: 1.8.2 - return run_to_extract_version(Tools::NINJA, exe_path, Command(exe_path).string_arg("--version")); + return run_to_extract_version(context, Tools::NINJA, exe_path, Command(exe_path).string_arg("--version")); } }; @@ -450,31 +516,43 @@ namespace vcpkg virtual std::vector system_exe_stems() const override { return {Tools::NUGET}; } virtual std::array default_min_version() const override { return {4, 6, 2}; } - virtual ExpectedL get_version(const ToolCache& cache, - MessageSink& status_sink, - const Path& exe_path) const override + virtual Optional get_version(DiagnosticContext& context, + const Filesystem& fs, + const ToolCache& cache, + const Path& exe_path) const override { - (void)cache; - (void)status_sink; Command cmd; -#if !defined(_WIN32) - cmd.string_arg(cache.get_tool_path(Tools::MONO, status_sink)); +#if defined(_WIN32) + (void)fs; + (void)cache; +#else + const auto* mono_path = cache.get_tool_path(context, fs, Tools::MONO); + if (!mono_path) + { + context.report(DiagnosticLine{DiagKind::Note, msg::format(msgMonoInstructions)}); + return nullopt; + } + + cmd.string_arg(*mono_path); #endif // ^^^ !_WIN32 cmd.string_arg(exe_path).string_arg("help").string_arg("-ForceEnglishOutput"); - return run_to_extract_version(Tools::NUGET, exe_path, std::move(cmd)) + auto maybe_output = run_to_extract_version(context, Tools::NUGET, exe_path, std::move(cmd)); + if (maybe_output) + { + // Sample output: + // NuGet Version: 4.6.2.5055 + // usage: NuGet [args] [options] + // Type 'NuGet help ' for help on a specific command. + extract_prefixed_nonwhitespace(context, "NuGet Version: ", Tools::NUGET, maybe_output, exe_path); + } #if !defined(_WIN32) - .map_error([](LocalizedString&& error) { - return std::move(error.append_raw('\n').append(msgMonoInstructions)); - }) + else + { + context.report(DiagnosticLine{DiagKind::Note, msg::format(msgMonoInstructions)}); + } #endif // ^^^ !_WIN32 - .then([&](std::string&& output) { - // Sample output: - // NuGet Version: 4.6.2.5055 - // usage: NuGet [args] [options] - // Type 'NuGet help ' for help on a specific command. - return extract_prefixed_nonwhitespace("NuGet Version: ", Tools::NUGET, std::move(output), exe_path); - }); + return maybe_output; } }; @@ -486,7 +564,9 @@ namespace vcpkg virtual std::array default_min_version() const override { return {16, 12, 0}; } #if defined(_WIN32) - virtual void add_system_paths(const ReadOnlyFilesystem&, std::vector& out_candidate_paths) const override + virtual void add_system_paths(DiagnosticContext&, + const ReadOnlyFilesystem&, + std::vector& out_candidate_paths) const override { const auto& program_files = get_program_files_platform_bitness(); if (const auto pf = program_files.get()) out_candidate_paths.push_back(*pf / "nodejs" / "node.exe"); @@ -495,13 +575,16 @@ namespace vcpkg } #endif - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path& exe_path) const override + virtual Optional get_version(DiagnosticContext& context, + const Filesystem&, + const ToolCache&, + const Path& exe_path) const override { - return run_to_extract_version(Tools::NODE, exe_path, Command(exe_path).string_arg("--version")) - .then([&](std::string&& output) { - // Sample output: v16.12.0 - return extract_prefixed_nonwhitespace("v", Tools::NODE, std::move(output), exe_path); - }); + // Sample output: v16.12.0 + auto maybe_output = + run_to_extract_version(context, Tools::NODE, exe_path, Command(exe_path).string_arg("--version")); + extract_prefixed_nonwhitespace(context, "v", Tools::NODE, maybe_output, exe_path); + return maybe_output; } }; @@ -513,7 +596,9 @@ namespace vcpkg virtual std::array default_min_version() const override { return {2, 7, 4}; } #if defined(_WIN32) - virtual void add_system_paths(const ReadOnlyFilesystem&, std::vector& out_candidate_paths) const override + virtual void add_system_paths(DiagnosticContext&, + const ReadOnlyFilesystem&, + std::vector& out_candidate_paths) const override { const auto& program_files = get_program_files_platform_bitness(); if (const auto pf = program_files.get()) out_candidate_paths.push_back(*pf / "git" / "cmd" / "git.exe"); @@ -525,13 +610,16 @@ namespace vcpkg } #endif - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path& exe_path) const override + virtual Optional get_version(DiagnosticContext& context, + const Filesystem&, + const ToolCache&, + const Path& exe_path) const override { - return run_to_extract_version(Tools::GIT, exe_path, Command(exe_path).string_arg("--version")) - .then([&](std::string&& output) { - // Sample output: git version 2.17.1.windows.2 - return extract_prefixed_nonwhitespace("git version ", Tools::GIT, std::move(output), exe_path); - }); + // Sample output: git version 2.17.1.windows.2 + auto maybe_output = + run_to_extract_version(context, Tools::GIT, exe_path, Command(exe_path).string_arg("--version")); + extract_prefixed_nonwhitespace(context, "git version ", Tools::GIT, maybe_output, exe_path); + return maybe_output; } }; @@ -542,15 +630,17 @@ namespace vcpkg virtual std::vector system_exe_stems() const override { return {Tools::MONO}; } virtual std::array default_min_version() const override { return {0, 0, 0}; } - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path& exe_path) const override + virtual Optional get_version(DiagnosticContext& context, + const Filesystem&, + const ToolCache&, + const Path& exe_path) const override { - return run_to_extract_version(Tools::MONO, exe_path, Command(exe_path).string_arg("--version")) - .then([&](std::string&& output) { - // Sample output: - // Mono JIT compiler version 6.8.0.105 (Debian 6.8.0.105+dfsg-2 Wed Feb 26 23:23:50 UTC 2020) - return extract_prefixed_nonwhitespace( - "Mono JIT compiler version ", Tools::MONO, std::move(output), exe_path); - }); + // Sample output: + // Mono JIT compiler version 6.8.0.105 (Debian 6.8.0.105+dfsg-2 Wed Feb 26 23:23:50 UTC 2020) + auto maybe_output = + run_to_extract_version(context, Tools::MONO, exe_path, Command(exe_path).string_arg("--version")); + extract_prefixed_nonwhitespace(context, "Mono JIT compiler version ", Tools::MONO, maybe_output, exe_path); + return maybe_output; } virtual void add_system_package_info(LocalizedString& out) const override @@ -573,14 +663,16 @@ namespace vcpkg virtual std::vector system_exe_stems() const override { return {Tools::GSUTIL}; } virtual std::array default_min_version() const override { return {4, 56, 0}; } - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path& exe_path) const override + virtual Optional get_version(DiagnosticContext& context, + const Filesystem&, + const ToolCache&, + const Path& exe_path) const override { - return run_to_extract_version(Tools::GSUTIL, exe_path, Command(exe_path).string_arg("version")) - .then([&](std::string&& output) { - // Sample output: gsutil version: 4.58 - return extract_prefixed_nonwhitespace( - "gsutil version: ", Tools::GSUTIL, std::move(output), exe_path); - }); + // Sample output: gsutil version: 4.58 + auto maybe_output = + run_to_extract_version(context, Tools::GSUTIL, exe_path, Command(exe_path).string_arg("version")); + extract_prefixed_nonwhitespace(context, "gsutil version: ", Tools::GSUTIL, maybe_output, exe_path); + return maybe_output; } }; @@ -591,13 +683,16 @@ namespace vcpkg virtual std::vector system_exe_stems() const override { return {Tools::AWSCLI}; } virtual std::array default_min_version() const override { return {2, 4, 4}; } - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path& exe_path) const override + virtual Optional get_version(DiagnosticContext& context, + const Filesystem&, + const ToolCache&, + const Path& exe_path) const override { - return run_to_extract_version(Tools::AWSCLI, exe_path, Command(exe_path).string_arg("--version")) - .then([&](std::string&& output) { - // Sample output: aws-cli/2.4.4 Python/3.8.8 Windows/10 exe/AMD64 prompt/off - return extract_prefixed_nonwhitespace("aws-cli/", Tools::AWSCLI, std::move(output), exe_path); - }); + // Sample output: aws-cli/2.4.4 Python/3.8.8 Windows/10 exe/AMD64 prompt/off + auto maybe_output = + run_to_extract_version(context, Tools::AWSCLI, exe_path, Command(exe_path).string_arg("--version")); + extract_prefixed_nonwhitespace(context, "aws-cli/", Tools::AWSCLI, maybe_output, exe_path); + return maybe_output; } }; @@ -608,21 +703,23 @@ namespace vcpkg virtual std::vector system_exe_stems() const override { return {Tools::AZCLI}; } virtual std::array default_min_version() const override { return {2, 64, 0}; } - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path& exe_path) const override - { - return run_to_extract_version( - Tools::AZCLI, - exe_path, - Command(exe_path).string_arg("version").string_arg("--output").string_arg("json")) - .then([&](std::string&& output) { - // { - // ... - // "azure-cli": "2.64.0", - // ... - // } - - return extract_prefixed_nonquote("\"azure-cli\": \"", Tools::AZCLI, std::move(output), exe_path); - }); + virtual Optional get_version(DiagnosticContext& context, + const Filesystem&, + const ToolCache&, + const Path& exe_path) const override + { + // { + // ... + // "azure-cli": "2.64.0", + // ... + // } + auto maybe_output = run_to_extract_version( + context, + Tools::AZCLI, + exe_path, + Command(exe_path).string_arg("version").string_arg("--output").string_arg("json")); + extract_prefixed_nonquote(context, "\"azure-cli\": \"", Tools::AZCLI, maybe_output, exe_path); + return maybe_output; } }; @@ -633,14 +730,16 @@ namespace vcpkg virtual std::vector system_exe_stems() const override { return {"cos"}; } virtual std::array default_min_version() const override { return {0, 11, 0}; } - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path& exe_path) const override + virtual Optional get_version(DiagnosticContext& context, + const Filesystem&, + const ToolCache&, + const Path& exe_path) const override { - return run_to_extract_version(Tools::COSCLI, exe_path, Command(exe_path).string_arg("--version")) - .then([&](std::string&& output) { - // Sample output: coscli version v0.11.0-beta - return extract_prefixed_nonwhitespace( - "coscli version v", Tools::COSCLI, std::move(output), exe_path); - }); + // Sample output: coscli version v0.11.0-beta + auto maybe_output = + run_to_extract_version(context, Tools::COSCLI, exe_path, Command(exe_path).string_arg("--version")); + extract_prefixed_nonwhitespace(context, "coscli version v", Tools::COSCLI, maybe_output, exe_path); + return maybe_output; } }; @@ -659,14 +758,16 @@ namespace vcpkg virtual std::vector system_exe_stems() const override { return {"pwsh"}; } virtual std::array default_min_version() const override { return {7, 0, 3}; } - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path& exe_path) const override + virtual Optional get_version(DiagnosticContext& context, + const Filesystem&, + const ToolCache&, + const Path& exe_path) const override { - return run_to_extract_version(Tools::POWERSHELL_CORE, exe_path, Command(exe_path).string_arg("--version")) - .then([&](std::string&& output) { - // Sample output: PowerShell 7.0.3\r\n - return extract_prefixed_nonwhitespace( - "PowerShell ", Tools::POWERSHELL_CORE, std::move(output), exe_path); - }); + // Sample output: PowerShell 7.0.3\r\n + auto maybe_output = run_to_extract_version( + context, Tools::POWERSHELL_CORE, exe_path, Command(exe_path).string_arg("--version")); + extract_prefixed_nonwhitespace(context, "PowerShell ", Tools::POWERSHELL_CORE, maybe_output, exe_path); + return maybe_output; } }; @@ -678,45 +779,52 @@ namespace vcpkg virtual std::array default_min_version() const override { return {3, 5, 0}; } // 3.5 added -m venv #if defined(_WIN32) - void add_system_paths_impl(const ReadOnlyFilesystem& fs, + void add_system_paths_impl(DiagnosticContext& context, + const ReadOnlyFilesystem& fs, std::vector& out_candidate_paths, const Path& program_files_root) const { - for (auto&& candidate : fs.get_directories_non_recursive(program_files_root, VCPKG_LINE_INFO)) + auto maybe_candidates = fs.try_get_directories_non_recursive(context, program_files_root); + if (auto candidates = maybe_candidates.get()) { - auto name = candidate.filename(); - if (Strings::case_insensitive_ascii_starts_with(name, "Python") && - std::all_of(name.begin() + 6, name.end(), ParserBase::is_ascii_digit)) + for (auto&& candidate : *candidates) { - out_candidate_paths.emplace_back(std::move(candidate)); + auto name = candidate.filename(); + if (Strings::case_insensitive_ascii_starts_with(name, "Python") && + std::all_of(name.begin() + 6, name.end(), ParserBase::is_ascii_digit)) + { + out_candidate_paths.emplace_back(std::move(candidate)); + } } } } - virtual void add_system_paths(const ReadOnlyFilesystem& fs, + virtual void add_system_paths(DiagnosticContext& context, + const ReadOnlyFilesystem& fs, std::vector& out_candidate_paths) const override { - const auto& program_files = get_program_files_platform_bitness(); - if (const auto pf = program_files.get()) + if (const auto pf = get_program_files_platform_bitness().get()) { - add_system_paths_impl(fs, out_candidate_paths, *pf); + add_system_paths_impl(context, fs, out_candidate_paths, *pf); } - const auto& program_files_32_bit = get_program_files_32_bit(); - if (const auto pf = program_files_32_bit.get()) + if (const auto pf = get_program_files_32_bit().get()) { - add_system_paths_impl(fs, out_candidate_paths, *pf); + add_system_paths_impl(context, fs, out_candidate_paths, *pf); } } #endif // ^^^ _WIN32 - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path& exe_path) const override + virtual Optional get_version(DiagnosticContext& context, + const Filesystem&, + const ToolCache&, + const Path& exe_path) const override { - return run_to_extract_version(Tools::PYTHON3, exe_path, Command(exe_path).string_arg("--version")) - .then([&](std::string&& output) { - // Sample output: Python 3.10.2\r\n - return extract_prefixed_nonwhitespace("Python ", Tools::PYTHON3, std::move(output), exe_path); - }); + // Sample output: Python 3.10.2\r\n + auto maybe_output = + run_to_extract_version(context, Tools::PYTHON3, exe_path, Command(exe_path).string_arg("--version")); + extract_prefixed_nonwhitespace(context, "Python ", Tools::PYTHON3, maybe_output, exe_path); + return maybe_output; } virtual void add_system_package_info(LocalizedString& out) const override @@ -760,7 +868,9 @@ namespace vcpkg virtual std::array default_min_version() const override { return {24, 9}; } #if defined(_WIN32) - virtual void add_system_paths(const ReadOnlyFilesystem&, std::vector& out_candidate_paths) const override + virtual void add_system_paths(DiagnosticContext&, + const ReadOnlyFilesystem&, + std::vector& out_candidate_paths) const override { const auto& program_files = get_program_files_platform_bitness(); if (const auto pf = program_files.get()) @@ -776,36 +886,35 @@ namespace vcpkg } #endif - virtual ExpectedL get_version(const ToolCache&, MessageSink&, const Path& exe_path) const override + virtual Optional get_version(DiagnosticContext& context, + const Filesystem&, + const ToolCache&, + const Path& exe_path) const override { - return run_to_extract_version(Tools::SEVEN_ZIP, exe_path, Command(exe_path)) - .then([&](std::string&& output) { - // Sample output: 7-Zip 24.09 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-11-29 - return extract_prefixed_nonwhitespace("7-Zip ", Tools::SEVEN_ZIP, std::move(output), exe_path); - }); + // Sample output: 7-Zip 24.09 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-11-29 + auto maybe_output = run_to_extract_version(context, Tools::SEVEN_ZIP, exe_path, Command(exe_path)); + extract_prefixed_nonwhitespace(context, "7-Zip ", Tools::SEVEN_ZIP, maybe_output, exe_path); + return maybe_output; } }; struct ToolCacheImpl final : ToolCache { - const Filesystem& fs; AssetCachingSettings asset_cache_settings; const Path downloads; const Path config_path; const Path tools; const RequireExactVersions abiToolVersionHandling; - vcpkg::Cache path_version_cache; - vcpkg::Lazy> m_tool_data_cache; + ContextCache path_version_cache; + mutable Optional, std::vector>> m_tool_data_cache; - ToolCacheImpl(const Filesystem& fs, - const AssetCachingSettings& asset_cache_settings, + ToolCacheImpl(const AssetCachingSettings& asset_cache_settings, Path downloads, Path config_path, Path tools, RequireExactVersions abiToolVersionHandling) - : fs(fs) - , asset_cache_settings(asset_cache_settings) + : asset_cache_settings(asset_cache_settings) , downloads(std::move(downloads)) , config_path(std::move(config_path)) , tools(std::move(tools)) @@ -819,7 +928,8 @@ namespace vcpkg * existing candidate. */ template - Optional find_first_with_sufficient_version(MessageSink& status_sink, + Optional find_first_with_sufficient_version(DiagnosticContext& context, + const Filesystem& fs, const ToolProvider& tool_provider, const std::vector& candidates, Func&& accept_version, @@ -829,15 +939,23 @@ namespace vcpkg { if (!fs.exists(candidate, IgnoreErrors{})) continue; if (!tool_provider.cheap_is_acceptable(candidate)) continue; - auto maybe_version = tool_provider.get_version(*this, status_sink, candidate); - log_candidate(candidate, maybe_version); + AttemptDiagnosticContext adc{context}; + auto maybe_version = tool_provider.get_version(adc, fs, *this, candidate); const auto version = maybe_version.get(); - if (!version) continue; + if (!version) + { + log_candidate(candidate, adc.to_string()); + adc.handle(); + continue; + } + + log_candidate(candidate, *version); const auto parsed_version = parse_tool_version_string(*version); if (!parsed_version) continue; auto& actual_version = *parsed_version.get(); if (!accept_version(actual_version)) continue; if (!tool_provider.is_acceptable(candidate)) continue; + adc.commit(); return PathAndVersion{candidate, *version}; } @@ -845,42 +963,43 @@ namespace vcpkg return nullopt; } - Path download_tool(const ToolData& tool_data, MessageSink& status_sink) const + Optional download_tool(DiagnosticContext& context, const Filesystem& fs, const ToolData& tool_data) const { using namespace Hash; const std::array& version = tool_data.version; const std::string version_as_string = fmt::format("{}.{}.{}", version[0], version[1], version[2]); - Checks::msg_check_exit(VCPKG_LINE_INFO, - !tool_data.url.empty(), - msgToolOfVersionXNotFound, - msg::tool_name = tool_data.name, - msg::version = version_as_string); - status_sink.println(Color::none, - msgDownloadingPortableToolVersionX, - msg::tool_name = tool_data.name, - msg::version = version_as_string); + if (tool_data.url.empty()) + { + context.report(DiagnosticLine{DiagKind::Error, + config_path, + msg::format(msgToolOfVersionXNotFound, + msg::tool_name = tool_data.name, + msg::version = version_as_string)}); + return nullopt; + } + context.statusln(msg::format( + msgDownloadingPortableToolVersionX, msg::tool_name = tool_data.name, msg::version = version_as_string)); const auto download_path = downloads / tool_data.download_subpath; - const auto hash_result = get_file_hash(console_diagnostic_context, fs, download_path, Algorithm::Sha512); + const auto hash_result = get_file_hash(context, fs, download_path, Algorithm::Sha512); switch (hash_result.prognosis) { case HashPrognosis::Success: if (!Strings::case_insensitive_ascii_equals(tool_data.sha512, hash_result.hash)) { - Checks::msg_exit_with_message(VCPKG_LINE_INFO, - LocalizedString::from_raw(download_path) - .append_raw(": ") - .append_raw(ErrorPrefix) - .append(msgToolHashMismatch, + context.report(DiagnosticLine{DiagKind::Error, + download_path, + msg::format(msgToolHashMismatch, msg::tool_name = tool_data.name, msg::expected = tool_data.sha512, - msg::actual = hash_result.hash)); + msg::actual = hash_result.hash)}); + return nullopt; } break; case HashPrognosis::FileNotFound: - if (!download_file_asset_cached(console_diagnostic_context, + if (!download_file_asset_cached(context, null_sink, asset_cache_settings, fs, @@ -890,10 +1009,10 @@ namespace vcpkg tool_data.download_subpath, tool_data.sha512)) { - Checks::exit_fail(VCPKG_LINE_INFO); + return nullopt; } break; - case HashPrognosis::OtherError: Checks::exit_fail(VCPKG_LINE_INFO); + case HashPrognosis::OtherError: return nullopt; default: Checks::unreachable(VCPKG_LINE_INFO); } @@ -902,38 +1021,68 @@ namespace vcpkg if (tool_data.is_archive) { - status_sink.println(Color::none, msgExtractingTool, msg::tool_name = tool_data.name); - Path to_path_partial = - extract_archive_to_temp_subdirectory(fs, *this, status_sink, download_path, tool_dir_path); - fs.rename_or_delete(to_path_partial, tool_dir_path, IgnoreErrors{}); - } - else - { - fs.create_directories(exe_path.parent_path(), IgnoreErrors{}); - fs.rename_or_delete(download_path, exe_path, IgnoreErrors{}); - } + context.statusln(msg::format(msgExtractingTool, msg::tool_name = tool_data.name)); + auto maybe_partial_path = + extract_archive_to_temp_subdirectory(context, fs, *this, download_path, tool_dir_path); + if (auto partial_path = maybe_partial_path.get()) + { + if (!fs.rename_or_delete(context, *partial_path, tool_dir_path)) + { + return nullopt; + } - if (!fs.exists(exe_path, IgnoreErrors{})) + if (!fs.exists(exe_path, IgnoreErrors{})) + { + context.report(DiagnosticLine{ + DiagKind::Error, + exe_path, + msg::format(msgExpectedPathToExistAfterExtractingTool, msg::tool_name = tool_data.name)}); + context.report(DiagnosticLine{DiagKind::Note, tool_dir_path, msg::format(msgExtractedHere)}); + return nullopt; + } + } + else + { + return nullopt; + } + } + else if (!fs.create_directories(context, exe_path.parent_path()) || + !fs.rename_or_delete(context, download_path, exe_path)) { - Checks::msg_exit_with_error(VCPKG_LINE_INFO, msgExpectedPathToExist, msg::path = exe_path); + return nullopt; } return exe_path; } - virtual const Path& get_tool_path(StringView tool, MessageSink& status_sink) const override + virtual const Path* get_tool_path(DiagnosticContext& context, + const Filesystem& fs, + StringView tool) const override { - return get_tool_pathversion(tool, status_sink).p; + if (auto p = get_tool_pathversion(context, fs, tool)) + { + return &p->p; + } + + return nullptr; } - PathAndVersion get_path(const ToolProvider& tool, MessageSink& status_sink) const + Optional get_path(DiagnosticContext& context, + const Filesystem& fs, + const ToolProvider& tool) const { const bool env_force_system_binaries = get_environment_variable(EnvironmentVariableVcpkgForceSystemBinaries).has_value(); const bool env_force_download_binaries = get_environment_variable(EnvironmentVariableVcpkgForceDownloadedBinaries).has_value(); - const auto maybe_tool_data = get_tool_data(load_tool_data(), tool.tool_data_name()); - const bool download_available = maybe_tool_data.has_value() && !maybe_tool_data.get()->url.empty(); + auto maybe_all_tool_data = load_tool_data(context, fs); + if (!maybe_all_tool_data) + { + return nullopt; + } + + const auto maybe_tool_data = get_tool_data(*maybe_all_tool_data, tool.tool_data_name()); + const bool download_available = download_is_available(maybe_tool_data); // search for system searchable tools unless forcing downloads and download available const auto system_exe_stems = tool.system_exe_stems(); const bool consider_system = @@ -966,7 +1115,7 @@ namespace vcpkg // (e.g Program Files). auto paths_from_path = fs.find_from_PATH(system_exe_stems); candidate_paths.insert(candidate_paths.end(), paths_from_path.cbegin(), paths_from_path.cend()); - tool.add_system_paths(fs, candidate_paths); + tool.add_system_paths(context, fs, candidate_paths); } std::string considered_versions; @@ -975,13 +1124,13 @@ namespace vcpkg // If we are forcing the system copy (and therefore ignoring versions), take the first entry that // is acceptable. const auto it = - std::find_if(candidate_paths.begin(), candidate_paths.end(), [this, &tool](const Path& p) { - return this->fs.is_regular_file(p) && tool.is_acceptable(p); + std::find_if(candidate_paths.begin(), candidate_paths.end(), [&fs, &tool](const Path& p) { + return fs.is_regular_file(p) && tool.is_acceptable(p); }); if (it != candidate_paths.end()) { - return {*it, "0"}; + return PathAndVersion{std::move(*it), "0"}; } } else @@ -989,7 +1138,8 @@ namespace vcpkg // Otherwise, execute each entry and compare its version against the constraint. Take the first that // matches. const auto maybe_path = find_first_with_sufficient_version( - status_sink, + context, + fs, tool, candidate_paths, [&min_version, exact_version](const std::array& actual_version) { @@ -1003,11 +1153,8 @@ namespace vcpkg (actual_version[0] == min_version[0] && actual_version[1] == min_version[1] && actual_version[2] >= min_version[2]); }, - [&](const auto& path, const ExpectedL& maybe_version) { - considered_versions += fmt::format("{}: {}\n", - path, - maybe_version.has_value() ? *maybe_version.get() - : maybe_version.error().data()); + [&](const Path& path, const std::string& version) { + considered_versions += fmt::format("{}: {}\n", path, version); }); if (const auto p = maybe_path.get()) { @@ -1020,10 +1167,23 @@ namespace vcpkg // If none of the current entries are acceptable, fall back to downloading if possible if (auto tool_data = maybe_tool_data.get()) { - auto downloaded_path = download_tool(*tool_data, status_sink); - auto downloaded_version = - tool.get_version(*this, status_sink, downloaded_path).value_or_exit(VCPKG_LINE_INFO); - return {std::move(downloaded_path), std::move(downloaded_version)}; + auto maybe_downloaded_path = download_tool(context, fs, *tool_data); + if (auto downloaded_path = maybe_downloaded_path.get()) + { + auto maybe_downloaded_version = tool.get_version(context, fs, *this, *downloaded_path); + if (auto downloaded_version = maybe_downloaded_version.get()) + { + return PathAndVersion{std::move(*downloaded_path), std::move(*downloaded_version)}; + } + else + { + return nullopt; + } + } + else + { + return nullopt; + } } } @@ -1050,69 +1210,121 @@ namespace vcpkg .append_raw('\n') .append_raw(considered_versions); } - Checks::msg_exit_with_message(VCPKG_LINE_INFO, s); - } - - const PathAndVersion& get_tool_pathversion(StringView tool, MessageSink& status_sink) const - { - return path_version_cache.get_lazy(tool, [&]() -> PathAndVersion { - // First deal with specially handled tools. - // For these we may look in locations like Program Files, the PATH etc as well as the auto-downloaded - // location. - if (tool == Tools::CMAKE) return get_path(CMakeProvider(), status_sink); - if (tool == Tools::GIT) return get_path(GitProvider(), status_sink); - if (tool == Tools::NINJA) return get_path(NinjaProvider(), status_sink); - if (tool == Tools::POWERSHELL_CORE) return get_path(PowerShellCoreProvider(), status_sink); - if (tool == Tools::NUGET) return get_path(NuGetProvider(), status_sink); - if (tool == Tools::NODE) return get_path(NodeProvider(), status_sink); - if (tool == Tools::MONO) return get_path(MonoProvider(), status_sink); - if (tool == Tools::GSUTIL) return get_path(GsutilProvider(), status_sink); - if (tool == Tools::AWSCLI) return get_path(AwsCliProvider(), status_sink); - if (tool == Tools::AZCOPY) return get_path(AzCopyProvider(), status_sink); - if (tool == Tools::AZCLI) return get_path(AzCliProvider(), status_sink); - if (tool == Tools::COSCLI) return get_path(CosCliProvider(), status_sink); - if (tool == Tools::PYTHON3) return get_path(Python3Provider(), status_sink); - if (tool == Tools::PYTHON3_WITH_VENV) return get_path(Python3WithVEnvProvider(), status_sink); - if (tool == Tools::SEVEN_ZIP || tool == Tools::SEVEN_ZIP_ALT) - { - return get_path(SevenZipProvider(), status_sink); - } - if (tool == Tools::TAR) - { - return {find_system_tar(fs).value_or_exit(VCPKG_LINE_INFO), {}}; - } - if (tool == Tools::CMAKE_SYSTEM) - { - return {find_system_cmake(fs).value_or_exit(VCPKG_LINE_INFO), {}}; - } - GenericToolProvider provider{tool}; - return get_path(provider, status_sink); - }); + + context.report_error(std::move(s)); + return nullopt; } - virtual const std::string& get_tool_version(StringView tool, MessageSink& status_sink) const override + const PathAndVersion* get_tool_pathversion(DiagnosticContext& context, + const Filesystem& fs, + StringView tool) const + { + return path_version_cache.get_lazy( + context, tool, [this, &fs, &tool](DiagnosticContext& inner_context) -> Optional { + // First deal with specially handled tools. + // For these we may look in locations like Program Files, the PATH etc as well as the + // auto-downloaded location. + if (tool == Tools::CMAKE) return get_path(inner_context, fs, CMakeProvider()); + if (tool == Tools::GIT) return get_path(inner_context, fs, GitProvider()); + if (tool == Tools::NINJA) return get_path(inner_context, fs, NinjaProvider()); + if (tool == Tools::POWERSHELL_CORE) return get_path(inner_context, fs, PowerShellCoreProvider()); + if (tool == Tools::NUGET) return get_path(inner_context, fs, NuGetProvider()); + if (tool == Tools::NODE) return get_path(inner_context, fs, NodeProvider()); + if (tool == Tools::MONO) return get_path(inner_context, fs, MonoProvider()); + if (tool == Tools::GSUTIL) return get_path(inner_context, fs, GsutilProvider()); + if (tool == Tools::AWSCLI) return get_path(inner_context, fs, AwsCliProvider()); + if (tool == Tools::AZCOPY) return get_path(inner_context, fs, AzCopyProvider()); + if (tool == Tools::AZCLI) return get_path(inner_context, fs, AzCliProvider()); + if (tool == Tools::COSCLI) return get_path(inner_context, fs, CosCliProvider()); + if (tool == Tools::PYTHON3) return get_path(inner_context, fs, Python3Provider()); + if (tool == Tools::PYTHON3_WITH_VENV) + { + return get_path(inner_context, fs, Python3WithVEnvProvider()); + } + + if (tool == Tools::SEVEN_ZIP || tool == Tools::SEVEN_ZIP_ALT) + { + return get_path(inner_context, fs, SevenZipProvider()); + } + + if (tool == Tools::TAR) + { + auto maybe_system_tar = find_system_tar(inner_context, fs); + if (auto system_tar = maybe_system_tar.get()) + { + return PathAndVersion{std::move(*system_tar), {}}; + } + + return nullopt; + } + + if (tool == Tools::CMAKE_SYSTEM) + { + auto maybe_system_cmake = find_system_cmake(inner_context, fs); + if (auto system_cmake = maybe_system_cmake.get()) + { + return PathAndVersion{std::move(*system_cmake), {}}; + } + + return nullopt; + } + + GenericToolProvider provider{tool}; + return get_path(inner_context, fs, provider); + }); + } + + virtual const std::string* get_tool_version(DiagnosticContext& context, + const Filesystem& fs, + StringView tool) const override { - return get_tool_pathversion(tool, status_sink).version; + if (auto p = get_tool_pathversion(context, fs, tool)) + { + return &p->version; + } + + return nullptr; } - std::vector load_tool_data() const + const ExpectedT, std::vector>& load_tool_data_impl( + DiagnosticContext& context, const ReadOnlyFilesystem& fs) const { - return m_tool_data_cache.get_lazy([&]() { - auto maybe_tool_data = parse_tool_data_file(fs, config_path); - if (auto tool_data = maybe_tool_data.get()) - { - return std::move(*tool_data); - } - Checks::msg_exit_with_error(VCPKG_LINE_INFO, maybe_tool_data.error()); - }); + if (auto existing = m_tool_data_cache.get()) + { + return *existing; + } + + ContextBufferedDiagnosticContext cbdc{context}; + auto maybe_tool_data = parse_tool_data_file(cbdc, fs, config_path); + if (auto success = maybe_tool_data.get()) + { + return m_tool_data_cache.emplace(std::move(*success)); + } + + return m_tool_data_cache.emplace(std::move(cbdc).lines); + } + + const std::vector* load_tool_data(DiagnosticContext& context, const ReadOnlyFilesystem& fs) const + { + const auto& loaded_data = load_tool_data_impl(context, fs); + if (auto success = loaded_data.get()) + { + return success; + } + + for (auto&& diag : loaded_data.error()) + { + context.report(diag); + } + + return nullptr; } }; - ExpectedL find_system_tar(const ReadOnlyFilesystem& fs) + Optional find_system_tar(DiagnosticContext& context, const ReadOnlyFilesystem& fs) { #if defined(_WIN32) - const auto& maybe_system32 = get_system32(); - if (auto system32 = maybe_system32.get()) + if (auto system32 = get_system32(context)) { auto shipped_with_windows = *system32 / "tar.exe"; if (fs.is_regular_file(shipped_with_windows)) @@ -1120,30 +1332,25 @@ namespace vcpkg return shipped_with_windows; } } - else - { - return maybe_system32.error(); - } #endif // ^^^ _WIN32 const auto tools = fs.find_from_PATH(Tools::TAR); - if (tools.empty()) + if (!tools.empty()) { - return msg::format_error(msgToolFetchFailed, msg::tool_name = Tools::TAR) + return tools[0]; + } + + context.report_error(msg::format(msgToolFetchFailed, msg::tool_name = Tools::TAR) #if defined(_WIN32) - .append(msgToolInWin10) + .append(msgToolInWin10) #else - .append(msgInstallWithSystemManager) + .append(msgInstallWithSystemManager) #endif - ; - } - else - { - return tools[0]; - } + ); + return nullopt; } - ExpectedL find_system_cmake(const ReadOnlyFilesystem& fs) + Optional find_system_cmake(DiagnosticContext& context, const ReadOnlyFilesystem& fs) { auto tools = fs.find_from_PATH(Tools::CMAKE); if (!tools.empty()) @@ -1168,22 +1375,24 @@ namespace vcpkg } #endif - return msg::format_error(msgToolFetchFailed, msg::tool_name = Tools::CMAKE) + auto error_text = msg::format(msgToolFetchFailed, msg::tool_name = Tools::CMAKE) #if !defined(_WIN32) - .append(msgInstallWithSystemManager) + .append(msgInstallWithSystemManager) #endif ; + + context.report_error(std::move(error_text)); + return nullopt; } - std::unique_ptr get_tool_cache(const Filesystem& fs, - const AssetCachingSettings& asset_cache_settings, + std::unique_ptr get_tool_cache(const AssetCachingSettings& asset_cache_settings, Path downloads, Path config_path, Path tools, RequireExactVersions abiToolVersionHandling) { return std::make_unique( - fs, asset_cache_settings, downloads, config_path, tools, abiToolVersionHandling); + asset_cache_settings, downloads, config_path, tools, abiToolVersionHandling); } struct ToolDataEntryDeserializer final : Json::IDeserializer diff --git a/src/vcpkg/vcpkgpaths.cpp b/src/vcpkg/vcpkgpaths.cpp index f641094206..a236934f50 100644 --- a/src/vcpkg/vcpkgpaths.cpp +++ b/src/vcpkg/vcpkgpaths.cpp @@ -577,7 +577,6 @@ namespace vcpkg "pkgs", VCPKG_LINE_INFO)) , m_tool_cache(get_tool_cache( - fs, m_asset_cache_settings, downloads, args.tools_data_file.has_value() ? Path{*args.tools_data_file.get()} : scripts / "vcpkg-tools.json", @@ -600,32 +599,33 @@ namespace vcpkg Debug::print("Using manifest-root: ", m_manifest_dir, '\n'); if (!args.do_not_take_lock) { - std::error_code ec; + const bool allow_errors = args.ignore_lock_failures.value_or(false); + DiagnosticContext* lock_taking_context = &stderr_diagnostic_context; + WarningDiagnosticContext wdc{stderr_diagnostic_context}; + if (allow_errors) + { + lock_taking_context = &wdc; + } + const auto vcpkg_root_file = root / ".vcpkg-root"; if (args.wait_for_lock.value_or(false)) { - file_lock_handle = fs.take_exclusive_file_lock(vcpkg_root_file, stderr_sink, ec); + file_lock_handle = fs.take_exclusive_file_lock(*lock_taking_context, vcpkg_root_file); } else { - file_lock_handle = fs.try_take_exclusive_file_lock(vcpkg_root_file, stderr_sink, ec); + auto maybe_file_lock_handle = + fs.try_take_exclusive_file_lock(*lock_taking_context, vcpkg_root_file); + if (auto got_handle = maybe_file_lock_handle.get()) + { + file_lock_handle = std::move(*got_handle); + } } - if (ec) + if (!file_lock_handle && !allow_errors) { - bool is_already_locked = ec == std::errc::device_or_resource_busy; - bool allow_errors = args.ignore_lock_failures.value_or(false); - if (is_already_locked || !allow_errors) - { - msg::write_unlocalized_text_to_stderr( - Color::error, - LocalizedString::from_raw(vcpkg_root_file) - .append_raw(": ") - .append_raw(ErrorPrefix) - .append(msgFailedToTakeFileSystemLock) - .append_raw(fmt::format("\n {}\n", ec.message()))); - Checks::exit_fail(VCPKG_LINE_INFO); - } + stderr_diagnostic_context.report_error(msgFailedToTakeFileSystemLock); + Checks::exit_fail(VCPKG_LINE_INFO); } } @@ -837,6 +837,43 @@ namespace vcpkg .map([](Path&& dot_git_parent) { return std::move(dot_git_parent) / ".git"; }); } + static Command git_cmd_builder(const Path& git_tool_path, const Path& dot_git_dir, const Path& work_tree) + { + Command ret(git_tool_path); + if (!dot_git_dir.empty()) + { + ret.string_arg(Strings::concat("--git-dir=", dot_git_dir)); + } + if (!work_tree.empty()) + { + ret.string_arg(Strings::concat("--work-tree=", work_tree)); + } + ret.string_arg("-c").string_arg("core.autocrlf=false"); + return ret; + } + + Optional VcpkgPaths::get_scripts_version(DiagnosticContext& context) const + { + if (auto git_tool_path = get_tool_path(context, Tools::GIT)) + { + const auto dot_git_dir = root / ".git"; + auto cmd = git_cmd_builder(*git_tool_path, dot_git_dir, dot_git_dir) + .string_arg("show") + .string_arg("--pretty=format:%h %cd (%cr)") + .string_arg("-s") + .string_arg("--date=short") + .string_arg("HEAD"); + + auto maybe_output = cmd_execute_and_capture_output(context, cmd); + if (auto* output = check_zero_exit_code(context, cmd, maybe_output)) + { + return std::move(*output); + } + } + + return nullopt; + } + std::string VcpkgPaths::get_toolver_diagnostics() const { std::string ret; @@ -849,52 +886,45 @@ namespace vcpkg } else { - const auto dot_git_dir = root / ".git"; - auto cmd = git_cmd_builder(dot_git_dir, dot_git_dir) - .string_arg("show") - .string_arg("--pretty=format:%h %cd (%cr)") - .string_arg("-s") - .string_arg("--date=short") - .string_arg("HEAD"); - - const auto maybe_output = flatten_out(cmd_execute_and_capture_output(cmd), Tools::GIT); - if (const auto output = maybe_output.get()) + auto maybe_scripts_version = get_scripts_version(null_diagnostic_context); + if (const auto scripts_version = maybe_scripts_version.get()) { - Strings::append(ret, " vcpkg-scripts version: ", *output, "\n"); + Strings::append(ret, " vcpkg-scripts version: ", *scripts_version, "\n"); } else { Strings::append(ret, " vcpkg-scripts version: unknown\n"); } } + return ret; } const Filesystem& VcpkgPaths::get_filesystem() const { return m_pimpl->m_fs; } const AssetCachingSettings& VcpkgPaths::get_asset_cache_settings() const { return m_pimpl->m_asset_cache_settings; } const ToolCache& VcpkgPaths::get_tool_cache() const { return *m_pimpl->m_tool_cache; } - const Path& VcpkgPaths::get_tool_exe(StringView tool, MessageSink& status_messages) const + const Path* VcpkgPaths::get_tool_path(DiagnosticContext& context, StringView tool) const { - return m_pimpl->m_tool_cache->get_tool_path(tool, status_messages); + return m_pimpl->m_tool_cache->get_tool_path(context, m_pimpl->m_fs, tool); } - const std::string& VcpkgPaths::get_tool_version(StringView tool, MessageSink& status_messages) const + const Path& VcpkgPaths::get_tool_path_required(StringView tool) const { - return m_pimpl->m_tool_cache->get_tool_version(tool, status_messages); - } - - Command VcpkgPaths::git_cmd_builder(const Path& dot_git_dir, const Path& work_tree) const - { - Command ret(get_tool_exe(Tools::GIT, out_sink)); - if (!dot_git_dir.empty()) + if (auto tool_path = get_tool_path(console_diagnostic_context, tool)) { - ret.string_arg(Strings::concat("--git-dir=", dot_git_dir)); + return *tool_path; } - if (!work_tree.empty()) + + Checks::exit_fail(VCPKG_LINE_INFO); + } + const std::string& VcpkgPaths::get_tool_version_required(StringView tool) const + { + if (const auto* tool_version = + m_pimpl->m_tool_cache->get_tool_version(console_diagnostic_context, m_pimpl->m_fs, tool)) { - ret.string_arg(Strings::concat("--work-tree=", work_tree)); + return *tool_version; } - ret.string_arg("-c").string_arg("core.autocrlf=false"); - return ret; + + Checks::exit_fail(VCPKG_LINE_INFO); } ExpectedL VcpkgPaths::get_current_git_sha() const @@ -904,20 +934,27 @@ namespace vcpkg return {*sha, expected_left_tag}; } - return flatten_out( - cmd_execute_and_capture_output( - git_cmd_builder(this->root / ".git", this->root).string_arg("rev-parse").string_arg("HEAD")), - Tools::GIT) - .map([](std::string&& output) { - Strings::inplace_trim(output); - return std::move(output); - }); + SinkBufferedDiagnosticContext bdc{stderr_sink}; + if (auto git_tool_path = get_tool_path(bdc, Tools::GIT)) + { + const auto dot_git_dir = root / ".git"; + auto cmd = + git_cmd_builder(*git_tool_path, dot_git_dir, dot_git_dir).string_arg("rev-parse").string_arg("HEAD"); + auto maybe_output = cmd_execute_and_capture_output(bdc, cmd); + if (auto output = check_zero_exit_code(bdc, cmd, maybe_output)) + { + Strings::inplace_trim(*output); + return std::move(*output); + } + } + + return LocalizedString::from_raw(bdc.to_string()); } LocalizedString VcpkgPaths::get_current_git_sha_baseline_message() const { if (is_shallow_clone(null_diagnostic_context, - get_tool_exe(Tools::GIT, out_sink), + get_tool_path_required(Tools::GIT), GitRepoLocator{GitRepoLocatorKind::CurrentDirectory, this->root}) .value_or(false)) { @@ -966,11 +1003,20 @@ namespace vcpkg ExpectedL VcpkgPaths::git_show(StringView treeish, const Path& dot_git_dir) const { - // All git commands are run with: --git-dir={dot_git_dir} --work-tree={work_tree_temp} - // git clone --no-checkout --local {vcpkg_root} {dot_git_dir} - return flatten_out(cmd_execute_and_capture_output( - git_cmd_builder(dot_git_dir, dot_git_dir).string_arg("show").string_arg(treeish)), - Tools::GIT); + SinkBufferedDiagnosticContext bdc{out_sink}; + if (const auto* git_tool_path = get_tool_path(bdc, Tools::GIT)) + { + // All git commands are run with: --git-dir={dot_git_dir} --work-tree={work_tree_temp} + // git clone --no-checkout --local {vcpkg_root} {dot_git_dir} + auto cmd = git_cmd_builder(*git_tool_path, dot_git_dir, dot_git_dir).string_arg("show").string_arg(treeish); + auto maybe_output = cmd_execute_and_capture_output(bdc, cmd); + if (auto output = check_zero_exit_code(bdc, cmd, maybe_output)) + { + return std::move(*output); + } + } + + return LocalizedString::from_raw(bdc.to_string()); } Optional> VcpkgPaths::get_builtin_ports_directory_trees( @@ -978,25 +1024,29 @@ namespace vcpkg { auto& fs = get_filesystem(); // this should write to `context` but the tools cache isn't context aware at this time - auto git_exe = get_tool_exe(Tools::GIT, out_sink); + const auto* git_exe = get_tool_path(context, Tools::GIT); + if (!git_exe) + { + return nullopt; + } const auto& builtin_ports = this->builtin_ports_directory(); - const auto maybe_prefix = git_prefix(context, git_exe, builtin_ports); + const auto maybe_prefix = git_prefix(context, *git_exe, builtin_ports); if (auto prefix = maybe_prefix.get()) { const auto locator = GitRepoLocator{GitRepoLocatorKind::CurrentDirectory, builtin_ports}; - const auto maybe_index_file = git_index_file(context, fs, git_exe, locator); + const auto maybe_index_file = git_index_file(context, fs, *git_exe, locator); if (const auto index_file = maybe_index_file.get()) { TempFileDeleter temp_index_file{fs, fmt::format("{}_vcpkg_{}.tmp", index_file->native(), get_process_id())}; if (fs.copy_file(context, *index_file, temp_index_file.path, CopyOptions::overwrite_existing) && - git_add_with_index(context, git_exe, builtin_ports, temp_index_file.path)) + git_add_with_index(context, *git_exe, builtin_ports, temp_index_file.path)) { - auto maybe_outer_tree_sha = git_write_index_tree(context, git_exe, locator, temp_index_file.path); + auto maybe_outer_tree_sha = git_write_index_tree(context, *git_exe, locator, temp_index_file.path); if (const auto outer_tree_sha = maybe_outer_tree_sha.get()) { - return git_ls_tree(context, git_exe, locator, fmt::format("{}:{}", *outer_tree_sha, *prefix)); + return git_ls_tree(context, *git_exe, locator, fmt::format("{}:{}", *outer_tree_sha, *prefix)); } } } @@ -1008,90 +1058,110 @@ namespace vcpkg ExpectedL VcpkgPaths::git_fetch_from_remote_registry(StringView repo, StringView treeish) const { - auto& fs = get_filesystem(); + SinkBufferedDiagnosticContext bdc{stderr_sink}; + auto& fs = get_filesystem(); const auto& work_tree = m_pimpl->m_registries_work_tree_dir; - fs.create_directories(work_tree, VCPKG_LINE_INFO); + if (!fs.create_directories(bdc, work_tree)) + { + return LocalizedString::from_raw(bdc.to_string()); + } + const auto& dot_git_dir = m_pimpl->m_registries_dot_git_dir; + const auto* git_tool_path = get_tool_path(bdc, Tools::GIT); + if (!git_tool_path) + { + return LocalizedString::from_raw(bdc.to_string()); + } - auto init_cmd = git_cmd_builder(dot_git_dir, work_tree).string_arg("init"); - auto maybe_init_output = flatten(cmd_execute_and_capture_output(init_cmd), Tools::GIT); - if (!maybe_init_output) + const auto base_cmd = git_cmd_builder(*git_tool_path, dot_git_dir, work_tree); + auto init_cmd = base_cmd; + init_cmd.string_arg("init"); + auto maybe_init_output = cmd_execute_and_capture_output(bdc, init_cmd); + if (!check_zero_exit_code(bdc, init_cmd, maybe_init_output)) { - return msg::format_error(msgGitCommandFailed, msg::command_line = init_cmd.command_line()) - .append_raw('\n') - .append(maybe_init_output.error()); + return LocalizedString::from_raw(bdc.to_string()); } auto lock_file = work_tree / ".vcpkg-lock"; - auto guard = fs.take_exclusive_file_lock(lock_file, stderr_sink, IgnoreErrors{}); - auto fetch_git_ref = git_cmd_builder(dot_git_dir, work_tree) - .string_arg("fetch") - .string_arg("--update-shallow") - .string_arg("--") - .string_arg(repo) - .string_arg(treeish); + auto guard = fs.take_exclusive_file_lock(bdc, lock_file); + if (!guard) + { + return LocalizedString::from_raw(bdc.to_string()); + } - auto maybe_fetch_output = flatten(cmd_execute_and_capture_output(fetch_git_ref), Tools::GIT); - if (!maybe_fetch_output) + auto fetch_git_ref = base_cmd; + fetch_git_ref.string_arg("fetch") + .string_arg("--update-shallow") + .string_arg("--") + .string_arg(repo) + .string_arg(treeish); + + auto maybe_fetch_output = cmd_execute_and_capture_output(bdc, fetch_git_ref); + if (!check_zero_exit_code(bdc, fetch_git_ref, maybe_fetch_output)) { - return msg::format_error(msgGitFailedToFetch, msg::value = treeish, msg::url = repo) - .append_raw('\n') - .append(msgGitCommandFailed, msg::command_line = fetch_git_ref.command_line()) - .append_raw('\n') - .append(std::move(maybe_fetch_output).error()); + return LocalizedString::from_raw(bdc.to_string()); } - auto get_fetch_head = git_cmd_builder(dot_git_dir, work_tree).string_arg("rev-parse").string_arg("FETCH_HEAD"); - return flatten_out(cmd_execute_and_capture_output(get_fetch_head), Tools::GIT) - .map([](std::string&& output) { return Strings::trim(output).to_string(); }) - .map_error([&](LocalizedString&& err) { - return msg::format_error(msgGitCommandFailed, msg::command_line = get_fetch_head.command_line()) - .append_raw('\n') - .append(std::move(err)); - }); + auto git_rev_parse_head = base_cmd; + git_rev_parse_head.string_arg("rev-parse").string_arg("FETCH_HEAD"); + auto maybe_rev_parse_output = cmd_execute_and_capture_output(bdc, git_rev_parse_head); + if (auto* rev_parse_output = check_zero_exit_code(bdc, git_rev_parse_head, maybe_rev_parse_output)) + { + Strings::inplace_trim(*rev_parse_output); + return *rev_parse_output; + } + + return LocalizedString::from_raw(bdc.to_string()); } ExpectedL VcpkgPaths::git_fetch(StringView repo, StringView treeish) const { + SinkBufferedDiagnosticContext bdc{stderr_sink}; + auto& fs = get_filesystem(); const auto& work_tree = m_pimpl->m_registries_work_tree_dir; - fs.create_directories(work_tree, VCPKG_LINE_INFO); + if (!fs.create_directories(bdc, work_tree)) + { + return LocalizedString::from_raw(bdc.to_string()); + } auto lock_file = work_tree / ".vcpkg-lock"; - auto guard = fs.take_exclusive_file_lock(lock_file, stderr_sink, IgnoreErrors{}); + auto git_tool_path = get_tool_path(bdc, Tools::GIT); + if (!git_tool_path) + { + return LocalizedString::from_raw(bdc.to_string()); + } + + auto guard = fs.take_exclusive_file_lock(bdc, lock_file); + if (!guard) + { + return LocalizedString::from_raw(bdc.to_string()); + } const auto& dot_git_dir = m_pimpl->m_registries_dot_git_dir; - auto init_registries_git_dir = git_cmd_builder(dot_git_dir, work_tree).string_arg("init"); - auto maybe_init_output = flatten(cmd_execute_and_capture_output(init_registries_git_dir), Tools::GIT); - if (!maybe_init_output) + auto init_registries_git_dir = git_cmd_builder(*git_tool_path, dot_git_dir, work_tree).string_arg("init"); + auto maybe_init_output = cmd_execute_and_capture_output(bdc, init_registries_git_dir); + if (!check_zero_exit_code(bdc, init_registries_git_dir, maybe_init_output)) { - return msg::format_error(msgGitFailedToInitializeLocalRepository, msg::path = work_tree) - .append_raw('\n') - .append(msgGitCommandFailed, msg::command_line = init_registries_git_dir.command_line()) - .append_raw('\n') - .append(std::move(maybe_init_output).error()); + return LocalizedString::from_raw(bdc.to_string()); } - auto fetch_git_ref = git_cmd_builder(dot_git_dir, work_tree) + auto fetch_git_ref = git_cmd_builder(*git_tool_path, dot_git_dir, work_tree) .string_arg("fetch") .string_arg("--update-shallow") .string_arg("--") .string_arg(repo) .string_arg(treeish); - auto maybe_fetch_output = flatten(cmd_execute_and_capture_output(fetch_git_ref), Tools::GIT); - if (!maybe_fetch_output) + auto maybe_fetch_output = cmd_execute_and_capture_output(bdc, fetch_git_ref); + if (!check_zero_exit_code(bdc, fetch_git_ref, maybe_fetch_output)) { - return msg::format_error(msgGitFailedToFetch, msg::value = treeish, msg::url = repo) - .append_raw('\n') - .append(msgGitCommandFailed, msg::command_line = fetch_git_ref.command_line()) - .append_raw('\n') - .append(std::move(maybe_fetch_output).error()); + return LocalizedString::from_raw(bdc.to_string()); } return {Unit{}}; @@ -1101,39 +1171,59 @@ namespace vcpkg // hash ExpectedL VcpkgPaths::git_show_from_remote_registry(StringView hash, const Path& relative_path) const { + SinkBufferedDiagnosticContext bdc{stderr_sink}; auto revision = fmt::format("{}:{}", hash, relative_path.generic_u8string()); - return flatten_out(cmd_execute_and_capture_output( - git_cmd_builder(m_pimpl->m_registries_dot_git_dir, m_pimpl->m_registries_work_tree_dir) - .string_arg("show") - .string_arg(revision)), - Tools::GIT); + if (const auto* git_tool_path = get_tool_path(bdc, Tools::GIT)) + { + auto cmd = + git_cmd_builder(*git_tool_path, m_pimpl->m_registries_dot_git_dir, m_pimpl->m_registries_work_tree_dir) + .string_arg("show") + .string_arg(revision); + auto maybe_output = cmd_execute_and_capture_output(bdc, cmd); + if (auto output = check_zero_exit_code(bdc, cmd, maybe_output)) + { + return std::move(*output); + } + } + + return LocalizedString::from_raw(bdc.to_string()); } ExpectedL VcpkgPaths::git_find_object_id_for_remote_registry_path(StringView hash, const Path& relative_path) const { + SinkBufferedDiagnosticContext bdc{stderr_sink}; auto revision = fmt::format("{}:{}", hash, relative_path.generic_u8string()); - return flatten_out(cmd_execute_and_capture_output( - git_cmd_builder(m_pimpl->m_registries_dot_git_dir, m_pimpl->m_registries_work_tree_dir) - .string_arg("rev-parse") - .string_arg(revision)), - Tools::GIT) - .map([](std::string&& output) { - Strings::inplace_trim(output); - return std::move(output); - }); + if (const auto* git_tool_path = get_tool_path(bdc, Tools::GIT)) + { + auto cmd = + git_cmd_builder(*git_tool_path, m_pimpl->m_registries_dot_git_dir, m_pimpl->m_registries_work_tree_dir) + .string_arg("rev-parse") + .string_arg(revision); + auto maybe_output = cmd_execute_and_capture_output(bdc, cmd); + if (auto output = check_zero_exit_code(bdc, cmd, maybe_output)) + { + Strings::inplace_trim(*output); + return std::move(*output); + } + } + + return LocalizedString::from_raw(bdc.to_string()); } ExpectedL VcpkgPaths::git_read_tree(const Path& destination, StringView tree, const Path& dot_git_dir) const { - BufferedDiagnosticContext bdc{out_sink}; - if (vcpkg::git_extract_tree(bdc, - get_filesystem(), - get_tool_exe(Tools::GIT, out_sink), - GitRepoLocator{GitRepoLocatorKind::DotGitDir, dot_git_dir}, - destination, - tree)) - { - return Unit{}; + SinkBufferedDiagnosticContext bdc{out_sink}; + if (auto git_path = get_tool_path(bdc, Tools::GIT)) + { + if (vcpkg::git_extract_tree(bdc, + get_filesystem(), + *git_path, + GitRepoLocator{GitRepoLocatorKind::DotGitDir, dot_git_dir}, + destination, + tree)) + { + return Unit{}; + } } return LocalizedString::from_raw(std::move(bdc).to_string());