blob: 7ae67827253aa8b0003e77bf3d2ab2f69c167db7 [file] [log] [blame]
.. _module-pw_env_setup:
------------
pw_env_setup
------------
A classic problem in the embedded space is reducing the time from git clone
to having a binary executing on a device. The issue is that an entire suite
of tools is needed for non-trivial production embedded projects. For example:
- A C++ compiler for your target device, and also for your host
- A build system or three; for example, GN, Ninja, CMake, Bazel
- A code formatting program like clang-format
- A debugger like OpenOCD to flash and debug your embedded device
- A known Python version with known modules installed for scripting
- A Go compiler for the Go-based command line tools
...and so on
In the server space, container solutions like Docker or Podman solve this;
however, in our experience container solutions are a mixed bag for embedded
systems development where one frequently needs access to native system
resources like USB devices, or must operate on Windows.
``pw_env_setup`` is our compromise solution for this problem that works on Mac,
Windows, and Linux. It leverages the Chrome packaging system `CIPD`_ to
bootstrap a Python installation, which in turn inflates a virtual
environment. The tooling is installed into your workspace, and makes no
changes to your system. This tooling is designed to be reused by any
project.
.. _CIPD: https://github.com/luci/luci-go/tree/HEAD/cipd
Users interact with ``pw_env_setup`` with two commands: ``. bootstrap.sh`` and
``. activate.sh``. The bootstrap command always pulls down the current versions
of CIPD packages and sets up the Python virtual environment. The activate
command reinitializes a previously configured environment, and if none is found,
runs bootstrap.
.. note::
On Windows the scripts used to set up the environment are ``bootstrap.bat``
and ``activate.bat``. For simplicity they will be referred to with the ``.sh``
endings unless the distinction is relevant.
.. warning::
At this time ``pw_env_setup`` works for us, but isnt well tested. We dont
suggest relying on it just yet. However, we are interested in experience
reports; if you give it a try, please `send us a note`_ about your
experience.
.. _send us a note: pigweed@googlegroups.com
On POSIX systems, the environment can be deactivated by running ``deactivate``.
==================================
Using pw_env_setup in your project
==================================
Downstream Projects Using Pigweed's Packages
********************************************
Projects using Pigweed can leverage ``pw_env_setup`` to install Pigweed's
dependencies or their own dependencies. Projects that only want to use Pigweed's
dependencies without modifying them can just source Pigweed's ``bootstrap.sh``
and ``activate.sh`` scripts.
An example of what your project's `bootstrap.sh` could look like is below. This
assumes `bootstrap.sh` is at the top level of your repository.
.. code-block:: bash
# Do not include a "#!" line, this must be sourced and not executed.
# This assumes the user is sourcing this file from it's parent directory. See
# below for a more flexible way to handle this.
PROJ_SETUP_SCRIPT_PATH="$(pwd)/bootstrap.sh"
export PW_PROJECT_ROOT="$(_python_abspath "$(dirname "$PROJ_SETUP_SCRIPT_PATH")")"
# You may wish to check if the user is attempting to execute this script
# instead of sourcing it. See below for an example of how to handle that
# situation.
# Source Pigweed's bootstrap utility script.
# Using '.' instead of 'source' for POSIX compatibility. Since users don't use
# dash directly, using 'source' in most documentation so users don't get
# confused and try to `./bootstrap.sh`.
. "$PW_PROJECT_ROOT/third_party/pigweed/pw_env_setup/util.sh"
pw_check_root "$PW_ROOT"
_PW_ACTUAL_ENVIRONMENT_ROOT="$(pw_get_env_root)"
export _PW_ACTUAL_ENVIRONMENT_ROOT
SETUP_SH="$_PW_ACTUAL_ENVIRONMENT_ROOT/activate.sh"
pw_bootstrap --args... # See below for details about args.
pw_finalize bootstrap "$SETUP_SH"
Bazel Usage
-----------
It is possible to pull in a CIPD dependency into Bazel using WORKSPACE rules
rather than using `bootstrap.sh`. e.g.
.. code:: python
# WORKSPACE
load("//pw_env_setup/bazel/cipd_setup:cipd_rules.bzl", "pigweed_deps")
# Setup CIPD client and packages.
# Required by: pigweed.
# Used by modules: all.
pigweed_deps()
load("@cipd_deps//:cipd_init.bzl", "cipd_init")
cipd_init()
This will make the entire set of Pigweeds remote repositories available
to your project. Though these repositories will only be donwloaded if
you use them. To get a full list of the remote repositories that this
configures, run:
.. code:: sh
bazel query //external:all | grep cipd_
All files and executables in each CIPD remote repository is exported
and visible either directely (`@cipd_<dep>//:<file>`) or from 'all' filegroup
(`@cipd_<dep>//:all`).
From here it is possible to get access to the Bloaty binaries using the
following command. For example;
.. code:: sh
bazel run @cipd_pigweed_third_party_bloaty_embedded_linux_amd64//:bloaty \
-- --help
User-Friendliness
-----------------
You may wish to allow sourcing `bootstrap.sh` from a different directory. In
that case you'll need the following at the top of `bootstrap.sh`.
.. code-block:: bash
_python_abspath () {
python -c "import os.path; print(os.path.abspath('$@'))"
}
# Use this code from Pigweed's bootstrap to find the path to this script when
# sourced. This should work with common shells. PW_CHECKOUT_ROOT is only used in
# presubmit tests with strange setups, and can be omitted if you're not using
# Pigweed's automated testing infrastructure.
if test -n "$PW_CHECKOUT_ROOT"; then
PROJ_SETUP_SCRIPT_PATH="$(_python_abspath "$PW_CHECKOUT_ROOT/bootstrap.sh")"
unset PW_CHECKOUT_ROOT
# Shell: bash.
elif test -n "$BASH"; then
PROJ_SETUP_SCRIPT_PATH="$(_python_abspath "$BASH_SOURCE")"
# Shell: zsh.
elif test -n "$ZSH_NAME"; then
PROJ_SETUP_SCRIPT_PATH="$(_python_abspath "${(%):-%N}")"
# Shell: dash.
elif test ${0##*/} = dash; then
PROJ_SETUP_SCRIPT_PATH="$(_python_abspath \
"$(lsof -p $$ -Fn0 | tail -1 | sed 's#^[^/]*##;')")"
# If everything else fails, try $0. It could work.
else
PROJ_SETUP_SCRIPT_PATH="$(_python_abspath "$0")"
fi
You may also wish to check if the user is attempting to execute `bootstrap.sh`
instead of sourcing it. Executing `bootstrap.sh` would download everything
required for the environment, but cannot modify the environment of the parent
process. To check for this add the following.
.. code-block:: bash
# Check if this file is being executed or sourced.
_pw_sourced=0
# If not running in Pigweed's automated testing infrastructure the
# SWARMING_BOT_ID check is unnecessary.
if [ -n "$SWARMING_BOT_ID" ]; then
# If set we're running on swarming and don't need this check.
_pw_sourced=1
elif [ -n "$ZSH_EVAL_CONTEXT" ]; then
case $ZSH_EVAL_CONTEXT in *:file) _pw_sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
[ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != \
"$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] \
&& _pw_sourced=1
elif [ -n "$BASH_VERSION" ]; then
(return 0 2>/dev/null) && _pw_sourced=1
else # All other shells: examine $0 for known shell binary filenames
# Detects `sh` and `dash`; add additional shell filenames as needed.
case ${0##*/} in sh|dash) _pw_sourced=1;; esac
fi
_pw_eval_sourced "$_pw_sourced"
Downstream Projects Using Different Packages
********************************************
Projects depending on Pigweed but using additional or different packages should
copy the Pigweed `sample project`'s ``bootstrap.sh`` and ``config.json`` and
update the call to ``pw_bootstrap``. Search for "downstream" for other places
that may require changes, like setting the ``PW_ROOT`` and ``PW_PROJECT_ROOT``
environment variables. Explanations of parts of ``config.json`` are described
here.
.. _sample project: https://pigweed.googlesource.com/pigweed/sample_project/+/HEAD
``root_variable``
Variable used to point to the root of the source tree. Optional, can always
use ``PW_PROJECT_ROOT`` instead. (That variable will be set regardless of
whether this is provided.)
``cipd_package_files``
CIPD package file. JSON file consisting of a list of additional CIPD package
files to import and a list of dictionaries with "path", "platforms", "subdir",
"tags", and "version_file" keys. Both top-level lists are optional. An
example is below. Only "path", "platforms", and "tags" are required. If
"version_file" is specified then ``pw doctor`` will fail if that version file
is not present. If "subdir" is specified then this packages will be installed
in a subdirectory of the directory created for packages in this file.
.. code-block:: json
{
"included_files": [
"foo.json"
],
"packages": [
{
"path": "infra/3pp/tools/go/${platform}",
"platforms": [
"linux-amd64",
"linux-arm64",
"mac-amd64",
"windows-amd64"
],
"subdir": "pa/th",
"tags": [
"version:2@1.16.3"
],
"version_file": ".versions/go.cipd_version"
}
]
}
``virtualenv.gn_args``
Any necessary GN args to be used when installing Python packages.
``virtualenv.gn_targets``
Target for installing Python packages. Downstream projects will need to
create targets to install their packages or only use Pigweed Python packages.
``virtualenv.gn_root``
The root directory of your GN build tree, relative to ``PW_PROJECT_ROOT``.
This is the directory your project's ``.gn`` file is located in. If you're
only installing Pigweed Python packages, use the location of the Pigweed
submodule.
``virtualenv.requirements``
A list of Python Pip requirements files for installing into the Pigweed
virtualenv. Each file will be passed as additional ``--requirement`` argument
to a single ```pip install`` at the beginning of bootstrap's ``Python
environment`` setup stage. See the `Requirements Files documentation`_ for
details on what can be specified using requirements files.
``virtualenv.constraints``
A list of Python Pip constraints files. These constraints will be passed to
every ``pip`` invocation as an additional ``--constraint`` argument during
bootstrap. virtualenv. See the `Constraints Files documentation`_ for details
on formatting.
``virtualenv.system_packages``
A boolean value that can be used the give the Python virtual environment
access to the system site packages. Defaults to ``false``.
``optional_submodules``
By default environment setup will check that all submodules are present in
the checkout. Any submodules in this list are excluded from that check.
``required_submodules``
If this is specified instead of ``optional_submodules`` bootstrap will only
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.
``gni_file``
Location to write a ``.gni`` file containing paths to many things within the
environment directory. Defaults to
``build_overrides/pigweed_environment.gni``.
``json_file``
Location to write a ``.json`` file containing step-by-step modifications to
the environment, for reading by tools that don't inherit an environment from
a sourced ``bootstrap.sh``.
``rosetta``
Whether to use Rosetta to use amd64 packages on arm64 Macs. Accepted values
are ``never``, ``allow``, and ``force``. For now, ``allow`` means ``force``.
At some point in the future ``allow`` will be changed to mean ``never``.
An example of a config file is below.
.. code-block:: json
{
"root_variable": "EXAMPLE_ROOT",
"cipd_package_files": [
"pigweed/pw_env_setup/py/pw_env_setup/cipd_setup/pigweed.json",
"pigweed/pw_env_setup/py/pw_env_setup/cipd_setup/luci.json"
"tools/myprojectname.json"
],
"virtualenv": {
"gn_root": ".",
"gn_targets": [
":python.install",
],
"system_packages": false
},
"pw_packages": [],
"optional_submodules": [
"optional/submodule/one",
"optional/submodule/two"
],
"gni_file": "tools/environment.gni",
"json_file": "tools/environment.json",
"rosetta": "allow"
}
In case the CIPD packages need to be referenced from other scripts, variables
like ``PW_${BASENAME}_CIPD_INSTALL_DIR`` point to the CIPD install directories,
where ``${BASENAME}`` is "PIGWEED" for
"pigweed/pw_env_setup/py/pw_env_setup/cipd_setup/pigweed.json" and "LUCI" for
"pigweed/pw_env_setup/py/pw_env_setup/cipd_setup/luci.json". This example would
set the following environment variables.
- ``PW_LUCI_CIPD_INSTALL_DIR``
- ``PW_MYPROJECTNAME_CIPD_INSTALL_DIR``
- ``PW_PIGWEED_CIPD_INSTALL_DIR``
These directories are also referenced in the gni_file specified by the
environment config file as ``dir_cipd_${BASENAME}``. This allows the GN build to
reliably reference these directories without using GN ``getenv()`` calls or
hardcoding paths.
In addition, ``PW_${BASENAME}_CIPD_INSTALL_DIR`` and
``PW_${BASENAME}_CIPD_INSTALL_DIR/bin`` are both added to ``PATH`` for each
package directory.
If multiple packages install executables with the same name, the file mentioned
last topologically takes priority. For example, with the file contents below,
``d.json``'s entries will appear in ``PATH`` before ``c.json``'s, which will
appear before ``b.json``'s, which will appear before ``a.json``'s.
``config.json``
``{"cipd_package_files": ["a.json", "b.json", "d.json"], ...}``
``a.json``
``{"package_files": [...]}``
``b.json``
``{"included_files": ["c.json"], "package_files": [...]}``
``c.json``
``{"package_files": [...]}``
``d.json``
``{"package_files": [...]}``
Pinning Python Packages
***********************
Python modules usually express dependencies as ranges, which makes it easier to
install many Python packages that might otherwise have conflicting dependencies.
However, this means version of packages can often change underneath us and
builds will not be hermetic.
To ensure versions don't change without approval, Pigweed by default pins the
versions of packages it depends on using a `pip constraints file`_. To pin the
versions of additional packages your project depends on, run
``pw python-packages list <path/to/constraints/file>`` and then add
``pw_build_PIP_CONSTRAINTS = ["//path/to/constraints/file"]`` to your project's
``.gn`` file (see `Pigweed's .gn file`_ for an example).
.. _pip constraints file: https://pip.pypa.io/en/stable/user_guide/#constraints-files
.. _default constraints: https://cs.pigweed.dev/pigweed/+/main:pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list
.. _Pigweed's .gn file: https://cs.pigweed.dev/pigweed/+/main:.gn
To update packages, set ``pw_build_PIP_CONSTRAINTS = []``, delete the
environment, and bootstrap again. Then run the ``list`` command from above
again, and run ``pw presubmit``.
Environment Variables
*********************
Input Variables
---------------
The following environment variables affect env setup behavior. Most users will
never need to set these.
``CIPD_CACHE_DIR``
Location of CIPD cache dir. Read by CIPD, but if unset will be defaulted to
``$HOME/.cipd-cache-dir``.
``PW_ACTIVATE_SKIP_CHECKS``
If set, skip running ``pw doctor`` at end of bootstrap/activate. Intended to
be used by automated tools but not interactively.
``PW_BANNER_FUNC``
Command to print a banner at the beginning of bootstrap.
``PW_BOOTSTRAP_PYTHON``
Python executable to be used, for example "python2" or "python3". Defaults to
"python".
``PW_CIPD_SERVICE_ACCOUNT_JSON``
Value to pass as ``-service-account-json`` to CIPD invocations. This should
point either to a service account JSON key file, or be the magical value
``:gce`` to tell the tool to fetch tokens from GCE metadata server.
``PW_ENVIRONMENT_ROOT``
Location to which packages are installed. Defaults to ``.environment`` folder
within the checkout root.
``PW_ENVSETUP_DISABLE_SPINNER``
Disable the spinner during env setup. Intended to be used when the output is
being redirected to a log.
``PW_ENVSETUP_DISABLE_SPINNER``
Disable the console spinner that runs when waiting for env setup steps to
complete.
``PW_ENVSETUP_NO_BANNER``
Skip printing the banner.
``PW_ENVSETUP_QUIET``
Disables all non-error output.
``PW_PROJECT_ROOT``
The absolute path of the project using Pigweed's env setup. For Pigweed this
is the same as ``PW_ROOT``. This should be set by the project's bootstrap
script.
``PW_ROOT``
The absolute path to the Pigweed repository within ``PW_PROJECT_ROOT``. This
should be set by the project's bootstrap script.
Output Variables
----------------
The following environment variables are set by env setup.
``PATH``
System executable search path. Many of the environment variables below are
also added to this variable.
``_PW_ACTUAL_ENVIRONMENT_ROOT``
Location the environment was installed into. Separate from
``PW_ENVIRONMENT_ROOT`` because setting that implicitly and switching to
another project directory causes unexpected behavior.
``PW_CIPD_INSTALL_DIR``
Top-level CIPD install directory. This is where the ``cipd`` executable is.
``PW_*_CIPD_INSTALL_DIR``
Each CIPD package file is installed into its own directory. This allows other
tools to determine what those directories are. The ``*`` is replaced with an
all-caps version of the basename of the package file, without the extension.
(E.g., "path/foo.json" becomes ``PW_FOO_CIPD_INSTALL_DIR``.)
``PW_PACKAGE_ROOT``
Location that packages installed by ``pw package`` will be installed to.
``VIRTUAL_ENV``
Path to Pigweed's virtualenv.
Non-Shell Environments
**********************
If using this outside of bash—for example directly from an IDE or CI
system—users can process the ``actions.json`` file that's generated in the
location specified by the environment config. It lists variables to set, clear,
and modify. An example ``actions.json`` is shown below. The "append" and
"prepend" actions are listed in the order they should be applied, so the
``<pigweed-root>/out/host/host_tools`` entry should be at the beginning of
``PATH`` and not in the middle somewhere.
.. code-block:: json
{
"modify": {
"PATH": {
"append": [],
"prepend": [
"<pigweed-root>/.environment/cipd",
"<pigweed-root>/.environment/cipd/pigweed",
"<pigweed-root>/.environment/cipd/pigweed/bin",
"<pigweed-root>/.environment/cipd/luci",
"<pigweed-root>/.environment/cipd/luci/bin",
"<pigweed-root>/.environment/pigweed-venv/bin",
"<pigweed-root>/out/host/host_tools"
],
"remove": []
}
},
"set": {
"PW_PROJECT_ROOT": "<pigweed-root>",
"PW_ROOT": "<pigweed-root>",
"_PW_ACTUAL_ENVIRONMENT_ROOT": "<pigweed-root>/.environment",
"PW_CIPD_INSTALL_DIR": "<pigweed-root>/.environment/cipd",
"CIPD_CACHE_DIR": "<home>/.cipd-cache-dir",
"PW_PIGWEED_CIPD_INSTALL_DIR": "<pigweed-root>/.environment/cipd/pigweed",
"PW_LUCI_CIPD_INSTALL_DIR": "<pigweed-root>/.environment/cipd/luci",
"VIRTUAL_ENV": "<pigweed-root>/.environment/pigweed-venv",
"PYTHONHOME": null,
"__PYVENV_LAUNCHER__": null
}
}
Many of these variables are directly exposed to the GN build as well, through
the GNI file specified in the environment config file.
.. code-block::
declare_args() {
pw_env_setup_CIPD_PIGWEED = "<environment-root>/cipd/packages/pigweed"
pw_env_setup_CIPD_LUCI = "<environment-root>/cipd/packages/luci"
pw_env_setup_VIRTUAL_ENV = "<environment-root>/pigweed-venv"
pw_env_setup_PACKAGE_ROOT = "<environment-root>/packages"
}
It's straightforward to use these variables.
.. code-block:: cpp
import("//build_overrides/pigweed_environment.gni")
deps = [ "$pw_env_setup_CIPD_PIGWEED/..." ]
Implementation
**************
The environment is set up by installing CIPD and Python packages in
``PW_ENVIRONMENT_ROOT`` or ``<checkout>/.environment``, and saving modifications
to environment variables in setup scripts in those directories. To support
multiple operating systems this is done in an operating system-agnostic manner
and then written into operating system-specific files to be sourced now and in
the future when running ``activate.sh`` instead of ``bootstrap.sh``. In the
future these could be extended to C shell and PowerShell. A logical mapping of
high-level commands to system-specific initialization files is shown below.
.. image:: doc_resources/pw_env_setup_output.png
:alt: Mapping of high-level commands to system-specific commands.
:align: left
.. _Requirements Files documentation: https://pip.pypa.io/en/stable/user_guide/#requirements-files
.. _Constraints Files documentation: https://pip.pypa.io/en/stable/user_guide/#constraints-files