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