pw_env_setup: Add option to install pw packages

Add an option to install pw packages at the end of env setup.

Change-Id: I47da5c5dd052f0594f3118c1ac53fd628897226c
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/75580
Reviewed-by: Zoltan Szatmary-Ban <szatmz@google.com>
Commit-Queue: Rob Mohr <mohrr@google.com>
diff --git a/pw_env_setup/config.json b/pw_env_setup/config.json
index bbefda2..6157dd2 100644
--- a/pw_env_setup/config.json
+++ b/pw_env_setup/config.json
@@ -11,5 +11,6 @@
     "gn_targets": [
       ":python.install"
     ]
-  }
+  },
+  "pw_packages": []
 }
diff --git a/pw_env_setup/docs.rst b/pw_env_setup/docs.rst
index dfaffbf..c19e66b 100644
--- a/pw_env_setup/docs.rst
+++ b/pw_env_setup/docs.rst
@@ -261,6 +261,10 @@
   complain if one of the required submodules is not present. Combining this
   with ``optional_submodules`` is not supported.
 
+``pw_packages``
+  A list of packages to install using :ref:`pw_package <module-pw_package>`
+  after the rest of bootstrap completes.
+
 An example of a config file is below.
 
 .. code-block:: json
@@ -279,6 +283,7 @@
       ],
       "system_packages": false
     },
+    "pw_packages": [],
     "optional_submodules": [
       "optional/submodule/one",
       "optional/submodule/two"
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 1c726e8..f89964f 100755
--- a/pw_env_setup/py/pw_env_setup/env_setup.py
+++ b/pw_env_setup/py/pw_env_setup/env_setup.py
@@ -150,7 +150,7 @@
     pass
 
 
-def result_func(glob_warnings):
+def result_func(glob_warnings=()):
     def result(status, *args):
         return _Result(status, *([str(x) for x in glob_warnings] + list(args)))
 
@@ -203,6 +203,7 @@
         self._optional_submodules = []
         self._required_submodules = []
         self._virtualenv_system_packages = False
+        self._pw_packages = []
         self._root_variable = None
 
         self._config_file_name = getattr(config_file, 'name', 'config file')
@@ -270,6 +271,9 @@
             os.path.join(self._project_root, x)
             for x in config.pop('cipd_package_files', ()))
 
+        for pkg in config.pop('pw_packages', ()):
+            self._pw_packages.append(pkg)
+
         virtualenv = config.pop('virtualenv', {})
 
         if virtualenv.get('gn_root'):
@@ -367,6 +371,7 @@
         steps = [
             ('CIPD package manager', self.cipd),
             ('Python environment', self.virtualenv),
+            ('pw packages', self.pw_package),
             ('Host tools', self.host_tools),
         ]
 
@@ -546,6 +551,36 @@
 
         return result(_Result.Status.DONE)
 
+    def pw_package(self, unused_spin):
+        """Install "default" pw packages."""
+
+        result = result_func()
+
+        if not self._pw_packages:
+            return result(_Result.Status.SKIPPED)
+
+        logdir = os.path.join(self._install_dir, 'packages')
+        if not os.path.isdir(logdir):
+            os.makedirs(logdir)
+
+        for pkg in self._pw_packages:
+            print('installing {}'.format(pkg))
+            cmd = ['pw', 'package', 'install', pkg]
+
+            log = os.path.join(logdir, '{}.log'.format(pkg))
+            try:
+                with open(log, 'w') as outs, self._env():
+                    print(*cmd, file=outs)
+                    subprocess.check_call(cmd,
+                                          stdout=outs,
+                                          stderr=subprocess.STDOUT)
+            except subprocess.CalledProcessError:
+                with open(log, 'r') as ins:
+                    sys.stderr.write(ins.read())
+                    raise
+
+        return result(_Result.Status.DONE)
+
     def host_tools(self, unused_spin):
         # The host tools are grabbed from CIPD, at least initially. If the
         # user has a current host build, that build will be used instead.