[build] Fixing hermetic deps in fidlc

Fixed detection of hermetic deps issue by creating a dep_file in fidlc.
Currently, the action which launches fidlc reads from some files which
are not its inputs, and can't be added as inputs as the files are not
known at gen time.  For such cases, tools like fidlc should generate a
dep file mapping their outputs to list-of-inputs, so that build system
can check for individual action's hermeticity. The fidlc works
incrementally correctly without this change, but build system can't
check that.

Updated gen_response_file to add depfile related arguments in the
response file.
Removed hermetic_deps suppression.

See:
https://fuchsia.dev/fuchsia-src/development/build/hermetic_actions?hl=en
https://fuchsia.dev/fuchsia-src/contribute/open_projects/build/hermetic_actions?hl=en

After this change we continuously verify that fidlc invocations are incrementally correct.

Bug: 74204

Change-Id: Ia06d1a7fa8d4f3af9ee3c81562cbcb7fb0815e83
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/513027
Commit-Queue: Shai Barack <shayba@google.com>
Reviewed-by: Shai Barack <shayba@google.com>
Reviewed-by: Pascal Perez <pascallouis@google.com>
diff --git a/build/fidl/fidl_library.gni b/build/fidl/fidl_library.gni
index b7b7917..476f740 100644
--- a/build/fidl/fidl_library.gni
+++ b/build/fidl/fidl_library.gni
@@ -64,6 +64,7 @@
   c_server = "$target_gen_dir/$c_stem.server.c"
   coding_tables = "$fidl_stem.tables.c"
   lint_stamp_file = "$target_gen_dir/$target_name.linted"
+  fidlc_depfile_path = "$target_gen_dir/$target_name.d"
 
   main_target_name = target_name
   response_file_target_name = "${target_name}_response_file"
@@ -125,6 +126,8 @@
              rebase_path(coding_tables, root_build_dir),
              "--name",
              library_name,
+             "--depfile",
+             rebase_path(fidlc_depfile_path, root_build_dir),
              "--sources",
            ] + rebase_path(sources, root_build_dir)
 
@@ -302,6 +305,8 @@
 
     inputs = [ response_file ]
 
+    depfile = fidlc_depfile_path
+
     outputs = [
       c_client,
       c_header,
@@ -310,16 +315,12 @@
       json_representation,
     ]
 
-    rebased_response_file = rebase_path(response_file, root_build_dir)
-
-    args = [ "@$rebased_response_file" ]
+    args = [ "@" + rebase_path(response_file, root_build_dir) ]
 
     deps = [
              ":$lint_target_name",
              ":$response_file_target_name",
            ] + non_fidl_deps
-
-    hermetic_deps = false
   }
 
   validate_json(verification_target_name) {
diff --git a/build/fidl/gen_response_file.py b/build/fidl/gen_response_file.py
index f88b370..a2b4829 100755
--- a/build/fidl/gen_response_file.py
+++ b/build/fidl/gen_response_file.py
@@ -51,11 +51,15 @@
     parser.add_argument(
         "--name", help="The name for the generated FIDL library, if any")
     parser.add_argument(
+        "--depfile", help="The name for the generated depfile, if any")
+    parser.add_argument(
         "--sources", help="List of FIDL source files", nargs="*")
     parser.add_argument(
         "--dep-libraries", help="List of dependent libraries", nargs="*")
     parser.add_argument(
-        "--experimental-flag", help="List of experimental flags", action="append")
+        "--experimental-flag",
+        help="List of experimental flags",
+        action="append")
     args = parser.parse_args()
 
     target_libraries = []
@@ -90,6 +94,9 @@
     if args.name:
         response_file.append("--name %s" % args.name)
 
+    if args.depfile:
+        response_file.append("--depfile %s" % args.depfile)
+
     if args.experimental_flag:
         for experimental_flag in args.experimental_flag:
             response_file.append("--experimental %s" % experimental_flag)
diff --git a/tools/fidl/fidlc/compiler/main.cc b/tools/fidl/fidlc/compiler/main.cc
index a4eb5e0..a1414ef 100644
--- a/tools/fidl/fidlc/compiler/main.cc
+++ b/tools/fidl/fidlc/compiler/main.cc
@@ -46,6 +46,7 @@
          "             [--werror]\n"
          "             [--format=[text|json]]\n"
          "             [--json-schema]\n"
+         "             [--depfile DEPFILE_PATH]\n"
          "             [--files [FIDL_FILE...]...]\n"
          "             [--help]\n"
          "\n"
