# CI process _This document is a work-in-progress._ _Please double-check file/function/etc. names for changes, as this document may be out of sync._ ### Dependencies All dependencies (e.g. `apt`, `pip`) should be installed in `install_dependencies()`, following the same pattern as those that already exist. Once a dependency is added/removed, please ensure that if `reload_env` (or similar) is updated if it exists, as CI systems differ on when `~/.bashrc` et al. are reloaded, if at all. (And they are not necessarily idempotent.) ### Bazel, environment variables, and caching Any environment variables passed to Bazel actions (e.g. `PATH`) should be idempotent to hit the Bazel cache. If a different `PATH` gets passed to a Bazel action, Bazel will not hit the cache, and you might trigger a full rebuild when you really expect an incremental (or no-op) build for an option (say `pip install -e .` after `bazel build //...`). ### Invocation The CI system (such as Travis) must _source_ (_not_ execute) `ci/travis/ci.sh` and pass the action(s) to execute. The script either handles the work or dispatches it to other script(s) as it deems appropriate. This helps ensure any environment setup/teardown is handled appropriately. ### Development best practices & pitfalls (read before adding a new script) Before adding new scripts, please read this section. First, please consider modifying an existing script instead (e.g. add your code as a separate function). Adding new scripts has a number of pitfalls that easily take hours (even days) to track down and fix: - When _calling_ other scripts (as executables), environment variables (like `PATH`) _cannot_ propagate back up to the caller. Often, the caller expects such variables to be updated. - When _sourcing_ other scripts, global state (`ROOT_DIR`, `main`, `set -e`, etc.) may be overwritten silently, causing unexpected behavior. The following practices can avoid such pitfalls while maintaining intuitive control flow: - Put all environment-modifying functions in the _same_ shell script, so that their invocation behaves intuitively. (The sheer length of the script is a secondary concern and can be mitigated by keeping functions modular.) - Avoid adding new scripts if possible. If it's necessary that you do so, call them instead of sourcing them. - Always add code inside a function, not at global scope. Use `local` for variables where it makes sense. However, be careful and know the shell rules: for example, e.g. `local x=$(false)` succeeds even under `set -e`. Ultimately, it's best to _only_ add new scripts if they might need to be executed directly by _non-CI_ code, as in that case, they should probably not use CI entrypoints (which assume exclusive control over the machine).