BUILD.gn: Add a check generated files are in allowed dirs.

This is important so that we know that the list of allowed directories
is in sync with other parts of the build in follow-up commits.

BUG=dawn:22

Change-Id: I202bec55b510989e43acf497956e2937c9a2f60a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/11360
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/generator/dawn_generator.gni b/generator/dawn_generator.gni
index 3ab81e7..fc72883 100644
--- a/generator/dawn_generator.gni
+++ b/generator/dawn_generator.gni
@@ -15,6 +15,16 @@
 import("../scripts/dawn_overrides_with_defaults.gni")
 import("generator_lib.gni")
 
+dawn_allowed_gen_output_dirs = [
+  "src/dawn/",
+  "src/dawn_native/",
+  "src/dawn_native/opengl/",
+  "src/dawn_wire/client/",
+  "src/dawn_wire/server/",
+  "src/dawn_wire/",
+  "src/include/dawn/",
+]
+
 # Template to help invoking Dawn code generators based on generator_lib
 #
 #   dawn_generator("my_target_gen") {
@@ -47,6 +57,7 @@
     forward_variables_from(invoker, "*")
     generator_lib_dir = "${dawn_root}/generator"
     jinja2_path = dawn_jinja2_dir
+    allowed_output_dirs = dawn_allowed_gen_output_dirs
   }
 }
 
diff --git a/generator/generator_lib.gni b/generator/generator_lib.gni
index d3698f2..10c560d 100644
--- a/generator/generator_lib.gni
+++ b/generator/generator_lib.gni
@@ -35,6 +35,11 @@
 #
 #   jinja2_path: Optional Jinja2 installation path.
 #
+#   allowed_output_dirs: Optional list of directories that are the only
+#     directories in which files of `outputs` are allowed to be (and not
+#     in children directories). Generation will fail if an output isn't
+#     in a directory in the list.
+#
 #   root_dir: Optional root source dir for Python dependencies
 #     computation. Defaults to "${generator_lib_dir}/..". Any dependency
 #     outside of this directory is considered a system file and will be
@@ -111,6 +116,19 @@
     rebase_path(_expected_outputs_file, root_build_dir),
   ]
 
+  # Check that all of the outputs are in a directory that's allowed. This is
+  # useful to keep the list of directories in sink with other parts of the
+  # build.
+  if (defined(invoker.allowed_output_dirs)) {
+    _allowed_output_dirs_file = "${_gen_dir}/${target_name}.allowed_output_dirs"
+    write_file(_allowed_output_dirs_file, invoker.allowed_output_dirs)
+
+    _generator_args += [
+      "--allowed-output-dirs-file",
+      rebase_path(_allowed_output_dirs_file, root_build_dir),
+    ]
+  }
+
   # The code generator invocation that will write the JSON tarball, check the
   # outputs are what's expected and write a depfile for Ninja.
   action(_json_tarball_target) {
diff --git a/generator/generator_lib.py b/generator/generator_lib.py
index 7307f56..9dcd179 100644
--- a/generator/generator_lib.py
+++ b/generator/generator_lib.py
@@ -211,6 +211,7 @@
     parser.add_argument('--depfile', default=None, type=str, help='Name of the Ninja depfile to create for the JSON tarball')
     parser.add_argument('--expected-outputs-file', default=None, type=str, help="File to compare outputs with and fail if it doesn't match")
     parser.add_argument('--root-dir', default=None, type=str, help='Optional source root directory for Python dependency computations')
+    parser.add_argument('--allowed-output-dirs-file', default=None, type=str, help="File containing a list of allowed directories where files can be output.")
 
     args = parser.parse_args()
 
@@ -231,6 +232,26 @@
 
     outputs = _do_renders(renders, args.template_dir)
 
+    # The caller wants to assert that the outputs are only in specific directories.
+    if args.allowed_output_dirs_file != None:
+        with open(args.allowed_output_dirs_file) as f:
+            allowed_dirs = set([line.strip() for line in f.readlines()])
+
+        for directory in allowed_dirs:
+            if not directory.endswith('/'):
+                print('Allowed directory entry "{}" doesn\'t end with /'.format(directory))
+                return 1
+
+        def check_in_subdirectory(path, directory):
+            return path.startswith(directory) and not '/' in path[len(directory):]
+
+        for render in renders:
+            if not any(check_in_subdirectory(render.output, directory) for directory in allowed_dirs):
+                print('Output file "{}" is not in the allowed directory list below:'.format(render.output))
+                for directory in sorted(allowed_dirs):
+                    print('    "{}"'.format(directory))
+                return 1
+
     # Output the tarball and its depfile
     if args.output_json_tarball != None:
         json_root = {}