@@ -79,6 +80,12 @@
          " * `--experimental FLAG_NAME`. If present, this flag enables an experimental\n"
          "    feature of fidlc.\n"
          "\n"
+         " * `--depfile DEPFILE_PATH`. Path of depfile generated by `fidlc`. This depfile is\n"
+         "   used to get correct incremental compilation rules. This file is populated by fidlc\n"
+         "   as Line1: out1: in1 in2 in3, Line2: out2: in1 in2 in3 ... Where out[1-2] are all the\n"
+         "   outputs generated by fidlc and in[1-3] are the files read. The input files are\n"
+         "   what are passed by --files. Output files are those generated by fidlc.\n"
+         "\n"
          " * `--files [FIDL_FILE...]...`. Each `--file [FIDL_FILE...]` chunk of arguments\n"
          "   describes a library, all of which must share the same top-level library name\n"
          "   declaration. Libraries must be presented in dependency order, with later\n"
@@ -296,6 +303,7 @@
 // TODO(pascallouis): remove forward declaration, this was only introduced to
 // reduce diff size while breaking things up.
 int compile(fidl::Reporter* reporter, fidl::flat::Typespace* typespace, std::string library_name,
+            std::string dep_file_path, const std::vector<std::string>& source_list,
             std::vector<std::pair<Behavior, std::string>> outputs,
             const std::vector<fidl::SourceManager>& source_managers,
             fidl::ExperimentalFlags experimental_flags);
@@ -312,6 +320,8 @@
 
   std::string library_name;
 
+  std::string dep_file_path;
+
   bool warnings_as_errors = false;
   std::string format = "text";
   std::vector<std::pair<Behavior, std::string>> outputs;
@@ -362,6 +372,8 @@
       if (!experimental_flags.SetFlagByName(flag)) {
         FailWithUsage("Unknown experimental flag %s\n", flag.data());
       }
+    } else if (behavior_argument == "--depfile") {
+      dep_file_path = args->Claim();
     } else if (behavior_argument == "--files") {
       // Start parsing filenames.
       break;
@@ -372,6 +384,7 @@
 
   // Prepare source files.
   std::vector<fidl::SourceManager> source_managers;
+  std::vector<std::string> source_list;
   source_managers.push_back(fidl::SourceManager());
   while (args->Remaining()) {
     std::string arg = args->Claim();
@@ -381,6 +394,7 @@
       if (!source_managers.back().CreateSource(arg.data())) {
         Fail("Couldn't read in source data from %s\n", arg.data());
       }
+      source_list.emplace_back(arg.data());
     }
   }
 
@@ -388,8 +402,8 @@
   bool enable_color = !std::getenv("NO_COLOR") && isatty(fileno(stderr));
   fidl::Reporter reporter(warnings_as_errors, enable_color);
   auto typespace = fidl::flat::Typespace::RootTypes(&reporter);
-  auto status = compile(&reporter, &typespace, library_name, std::move(outputs), source_managers,
-                        std::move(experimental_flags));
+  auto status = compile(&reporter, &typespace, library_name, dep_file_path, source_list,
+                        std::move(outputs), source_managers, std::move(experimental_flags));
   if (format == "json") {
     reporter.PrintReportsJson();
   } else {
@@ -399,6 +413,7 @@
 }
 
 int compile(fidl::Reporter* reporter, fidl::flat::Typespace* typespace, std::string library_name,
+            std::string dep_file_path, const std::vector<std::string>& source_list,
             std::vector<std::pair<Behavior, std::string>> outputs,
             const std::vector<fidl::SourceManager>& source_managers,
             fidl::ExperimentalFlags experimental_flags) {
@@ -455,6 +470,25 @@
          library_name.data());
   }
 
+  // Write depfile, with format:
+  // output1 : inputA inputB inputC
+  // output2 : inputA inputB inputC
+  // ...
+  if (dep_file_path.size() != 0) {
+    std::ostringstream dep_file_contents;
+    for (auto& output : outputs) {
+      auto& file_path = output.second;
+      dep_file_contents << file_path << " ";
+      dep_file_contents << ": ";
+      for (auto& input_path : source_list) {
+        dep_file_contents << input_path << " ";
+      }
+      dep_file_contents << "\n";
+    }
+
+    Write(std::move(dep_file_contents), dep_file_path);
+  }
+
   // We recompile dependencies, and only emit output for the final
   // library.
   for (auto& output : outputs) {