| // Copyright (c) 2023 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "gn/ninja_outputs_writer.h" | |
| #include <algorithm> | |
| #include <memory> | |
| #include "base/command_line.h" | |
| #include "base/files/file_path.h" | |
| #include "base/json/string_escape.h" | |
| #include "gn/builder.h" | |
| #include "gn/commands.h" | |
| #include "gn/filesystem_utils.h" | |
| #include "gn/invoke_python.h" | |
| #include "gn/settings.h" | |
| #include "gn/string_output_buffer.h" | |
| // NOTE: Intentional macro definition allows compile-time string concatenation. | |
| // (see usage below). | |
| #if defined(OS_WINDOWS) | |
| #define LINE_ENDING "\r\n" | |
| #else | |
| #define LINE_ENDING "\n" | |
| #endif | |
| namespace { | |
| using MapType = NinjaOutputsWriter::MapType; | |
| // Sort the targets according to their human visible labels first. | |
| struct TargetLabelPair { | |
| TargetLabelPair(const Target* target, const Label& default_toolchain_label) | |
| : target(target), | |
| label(std::make_unique<std::string>( | |
| target->label().GetUserVisibleName(default_toolchain_label))) {} | |
| const Target* target; | |
| std::unique_ptr<std::string> label; | |
| bool operator<(const TargetLabelPair& other) const { | |
| return *label < *other.label; | |
| } | |
| using List = std::vector<TargetLabelPair>; | |
| // Create list of TargetLabelPairs sorted by their target labels. | |
| static List CreateSortedList(const MapType& outputs_map, | |
| const Label& default_toolchain_label) { | |
| List result; | |
| result.reserve(outputs_map.size()); | |
| for (const auto& output_pair : outputs_map) | |
| result.emplace_back(output_pair.first, default_toolchain_label); | |
| std::sort(result.begin(), result.end()); | |
| return result; | |
| } | |
| }; | |
| } // namespace | |
| // static | |
| StringOutputBuffer NinjaOutputsWriter::GenerateJSON( | |
| const MapType& outputs_map) { | |
| Label default_toolchain_label; | |
| if (!outputs_map.empty()) { | |
| default_toolchain_label = | |
| outputs_map.begin()->first->settings()->default_toolchain_label(); | |
| } | |
| auto sorted_pairs = | |
| TargetLabelPair::CreateSortedList(outputs_map, default_toolchain_label); | |
| StringOutputBuffer out; | |
| out.Append('{'); | |
| auto escape = [](std::string_view str) -> std::string { | |
| std::string result; | |
| base::EscapeJSONString(str, true, &result); | |
| return result; | |
| }; | |
| bool first_label = true; | |
| for (const auto& pair : sorted_pairs) { | |
| const Target* target = pair.target; | |
| const std::string& label = *pair.label; | |
| auto it = outputs_map.find(target); | |
| CHECK(it != outputs_map.end()); | |
| if (!first_label) | |
| out.Append(','); | |
| first_label = false; | |
| out.Append("\n "); | |
| out.Append(escape(label)); | |
| out.Append(": ["); | |
| bool first_path = true; | |
| for (const auto& output : it->second) { | |
| if (!first_path) | |
| out.Append(','); | |
| first_path = false; | |
| out.Append("\n "); | |
| out.Append(escape(output.value())); | |
| } | |
| out.Append("\n ]"); | |
| } | |
| out.Append("\n}"); | |
| return out; | |
| } | |
| bool NinjaOutputsWriter::RunAndWriteFiles( | |
| const MapType& outputs_map, | |
| const BuildSettings* build_settings, | |
| const std::string& file_name, | |
| const std::string& exec_script, | |
| const std::string& exec_script_extra_args, | |
| bool quiet, | |
| Err* err) { | |
| SourceFile output_file = build_settings->build_dir().ResolveRelativeFile( | |
| Value(nullptr, file_name), err); | |
| if (output_file.is_null()) { | |
| return false; | |
| } | |
| StringOutputBuffer outputs = GenerateJSON(outputs_map); | |
| base::FilePath output_path = build_settings->GetFullPath(output_file); | |
| if (!outputs.ContentsEqual(output_path)) { | |
| if (!outputs.WriteToFile(output_path, err)) { | |
| return false; | |
| } | |
| if (!exec_script.empty()) { | |
| SourceFile script_file; | |
| if (exec_script[0] != '/') { | |
| // Relative path, assume the base is in build_dir. | |
| script_file = build_settings->build_dir().ResolveRelativeFile( | |
| Value(nullptr, exec_script), err); | |
| if (script_file.is_null()) { | |
| return false; | |
| } | |
| } else { | |
| script_file = SourceFile(exec_script); | |
| } | |
| base::FilePath script_path = build_settings->GetFullPath(script_file); | |
| return internal::InvokePython(build_settings, script_path, | |
| exec_script_extra_args, output_path, quiet, | |
| err); | |
| } | |
| } | |
| return true; | |
| } |