pw_env_setup: Allow specifying GN directory

Allow the GN out directory for Python installation to be specified with
the --virtualenv-gn-out-dir argument. This facilitates having projects
include Python installation in their default builds without duplicated
installations between env setup and the normal build.

Change-Id: I0606e7a7b0106e71b5b62f9852810eabc6dc86e4
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/29080
Reviewed-by: Rob Mohr <mohrr@google.com>
Commit-Queue: Wyatt Hepler <hepler@google.com>
diff --git a/pw_env_setup/py/pw_env_setup/env_setup.py b/pw_env_setup/py/pw_env_setup/env_setup.py
index f0461ff..4a217f2 100755
--- a/pw_env_setup/py/pw_env_setup/env_setup.py
+++ b/pw_env_setup/py/pw_env_setup/env_setup.py
@@ -175,7 +175,8 @@
     def __init__(self, pw_root, cipd_cache_dir, shell_file, quiet, install_dir,
                  use_pigweed_defaults, cipd_package_file, virtualenv_root,
                  virtualenv_requirements, virtualenv_gn_target,
-                 cargo_package_file, enable_cargo, json_file, project_root):
+                 virtualenv_gn_out_dir, cargo_package_file, enable_cargo,
+                 json_file, project_root):
         self._env = environment.Environment()
         self._project_root = project_root
         self._pw_root = pw_root
@@ -229,6 +230,7 @@
         self._cipd_package_file.extend(cipd_package_file)
         self._virtualenv_requirements.extend(virtualenv_requirements)
         self._virtualenv_gn_targets.extend(virtualenv_gn_target)
+        self._virtualenv_gn_out_dir = virtualenv_gn_out_dir
         self._cargo_package_file.extend(cargo_package_file)
 
         self._env.set('PW_PROJECT_ROOT', project_root)
@@ -410,6 +412,7 @@
                 venv_path=self._virtualenv_root,
                 requirements=requirements,
                 gn_targets=self._virtualenv_gn_targets,
+                gn_out_dir=self._virtualenv_gn_out_dir,
                 python=new_python3,
                 env=self._env,
         ):
@@ -527,13 +530,19 @@
     parser.add_argument(
         '--virtualenv-gn-target',
         help=('GN targets that build and install Python packages. Format: '
-              "path/to/gn_root#target"),
+              'path/to/gn_root#target'),
         default=[],
         action='append',
         type=virtualenv_setup.GnTarget,
     )
 
     parser.add_argument(
+        '--virtualenv-gn-out-dir',
+        help=('Output directory to use when building and installing Python '
+              'packages with GN; defaults to a unique path in the environment '
+              'directory.'))
+
+    parser.add_argument(
         '--virtualenv-root',
         help=('Root of virtualenv directory. Default: '
               '<install_dir>/pigweed-venv'),
diff --git a/pw_env_setup/py/pw_env_setup/virtualenv_setup/install.py b/pw_env_setup/py/pw_env_setup/virtualenv_setup/install.py
index fd6b97f..41d545b 100644
--- a/pw_env_setup/py/pw_env_setup/virtualenv_setup/install.py
+++ b/pw_env_setup/py/pw_env_setup/virtualenv_setup/install.py
@@ -121,6 +121,7 @@
         full_envsetup=True,
         requirements=(),
         gn_targets=(),
+        gn_out_dir=None,
         python=sys.executable,
         env=None,
 ):
@@ -187,7 +188,10 @@
                     *requirement_args)
 
     def install_packages(gn_target):
-        build = os.path.join(venv_path, gn_target.name)
+        if gn_out_dir is None:
+            build_dir = os.path.join(venv_path, gn_target.name)
+        else:
+            build_dir = gn_out_dir
 
         env_log = 'env-{}.log'.format(gn_target.name)
         env_log_path = os.path.join(venv_path, env_log)
@@ -206,7 +210,7 @@
         gn_log_path = os.path.join(venv_path, gn_log)
         try:
             with open(gn_log_path, 'w') as outs:
-                subprocess.check_call(('gn', 'gen', build),
+                subprocess.check_call(('gn', 'gen', build_dir),
                                       cwd=os.path.join(project_root,
                                                        gn_target.directory),
                                       stdout=outs,
@@ -220,7 +224,7 @@
         ninja_log_path = os.path.join(venv_path, ninja_log)
         try:
             with open(ninja_log_path, 'w') as outs:
-                ninja_cmd = ['ninja', '-C', build]
+                ninja_cmd = ['ninja', '-C', build_dir]
                 ninja_cmd.append(gn_target.target)
                 subprocess.check_call(ninja_cmd, stdout=outs, stderr=outs)
         except subprocess.CalledProcessError as err: