diff --git a/include/vcpkg/commands.format-port.h b/include/vcpkg/commands.format-port.h new file mode 100644 index 0000000000..1a1e25491a --- /dev/null +++ b/include/vcpkg/commands.format-port.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace vcpkg::Commands::FormatPort +{ + extern const CommandStructure COMMAND_STRUCTURE; + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths); + + struct FormatPortCommand : PathsCommand + { + virtual void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) const override; + }; +} diff --git a/src/vcpkg/commands.cpp b/src/vcpkg/commands.cpp index 5ab13a5baa..29b2390948 100644 --- a/src/vcpkg/commands.cpp +++ b/src/vcpkg/commands.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,7 @@ namespace vcpkg::Commands static const Fetch::FetchCommand fetch{}; static const FindCommand find_{}; static const FormatManifest::FormatManifestCommand format_manifest{}; + static const FormatPort::FormatPortCommand format_port{}; static const Help::HelpCommand help{}; static const Info::InfoCommand info{}; static const Integrate::IntegrateCommand integrate{}; @@ -119,6 +121,7 @@ namespace vcpkg::Commands {"fetch", &fetch}, {"find", &find_}, {"format-manifest", &format_manifest}, + {"format-port", &format_port}, {"integrate", &integrate}, {"list", &list}, {"new", &new_}, diff --git a/src/vcpkg/commands.format-port.cpp b/src/vcpkg/commands.format-port.cpp new file mode 100644 index 0000000000..aafa006c17 --- /dev/null +++ b/src/vcpkg/commands.format-port.cpp @@ -0,0 +1,294 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace +{ + using namespace vcpkg; + + struct ToWrite + { + SourceControlFile scf; + Path file_to_write; + Path original_path; + std::string original_source; + }; + + Optional read_manifest(Filesystem& fs, Path&& manifest_path) + { + auto path_string = manifest_path.native(); + Debug::print("Reading ", path_string, "\n"); + auto contents = fs.read_contents(manifest_path, VCPKG_LINE_INFO); + auto parsed_json_opt = Json::parse(contents, manifest_path); + if (!parsed_json_opt.has_value()) + { + vcpkg::printf(Color::error, "Failed to parse %s: %s\n", path_string, parsed_json_opt.error()->format()); + return nullopt; + } + + const auto& parsed_json = parsed_json_opt.value_or_exit(VCPKG_LINE_INFO).first; + if (!parsed_json.is_object()) + { + vcpkg::printf(Color::error, "The file %s is not an object\n", path_string); + return nullopt; + } + + auto parsed_json_obj = parsed_json.object(); + + auto scf = SourceControlFile::parse_manifest_object(manifest_path, parsed_json_obj); + if (!scf.has_value()) + { + vcpkg::printf(Color::error, "Failed to parse manifest file: %s\n", path_string); + print_error_message(scf.error()); + return nullopt; + } + + return ToWrite{ + std::move(*scf.value_or_exit(VCPKG_LINE_INFO)), + manifest_path, + manifest_path, + std::move(contents), + }; + } + + Optional read_control_file(Filesystem& fs, Path&& control_path) + { + std::error_code ec; + Debug::print("Reading ", control_path, "\n"); + + auto manifest_path = Path(control_path.parent_path()) / "vcpkg.json"; + auto contents = fs.read_contents(control_path, VCPKG_LINE_INFO); + auto paragraphs = Paragraphs::parse_paragraphs(contents, control_path); + + if (!paragraphs) + { + vcpkg::printf(Color::error, "Failed to read paragraphs from %s: %s\n", control_path, paragraphs.error()); + return {}; + } + auto scf_res = + SourceControlFile::parse_control_file(control_path, std::move(paragraphs).value_or_exit(VCPKG_LINE_INFO)); + if (!scf_res) + { + vcpkg::printf(Color::error, "Failed to parse control file: %s\n", control_path); + print_error_message(scf_res.error()); + return {}; + } + + return ToWrite{ + std::move(*scf_res.value_or_exit(VCPKG_LINE_INFO)), + manifest_path, + control_path, + std::move(contents), + }; + } + + void open_for_write(Filesystem& fs, const ToWrite& data) + { + const auto& original_path_string = data.original_path.native(); + const auto& file_to_write_string = data.file_to_write.native(); + if (data.file_to_write == data.original_path) + { + Debug::print("Formatting ", file_to_write_string, "\n"); + } + else + { + Debug::print("Converting ", file_to_write_string, " -> ", original_path_string, "\n"); + } + auto res = serialize_manifest(data.scf); + + auto check = SourceControlFile::parse_manifest_object(StringView{}, res); + if (!check) + { + vcpkg::printf(Color::error, + R"([correctness check] Failed to parse serialized manifest file of %s +Please open an issue at https://github.com/microsoft/vcpkg, with the following output: +Error:)", + data.scf.core_paragraph->name); + print_error_message(check.error()); + Checks::exit_maybe_upgrade(VCPKG_LINE_INFO, + R"( +=== Serialized manifest file === +%s +)", + Json::stringify(res, {})); + } + + auto check_scf = std::move(check).value_or_exit(VCPKG_LINE_INFO); + if (*check_scf != data.scf) + { + Checks::exit_maybe_upgrade( + VCPKG_LINE_INFO, + R"([correctness check] The serialized manifest SCF was different from the original SCF. +Please open an issue at https://github.com/microsoft/vcpkg, with the following output: + +=== Original File === +%s + +=== Serialized File === +%s + +=== Original SCF === +%s + +=== Serialized SCF === +%s +)", + data.original_source, + Json::stringify(res, {}), + Json::stringify(serialize_debug_manifest(data.scf), {}), + Json::stringify(serialize_debug_manifest(*check_scf), {})); + } + + // the manifest scf is correct + std::error_code ec; + fs.write_contents(data.file_to_write, Json::stringify(res, {}), ec); + if (ec) + { + Checks::exit_with_message( + VCPKG_LINE_INFO, "Failed to write manifest file %s: %s\n", file_to_write_string, ec.message()); + } + if (data.original_path != data.file_to_write) + { + fs.remove(data.original_path, ec); + if (ec) + { + Checks::exit_with_message( + VCPKG_LINE_INFO, "Failed to remove control file %s: %s\n", original_path_string, ec.message()); + } + } + } +} + +namespace vcpkg::Commands::FormatPort +{ + static constexpr StringLiteral OPTION_ALL = "all"; + static constexpr StringLiteral OPTION_CONVERT_CONTROL = "convert-control"; + + const CommandSwitch FORMAT_SWITCHES[] = { + {OPTION_ALL, "Format all ports' manifest files."}, + {OPTION_CONVERT_CONTROL, "Convert CONTROL files to manifest files."}, + }; + + const CommandStructure COMMAND_STRUCTURE = { + create_example_string(R"###(format-port --all)###"), + 0, + SIZE_MAX, + {FORMAT_SWITCHES, {}, {}}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + auto parsed_args = args.parse_arguments(COMMAND_STRUCTURE); + + auto& fs = paths.get_filesystem(); + CommandRegistryPaths registry_paths = resolve_command_registry_paths(fs, paths, args, Build::Editable::YES); + + bool has_error = false; + + const bool format_all = Util::Sets::contains(parsed_args.switches, OPTION_ALL); + const bool convert_control = Util::Sets::contains(parsed_args.switches, OPTION_CONVERT_CONTROL); + + if (!format_all && convert_control) + { + print2(Color::warning, R"(format-port was passed '--convert-control' without '--all'. + This doesn't do anything: + we will automatically convert all control files passed explicitly.)"); + } + + if (!format_all && args.command_arguments.empty()) + { + Checks::exit_with_message( + VCPKG_LINE_INFO, + "No files to format; please pass either --all, or the explicit files to format or convert."); + } + + std::vector to_write; + + const auto add_file = [&to_write, &has_error](Optional&& opt) { + if (auto t = opt.get()) + to_write.push_back(std::move(*t)); + else + has_error = true; + }; + + for (auto&& port_name : args.command_arguments) + { + auto port_path = registry_paths.ports_directory_path / port_name; + + if (!fs.exists(port_path, VCPKG_LINE_INFO)) + { + vcpkg::printf(Color::error, "Error: Couldn't find required port `%s`\n.", port_name); + has_error = true; + continue; + } + + auto port_control_path = port_path / "CONTROL"; + auto port_manifest_path = port_path / "vcpkg.json"; + + if (fs.exists(port_control_path, VCPKG_LINE_INFO)) + { + add_file(read_control_file(fs, std::move(port_control_path))); + } + else + { + add_file(read_manifest(fs, std::move(port_manifest_path))); + } + } + + if (format_all) + { + for (const auto& dir : + fs.get_directories_non_recursive(registry_paths.ports_directory_path, VCPKG_LINE_INFO)) + { + auto control_path = dir / "CONTROL"; + auto manifest_path = dir / "vcpkg.json"; + auto manifest_exists = fs.exists(manifest_path, IgnoreErrors{}); + auto control_exists = fs.exists(control_path, IgnoreErrors{}); + + Checks::check_exit(VCPKG_LINE_INFO, + !manifest_exists || !control_exists, + "Both a manifest file and a CONTROL file exist in port directory: %s", + dir); + + if (manifest_exists) + { + add_file(read_manifest(fs, std::move(manifest_path))); + } + if (convert_control && control_exists) + { + add_file(read_control_file(fs, std::move(control_path))); + } + } + } + + for (auto const& el : to_write) + { + open_for_write(fs, el); + } + + if (has_error) + { + Checks::exit_fail(VCPKG_LINE_INFO); + } + else + { + print2("Succeeded in formatting the manifest files.\n"); + Checks::exit_success(VCPKG_LINE_INFO); + } + } + + void FormatPortCommand::perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) const + { + FormatPort::perform_and_exit(args, paths); + } +}