• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1.. _module-pw_env_setup:
2
3------------
4pw_env_setup
5------------
6A classic problem in the embedded space is reducing the time from git clone
7to having a binary executing on a device. The issue is that an entire suite
8of tools is needed for non-trivial production embedded projects. For example:
9
10- A C++ compiler for your target device, and also for your host
11- A build system or three; for example, GN, Ninja, CMake, Bazel
12- A code formatting program like clang-format
13- A debugger like OpenOCD to flash and debug your embedded device (OpenOCD
14  support removed for Windows)
15- A known Python version with known modules installed for scripting
16- A Go compiler for the Go-based command line tools
17
18...and so on
19
20In the server space, container solutions like Docker or Podman solve this;
21however, in our experience container solutions are a mixed bag for embedded
22systems development where one frequently needs access to native system
23resources like USB devices, or must operate on Windows.
24
25``pw_env_setup`` is our compromise solution for this problem that works on Mac,
26Windows, and Linux. It leverages the Chrome packaging system `CIPD`_ to
27bootstrap a Python installation, which in turn inflates a virtual
28environment. The tooling is installed into your workspace, and makes no
29changes to your system. This tooling is designed to be reused by any
30project.
31
32.. _CIPD: https://github.com/luci/luci-go/tree/HEAD/cipd
33
34Users interact with  ``pw_env_setup`` with two commands: ``. bootstrap.sh`` and
35``. activate.sh``. The bootstrap command always pulls down the current versions
36of CIPD packages and sets up the Python virtual environment. The activate
37command reinitializes a previously configured environment, and if none is found,
38runs bootstrap.
39
40.. note::
41
42   On Windows the scripts used to set up the environment are ``bootstrap.bat``
43   and ``activate.bat``. For simplicity they will be referred to with the
44   ``.sh`` endings unless the distinction is relevant.
45
46.. warning::
47
48   At this time ``pw_env_setup`` works for us, but isn’t well tested. We don’t
49   suggest relying on it just yet. However, we are interested in experience
50   reports; if you give it a try, please `send us a note`_ about your
51   experience.
52
53.. _send us a note: pigweed@googlegroups.com
54
55On POSIX systems, the environment can be deactivated by running ``deactivate``.
56
57==================================
58Using pw_env_setup in your project
59==================================
60
61Downstream Projects Using Pigweed's Packages
62********************************************
63
64Projects using Pigweed can leverage ``pw_env_setup`` to install Pigweed's
65dependencies or their own dependencies. Projects that only want to use Pigweed's
66dependencies without modifying them can just source Pigweed's ``bootstrap.sh``
67and ``activate.sh`` scripts.
68
69An example of what your project's `bootstrap.sh` could look like is below. This
70assumes `bootstrap.sh` is at the top level of your repository.
71
72.. code-block:: bash
73
74   # Do not include a "#!" line, this must be sourced and not executed.
75
76   # This assumes the user is sourcing this file from it's parent directory. See
77   # below for a more flexible way to handle this.
78   PROJ_SETUP_SCRIPT_PATH="$(pwd)/bootstrap.sh"
79
80   export PW_PROJECT_ROOT="$(_python_abspath "$(dirname "$PROJ_SETUP_SCRIPT_PATH")")"
81
82   # You may wish to check if the user is attempting to execute this script
83   # instead of sourcing it. See below for an example of how to handle that
84   # situation.
85
86   # Source Pigweed's bootstrap utility script.
87   # Using '.' instead of 'source' for POSIX compatibility. Since users don't use
88   # dash directly, using 'source' in most documentation so users don't get
89   # confused and try to `./bootstrap.sh`.
90   . "$PW_PROJECT_ROOT/third_party/pigweed/pw_env_setup/util.sh"
91
92   pw_check_root "$PW_ROOT"
93   _PW_ACTUAL_ENVIRONMENT_ROOT="$(pw_get_env_root)"
94   export _PW_ACTUAL_ENVIRONMENT_ROOT
95   SETUP_SH="$_PW_ACTUAL_ENVIRONMENT_ROOT/activate.sh"
96   pw_bootstrap --args...  # See below for details about args.
97   pw_finalize bootstrap "$SETUP_SH"
98
99
100Bazel Usage
101-----------
102It is possible to pull in a CIPD dependency into Bazel using WORKSPACE rules
103rather than using `bootstrap.sh`. e.g.
104
105.. code:: python
106
107   # WORKSPACE
108
109   load("//pw_env_setup/bazel/cipd_setup:cipd_rules.bzl", "pigweed_deps")
110
111   # Setup CIPD client and packages.
112   # Required by: pigweed.
113   # Used by modules: all.
114   pigweed_deps()
115
116   load("@cipd_deps//:cipd_init.bzl", "cipd_init")
117
118   cipd_init()
119
120
121This will make the entire set of Pigweeds remote repositories available to your
122project. Though these repositories will only be donwloaded if you use them. To
123get a full list of the remote repositories that this configures, run:
124
125.. code:: sh
126
127   bazel query //external:all | grep cipd_
128
129All files and executables in each CIPD remote repository is exported and visible
130either directely (`@cipd_<dep>//:<file>`) or from 'all' filegroup
131(`@cipd_<dep>//:all`).
132
133From here it is possible to get access to the Bloaty binaries using the
134following command. For example;
135
136.. code:: sh
137
138   bazel run @cipd_pigweed_third_party_bloaty_embedded_linux_amd64//:bloaty \
139    -- --help
140
141User-Friendliness
142-----------------
143
144You may wish to allow sourcing `bootstrap.sh` from a different directory. In
145that case you'll need the following at the top of `bootstrap.sh`.
146
147.. code-block:: bash
148
149   _python_abspath () {
150     python -c "import os.path; print(os.path.abspath('$@'))"
151   }
152
153   # Use this code from Pigweed's bootstrap to find the path to this script when
154   # sourced. This should work with common shells. PW_CHECKOUT_ROOT is only used in
155   # presubmit tests with strange setups, and can be omitted if you're not using
156   # Pigweed's automated testing infrastructure.
157   if test -n "$PW_CHECKOUT_ROOT"; then
158     PROJ_SETUP_SCRIPT_PATH="$(_python_abspath "$PW_CHECKOUT_ROOT/bootstrap.sh")"
159     unset PW_CHECKOUT_ROOT
160   # Shell: bash.
161   elif test -n "$BASH"; then
162     PROJ_SETUP_SCRIPT_PATH="$(_python_abspath "$BASH_SOURCE")"
163   # Shell: zsh.
164   elif test -n "$ZSH_NAME"; then
165     PROJ_SETUP_SCRIPT_PATH="$(_python_abspath "${(%):-%N}")"
166   # Shell: dash.
167   elif test ${0##*/} = dash; then
168     PROJ_SETUP_SCRIPT_PATH="$(_python_abspath \
169       "$(lsof -p $$ -Fn0 | tail -1 | sed 's#^[^/]*##;')")"
170   # If everything else fails, try $0. It could work.
171   else
172     PROJ_SETUP_SCRIPT_PATH="$(_python_abspath "$0")"
173   fi
174
175You may also wish to check if the user is attempting to execute `bootstrap.sh`
176instead of sourcing it. Executing `bootstrap.sh` would download everything
177required for the environment, but cannot modify the environment of the parent
178process. To check for this add the following.
179
180.. code-block:: bash
181
182   # Check if this file is being executed or sourced.
183   _pw_sourced=0
184   # If not running in Pigweed's automated testing infrastructure the
185   # SWARMING_BOT_ID check is unnecessary.
186   if [ -n "$SWARMING_BOT_ID" ]; then
187     # If set we're running on swarming and don't need this check.
188     _pw_sourced=1
189   elif [ -n "$ZSH_EVAL_CONTEXT" ]; then
190     case $ZSH_EVAL_CONTEXT in *:file) _pw_sourced=1;; esac
191   elif [ -n "$KSH_VERSION" ]; then
192     [ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != \
193       "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] \
194       && _pw_sourced=1
195   elif [ -n "$BASH_VERSION" ]; then
196     (return 0 2>/dev/null) && _pw_sourced=1
197   else  # All other shells: examine $0 for known shell binary filenames
198     # Detects `sh` and `dash`; add additional shell filenames as needed.
199     case ${0##*/} in sh|dash) _pw_sourced=1;; esac
200   fi
201
202   _pw_eval_sourced "$_pw_sourced"
203
204Downstream Projects Using Different Packages
205********************************************
206Projects depending on Pigweed but using additional or different packages should
207copy the Pigweed `sample project`'s ``bootstrap.sh`` and ``config.json`` and
208update the call to ``pw_bootstrap``. Search for "downstream" for other places
209that may require changes, like setting the ``PW_ROOT`` and ``PW_PROJECT_ROOT``
210environment variables. Explanations of parts of ``config.json`` are described
211here.
212
213.. _sample project: https://pigweed.googlesource.com/pigweed/sample_project/+/HEAD
214
215``root_variable``
216  Variable used to point to the root of the source tree. Optional, can always
217  use ``PW_PROJECT_ROOT`` instead. (That variable will be set regardless of
218  whether this is provided.)
219
220``cipd_package_files``
221  CIPD package file. JSON file consisting of a list of additional CIPD package
222  files to import and a list of dictionaries with "path", "platforms", "subdir",
223  "tags", and "version_file" keys. Both top-level lists are optional. An
224  example is below. Only "path", "platforms", and "tags" are required. If
225  "version_file" is specified then ``pw doctor`` will fail if that version file
226  is not present. If "subdir" is specified then this packages will be installed
227  in a subdirectory of the directory created for packages in this file.
228
229.. code-block:: json
230
231   {
232     "included_files": [
233       "foo.json"
234     ],
235     "packages": [
236       {
237         "path": "infra/3pp/tools/go/${platform}",
238         "platforms": [
239             "linux-amd64",
240             "linux-arm64",
241             "mac-amd64",
242             "windows-amd64"
243         ],
244         "subdir": "pa/th",
245         "tags": [
246           "version:2@1.16.3"
247         ],
248         "version_file": ".versions/go.cipd_version"
249       }
250     ]
251   }
252
253``virtualenv.gn_args``
254  Any necessary GN args to be used when installing Python packages.
255
256``virtualenv.gn_targets``
257  Target for installing Python packages. Downstream projects will need to
258  create targets to install their packages or only use Pigweed Python packages.
259
260``virtualenv.gn_root``
261  The root directory of your GN build tree, relative to ``PW_PROJECT_ROOT``.
262  This is the directory your project's ``.gn`` file is located in. If you're
263  only installing Pigweed Python packages, use the location of the Pigweed
264  submodule.
265
266``virtualenv.requirements``
267  A list of Python Pip requirements files for installing into the Pigweed
268  virtualenv. Each file will be passed as additional ``--requirement`` argument
269  to a single ```pip install`` at the beginning of bootstrap's ``Python
270  environment`` setup stage. See the `Requirements Files documentation`_ for
271  details on what can be specified using requirements files.
272
273``virtualenv.constraints``
274  A list of Python Pip constraints files. These constraints will be passed to
275  every ``pip`` invocation as an additional ``--constraint`` argument during
276  bootstrap.  virtualenv. See the `Constraints Files documentation`_ for details
277  on formatting.
278
279``virtualenv.system_packages``
280  A boolean value that can be used the give the Python virtual environment
281  access to the system site packages. Defaults to ``false``.
282
283``optional_submodules``
284  By default environment setup will check that all submodules are present in
285  the checkout. Any submodules in this list are excluded from that check.
286
287``required_submodules``
288  If this is specified instead of ``optional_submodules`` bootstrap will only
289  complain if one of the required submodules is not present. Combining this
290  with ``optional_submodules`` is not supported.
291
292``pw_packages``
293  A list of packages to install using :ref:`pw_package <module-pw_package>`
294  after the rest of bootstrap completes.
295
296``gni_file``
297  Location to write a ``.gni`` file containing paths to many things within the
298  environment directory. Defaults to
299  ``build_overrides/pigweed_environment.gni``.
300
301``json_file``
302  Location to write a ``.json`` file containing step-by-step modifications to
303  the environment, for reading by tools that don't inherit an environment from
304  a sourced ``bootstrap.sh``.
305
306``rosetta``
307  Whether to use Rosetta to use amd64 packages on arm64 Macs. Accepted values
308  are  ``never``, ``allow``, and ``force``. For now, ``allow`` means ``force``.
309  At some point in the future ``allow`` will be changed to mean ``never``.
310
311An example of a config file is below.
312
313.. code-block:: json
314
315   {
316     "root_variable": "EXAMPLE_ROOT",
317     "cipd_package_files": [
318       "pigweed/pw_env_setup/py/pw_env_setup/cipd_setup/pigweed.json",
319       "pigweed/pw_env_setup/py/pw_env_setup/cipd_setup/luci.json"
320       "tools/myprojectname.json"
321     ],
322     "virtualenv": {
323       "gn_root": ".",
324       "gn_targets": [
325         ":python.install",
326       ],
327       "system_packages": false
328     },
329     "pw_packages": [],
330     "optional_submodules": [
331       "optional/submodule/one",
332       "optional/submodule/two"
333     ],
334     "gni_file": "tools/environment.gni",
335     "json_file": "tools/environment.json",
336     "rosetta": "allow"
337   }
338
339Only the packages necessary for almost all projects based on Pigweed are
340included in the ``pigweed.json`` file. A number of other files are present in
341that directory for projects that need more than the minimum. Internal-Google
342projects using LUCI should at least include ``luci.json``.
343
344In case the CIPD packages need to be referenced from other scripts, variables
345like ``PW_${BASENAME}_CIPD_INSTALL_DIR`` point to the CIPD install directories,
346where ``${BASENAME}`` is ``"PIGWEED"`` for
347``"pigweed/pw_env_setup/py/pw_env_setup/cipd_setup/pigweed.json"`` and
348``"LUCI"`` for
349``"pigweed/pw_env_setup/py/pw_env_setup/cipd_setup/luci.json"``. This example
350would set the following environment variables.
351
352- ``PW_LUCI_CIPD_INSTALL_DIR``
353- ``PW_MYPROJECTNAME_CIPD_INSTALL_DIR``
354- ``PW_PIGWEED_CIPD_INSTALL_DIR``
355
356These directories are also referenced in the gni_file specified by the
357environment config file as ``dir_cipd_${BASENAME}``. This allows the GN build to
358reliably reference these directories without using GN ``getenv()`` calls or
359hardcoding paths.
360
361In addition, ``PW_${BASENAME}_CIPD_INSTALL_DIR`` and
362``PW_${BASENAME}_CIPD_INSTALL_DIR/bin`` are both added to ``PATH`` for each
363package directory.
364
365If multiple packages install executables with the same name, the file mentioned
366last topologically takes priority. For example, with the file contents below,
367``d.json``'s entries will appear in ``PATH`` before ``c.json``'s, which will
368appear before ``b.json``'s, which will appear before ``a.json``'s.
369
370``config.json``
371  ``{"cipd_package_files": ["a.json", "b.json", "d.json"], ...}``
372
373``a.json``
374  ``{"package_files": [...]}``
375
376``b.json``
377  ``{"included_files": ["c.json"], "package_files": [...]}``
378
379``c.json``
380  ``{"package_files": [...]}``
381
382``d.json``
383  ``{"package_files": [...]}``
384
385Pinning Python Packages
386***********************
387Python modules usually express dependencies as ranges, which makes it easier to
388install many Python packages that might otherwise have conflicting dependencies.
389However, this means version of packages can often change underneath us and
390builds will not be hermetic.
391
392To ensure versions don't change without approval, Pigweed by default pins the
393versions of packages it depends on using a `pip constraints file`_. To pin the
394versions of additional packages your project depends on, run
395``pw python-packages list <path/to/constraints/file>`` and then add
396``pw_build_PIP_CONSTRAINTS = ["//path/to/constraints/file"]`` to your project's
397``.gn`` file (see `Pigweed's .gn file`_ for an example).
398
399.. _pip constraints file: https://pip.pypa.io/en/stable/user_guide/#constraints-files
400.. _default constraints: https://cs.pigweed.dev/pigweed/+/main:pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list
401.. _Pigweed's .gn file: https://cs.pigweed.dev/pigweed/+/main:.gn
402
403To update packages, set ``pw_build_PIP_CONSTRAINTS = []``, delete the
404environment, and bootstrap again. Then run the ``list`` command from above
405again, and run ``pw presubmit``.
406
407Environment Variables
408*********************
409Input Variables
410---------------
411The following environment variables affect env setup behavior. Most users will
412never need to set these.
413
414``CIPD_CACHE_DIR``
415  Location of CIPD cache dir. Read by CIPD, but if unset will be defaulted to
416  ``$HOME/.cipd-cache-dir``.
417
418``PW_ACTIVATE_SKIP_CHECKS``
419  If set, skip running ``pw doctor`` at end of bootstrap/activate. Intended to
420  be used by automated tools but not interactively.
421
422``PW_BANNER_FUNC``
423  Command to print a banner at the beginning of bootstrap.
424
425``PW_BOOTSTRAP_PYTHON``
426  Python executable to be used, for example "python2" or "python3". Defaults to
427  "python".
428
429``PW_CIPD_SERVICE_ACCOUNT_JSON``
430  Value to pass as ``-service-account-json`` to CIPD invocations. This should
431  point either to a service account JSON key file, or be the magical value
432  ``:gce`` to tell the tool to fetch tokens from GCE metadata server.
433
434``PW_ENVIRONMENT_ROOT``
435  Location to which packages are installed. Defaults to ``environment`` folder
436  within the checkout root. This variable is cleared after environment setup is
437  complete.
438
439``PW_ENVSETUP_DISABLE_SPINNER``
440  Disable the spinner during env setup. Intended to be used when the output is
441  being redirected to a log.
442
443``PW_ENVSETUP_DISABLE_SPINNER``
444  Disable the console spinner that runs when waiting for env setup steps to
445  complete.
446
447``PW_ENVSETUP_NO_BANNER``
448  Skip printing the banner.
449
450``PW_ENVSETUP_QUIET``
451  Disables all non-error output.
452
453``PW_PROJECT_ROOT``
454  The absolute path of the project using Pigweed's env setup. For Pigweed this
455  is the same as ``PW_ROOT``. This should be set by the project's bootstrap
456  script.
457
458``PW_ROOT``
459  The absolute path to the Pigweed repository within ``PW_PROJECT_ROOT``. This
460  should be set by the project's bootstrap script.
461
462Output Variables
463----------------
464The following environment variables are set by env setup.
465
466``PATH``
467  System executable search path. Many of the environment variables below are
468  also added to this variable.
469
470``_PW_ACTUAL_ENVIRONMENT_ROOT``
471  Location the environment was installed into. Separate from
472  ``PW_ENVIRONMENT_ROOT`` because setting that implicitly and switching to
473  another project directory causes unexpected behavior.
474
475``PW_CIPD_INSTALL_DIR``
476  Top-level CIPD install directory. This is where the ``cipd`` executable is.
477
478``PW_*_CIPD_INSTALL_DIR``
479  Each CIPD package file is installed into its own directory. This allows other
480  tools to determine what those directories are. The ``*`` is replaced with an
481  all-caps version of the basename of the package file, without the extension.
482  (E.g., "path/foo.json" becomes ``PW_FOO_CIPD_INSTALL_DIR``.)
483
484``PW_PACKAGE_ROOT``
485  Location that packages installed by ``pw package`` will be installed to.
486
487``VIRTUAL_ENV``
488  Path to Pigweed's virtualenv.
489
490Non-Shell Environments
491**********************
492If using this outside of bash—for example directly from an IDE or CI
493system—users can process the ``actions.json`` file that's generated in the
494location specified by the environment config. It lists variables to set, clear,
495and modify. An example ``actions.json`` is shown below. The "append" and
496"prepend" actions are listed in the order they should be applied, so the
497``<pigweed-root>/out/host/host_tools`` entry should be at the beginning of
498``PATH`` and not in the middle somewhere.
499
500.. code-block:: json
501
502   {
503       "modify": {
504           "PATH": {
505               "append": [],
506               "prepend": [
507                   "<pigweed-root>/environment/cipd",
508                   "<pigweed-root>/environment/cipd/pigweed",
509                   "<pigweed-root>/environment/cipd/pigweed/bin",
510                   "<pigweed-root>/environment/cipd/luci",
511                   "<pigweed-root>/environment/cipd/luci/bin",
512                   "<pigweed-root>/environment/pigweed-venv/bin",
513                   "<pigweed-root>/out/host/host_tools"
514               ],
515               "remove": []
516           }
517       },
518       "set": {
519           "PW_PROJECT_ROOT": "<pigweed-root>",
520           "PW_ROOT": "<pigweed-root>",
521           "_PW_ACTUAL_ENVIRONMENT_ROOT": "<pigweed-root>/environment",
522           "PW_CIPD_INSTALL_DIR": "<pigweed-root>/environment/cipd",
523           "CIPD_CACHE_DIR": "<home>/.cipd-cache-dir",
524           "PW_PIGWEED_CIPD_INSTALL_DIR": "<pigweed-root>/environment/cipd/pigweed",
525           "PW_LUCI_CIPD_INSTALL_DIR": "<pigweed-root>/environment/cipd/luci",
526           "VIRTUAL_ENV": "<pigweed-root>/environment/pigweed-venv",
527           "PYTHONHOME": null,
528           "__PYVENV_LAUNCHER__": null
529       }
530   }
531
532Many of these variables are directly exposed to the GN build as well, through
533the GNI file specified in the environment config file.
534
535.. code-block::
536
537   declare_args() {
538     pw_env_setup_CIPD_PIGWEED = "<environment-root>/cipd/packages/pigweed"
539     pw_env_setup_CIPD_LUCI = "<environment-root>/cipd/packages/luci"
540     pw_env_setup_VIRTUAL_ENV = "<environment-root>/pigweed-venv"
541     pw_env_setup_PACKAGE_ROOT = "<environment-root>/packages"
542   }
543
544It's straightforward to use these variables.
545
546.. code-block:: cpp
547
548   import("//build_overrides/pigweed_environment.gni")
549
550   deps = [ "$pw_env_setup_CIPD_PIGWEED/..." ]
551
552Implementation
553**************
554The environment is set up by installing CIPD and Python packages in
555``PW_ENVIRONMENT_ROOT`` or ``<checkout>/environment``, and saving modifications
556to environment variables in setup scripts in those directories. To support
557multiple operating systems this is done in an operating system-agnostic manner
558and then written into operating system-specific files to be sourced now and in
559the future when running ``activate.sh`` instead of ``bootstrap.sh``. In the
560future these could be extended to C shell and PowerShell. A logical mapping of
561high-level commands to system-specific initialization files is shown below.
562
563.. image:: doc_resources/pw_env_setup_output.png
564   :alt: Mapping of high-level commands to system-specific commands.
565   :align: left
566
567.. _Requirements Files documentation: https://pip.pypa.io/en/stable/user_guide/#requirements-files
568.. _Constraints Files documentation: https://pip.pypa.io/en/stable/user_guide/#constraints-files
569