Unverified Commit 53262a98 authored by Hunter Hansen's avatar Hunter Hansen Committed by GitHub

Adds dependency installation procedure to `./orbit.sh` (#514)

# Description

~Adds a new package `orbit_hooks` to `/orbit/docker/orbit_hooks`. This
is a small library of `setuptools.Command` child classes, built at the
beginning of `Dockerfile.base`. They can be referenced in the `setup.py`
of extensions which want hooks, such as ROS or apt packages installed at
Dockerfile build time.~

~The currently existing hooks are `InstallAptDeps` and `InstallRosDeps`.
These will automatically perform certain installation processes:~

~`InstallAptDeps` looks in the extension's `extension.toml` for an
`orbit_hooks` `apt_deps` list, which will be installed during the
`Dockerfile.base` build procedure.~

~`InstallRosDeps` looks in the extension's `extension.toml` for an
`orbit_hooks` `ros_ws` path. In building `Dockerfile.ros2`, we will
perform a `rosdep` installation of any dependencies in ros packages
beneath `ros_ws`.~

~Additionally, I have added the script `check_and_install_deps.py`,
which scans all subdirs in `orbit/source/extensions` for the existence
of certain setup.py cmdclasses (`install_apt_deps`,`install_ros_deps`)
and calls them if they exist.~

~For an extension to make use of this optional install procedure, they
will need to `import orbit_hooks` in their `setup.py` the relevant
command class dict (`INSTALL_ALL_DEPS`, `INSTALL_APT_DEPS`,
`INSTALL_ROS_DEPS`) and to have the corresponding values in their
`extension.toml`. For an example, [this
PR](https://github.com/bdaiinstitute/orbit.eval_sim/pull/36) has the
related modifications to its `extension.toml` and its `setup.py`.~

**Update**:
Adds a new script `/tools/install_deps.py` which has commands for
installing ros and apt dependencies in extensions via options set in the
`extension.toml`.

`install_apt_packages` looks in the extension's `extension.toml` for an
`orbit_settings` `apt_deps` list, which will be installed during the
`Dockerfile.base` build procedure.

`install_rosdep_packages` looks in the extension's `extension.toml` for
an `orbit_settings` `ros_ws` path. In building `Dockerfile.ros2`, we
will perform a `rosdep` installation of any dependencies in ros packages
beneath `ros_ws`.

Added section about this in `developers.rst`, as well as reference to it
in the this [draft
PR](https://github.com/isaac-orbit/orbit.ext_template/pull/18) for
`orbit.ext_template`

## Type of change

- New feature (non-breaking change which adds functionality)

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./orbit.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have run all the tests with `./orbit.sh --test` and they pass
- [ ] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

---------
Signed-off-by: 's avatarHunter Hansen <50837800+hhansen-bdai@users.noreply.github.com>
parent 5d39e0ed
...@@ -7,8 +7,9 @@ ...@@ -7,8 +7,9 @@
# Please check above link for license information. # Please check above link for license information.
# Base image # Base image
ARG ISAACSIM_VERSION ARG ISAACSIM_VERSION_ARG
FROM nvcr.io/nvidia/isaac-sim:${ISAACSIM_VERSION} AS base FROM nvcr.io/nvidia/isaac-sim:${ISAACSIM_VERSION_ARG} AS base
ENV ISAACSIM_VERSION=${ISAACSIM_VERSION_ARG}
# Set default RUN shell to bash # Set default RUN shell to bash
SHELL ["/bin/bash", "-c"] SHELL ["/bin/bash", "-c"]
...@@ -19,11 +20,14 @@ LABEL description="Dockerfile for building and running the Orbit framework insid ...@@ -19,11 +20,14 @@ LABEL description="Dockerfile for building and running the Orbit framework insid
# Arguments # Arguments
# Path to Isaac Sim root folder # Path to Isaac Sim root folder
ARG ISAACSIM_ROOT_PATH ARG ISAACSIM_ROOT_PATH_ARG
ENV ISAACSIM_ROOT_PATH=${ISAACSIM_ROOT_PATH_ARG}
# Path to orbit directory # Path to orbit directory
ARG ORBIT_PATH ARG ORBIT_PATH_ARG
ENV ORBIT_PATH=${ORBIT_PATH_ARG}
# Home dir of docker user, typically '/root' # Home dir of docker user, typically '/root'
ARG DOCKER_USER_HOME ARG DOCKER_USER_HOME_ARG
ENV DOCKER_USER_HOME=${DOCKER_USER_HOME_ARG}
# Set environment variables # Set environment variables
ENV LANG=C.UTF-8 ENV LANG=C.UTF-8
...@@ -46,8 +50,14 @@ COPY ../ ${ORBIT_PATH} ...@@ -46,8 +50,14 @@ COPY ../ ${ORBIT_PATH}
# Set up a symbolic link between the installed Isaac Sim root folder and _isaac_sim in the orbit directory # Set up a symbolic link between the installed Isaac Sim root folder and _isaac_sim in the orbit directory
RUN ln -sf ${ISAACSIM_ROOT_PATH} ${ORBIT_PATH}/_isaac_sim RUN ln -sf ${ISAACSIM_ROOT_PATH} ${ORBIT_PATH}/_isaac_sim
# Install apt dependencies for extensions that declare them in their extension.toml
RUN --mount=type=cache,target=/var/cache/apt \
${ORBIT_PATH}/orbit.sh --install-deps apt && \
apt -y autoremove && apt clean autoclean && \
rm -rf /var/lib/apt/lists/*
# for singularity usage, have to create the directories that will binded # for singularity usage, have to create the directories that will binded
RUN mkdir -p ${ISAACSIM_ROOT_PATH}/kit/cache && \ RUN mkdir -p ${ISAACSIM_ROOT_PATH}/kit/cachecd && \
mkdir -p ${DOCKER_USER_HOME}/.cache/ov && \ mkdir -p ${DOCKER_USER_HOME}/.cache/ov && \
mkdir -p ${DOCKER_USER_HOME}/.cache/pip && \ mkdir -p ${DOCKER_USER_HOME}/.cache/pip && \
mkdir -p ${DOCKER_USER_HOME}/.cache/nvidia/GLCache && \ mkdir -p ${DOCKER_USER_HOME}/.cache/nvidia/GLCache && \
......
...@@ -4,8 +4,6 @@ FROM orbit-base AS ros2 ...@@ -4,8 +4,6 @@ FROM orbit-base AS ros2
# Which ROS2 apt package to install # Which ROS2 apt package to install
ARG ROS2_APT_PACKAGE ARG ROS2_APT_PACKAGE
# Home of the docker user, generally /root
ARG DOCKER_USER_HOME
# ROS2 Humble Apt installations # ROS2 Humble Apt installations
RUN --mount=type=cache,target=/var/cache/apt \ RUN --mount=type=cache,target=/var/cache/apt \
...@@ -24,6 +22,9 @@ RUN --mount=type=cache,target=/var/cache/apt \ ...@@ -24,6 +22,9 @@ RUN --mount=type=cache,target=/var/cache/apt \
ros-humble-rmw-fastrtps-cpp \ ros-humble-rmw-fastrtps-cpp \
# This includes various dev tools including colcon # This includes various dev tools including colcon
ros-dev-tools && \ ros-dev-tools && \
# Install rosdeps for extensions that declare a ros_ws in
# their extension.toml
${ORBIT_PATH}/orbit.sh --install-deps rosdep && \
apt -y autoremove && apt clean autoclean && \ apt -y autoremove && apt clean autoclean && \
rm -rf /var/lib/apt/lists/* && \ rm -rf /var/lib/apt/lists/* && \
# Add sourcing of setup.bash to .bashrc # Add sourcing of setup.bash to .bashrc
......
...@@ -57,7 +57,6 @@ x-default-orbit-volumes: &default-orbit-volumes ...@@ -57,7 +57,6 @@ x-default-orbit-volumes: &default-orbit-volumes
x-default-orbit-environment: &default-orbit-environment x-default-orbit-environment: &default-orbit-environment
- ISAACSIM_PATH=${DOCKER_ORBIT_PATH}/_isaac_sim - ISAACSIM_PATH=${DOCKER_ORBIT_PATH}/_isaac_sim
- ORBIT_PATH=${DOCKER_ORBIT_PATH}
- OMNI_KIT_ALLOW_ROOT=1 - OMNI_KIT_ALLOW_ROOT=1
x-default-orbit-deploy: &default-orbit-deploy x-default-orbit-deploy: &default-orbit-deploy
...@@ -77,10 +76,10 @@ services: ...@@ -77,10 +76,10 @@ services:
context: ../ context: ../
dockerfile: docker/Dockerfile.base dockerfile: docker/Dockerfile.base
args: args:
- ISAACSIM_VERSION=${ISAACSIM_VERSION} - ISAACSIM_VERSION_ARG=${ISAACSIM_VERSION}
- ISAACSIM_ROOT_PATH=${DOCKER_ISAACSIM_ROOT_PATH} - ISAACSIM_ROOT_PATH_ARG=${DOCKER_ISAACSIM_ROOT_PATH}
- ORBIT_PATH=${DOCKER_ORBIT_PATH} - ORBIT_PATH_ARG=${DOCKER_ORBIT_PATH}
- DOCKER_USER_HOME=${DOCKER_USER_HOME} - DOCKER_USER_HOME_ARG=${DOCKER_USER_HOME}
image: orbit-base image: orbit-base
container_name: orbit-base container_name: orbit-base
environment: *default-orbit-environment environment: *default-orbit-environment
...@@ -107,7 +106,6 @@ services: ...@@ -107,7 +106,6 @@ services:
# avoid a warning message when building only the base profile # avoid a warning message when building only the base profile
# with the .env.base file # with the .env.base file
- ROS2_APT_PACKAGE=${ROS2_APT_PACKAGE:-NONE} - ROS2_APT_PACKAGE=${ROS2_APT_PACKAGE:-NONE}
- DOCKER_USER_HOME=${DOCKER_USER_HOME}
image: orbit-ros2 image: orbit-ros2
container_name: orbit-ros2 container_name: orbit-ros2
environment: *default-orbit-environment environment: *default-orbit-environment
......
...@@ -188,6 +188,34 @@ important to note that Omniverse also provides a similar ...@@ -188,6 +188,34 @@ important to note that Omniverse also provides a similar
However, it requires going through the build process and does not support testing of the python module in However, it requires going through the build process and does not support testing of the python module in
standalone applications. standalone applications.
Extension Dependency Management
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Certain extensions may have dependencies which need to be installed before the extension can be run.
While Python dependencies can be expressed via the ``INSTALL_REQUIRES`` array in ``setup.py``, we need
a separate installation pipeline to handle non-Python dependencies. We have therefore created
an additional setup procedure, ``./orbit.sh --install-deps {dep_type}``, which scans the ``extension.toml``
file of the directories under ``/orbit/source/extensions`` for ``apt`` and ``rosdep`` dependencies.
This example ``extension.toml`` has both ``apt_deps`` and ``ros_ws`` specified, so both
``apt`` and ``rosdep`` packages will be installed if ``./orbit.sh --install-deps all``
is passed:
.. code-block:: toml
[orbit_settings]
apt_deps = ["example_package"]
ros_ws = "path/from/extension_root/to/ros_ws"
From the ``apt_deps`` in the above example, the package ``example_package`` would be installed via ``apt``.
From the ``ros_ws``, a ``rosdep install --from-paths {ros_ws}/src --ignore-src`` command will be called.
This will install all the `ROS package.xml dependencies <https://docs.ros.org/en/humble/Tutorials/Intermediate/Rosdep.html>`__
in the directory structure below. Currently the ROS distro is assumed to be ``humble``.
``apt`` deps are automatically installed this way during the build process of the ``Dockerfile.base``,
and ``rosdep`` deps during the build process of ``Dockerfile.ros2``.
Standalone applications Standalone applications
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
......
...@@ -79,13 +79,22 @@ install_orbit_extension() { ...@@ -79,13 +79,22 @@ install_orbit_extension() {
# retrieve the python executable # retrieve the python executable
python_exe=$(extract_python_exe) python_exe=$(extract_python_exe)
# if the directory contains setup.py then install the python module # if the directory contains setup.py then install the python module
if [ -f "$1/setup.py" ]; if [ -f "$1/setup.py" ]; then
then
echo -e "\t module: $1" echo -e "\t module: $1"
${python_exe} -m pip install --editable $1 ${python_exe} -m pip install --editable $1
fi fi
} }
install_extension_deps() {
# retrieve the python executable
set -e
path="$1"
cmd="$2"
python_exe=$(extract_python_exe)
echo -e "\t Installing deps for module: $1"
${python_exe} ${ORBIT_PATH}/tools/install_deps.py ${cmd} $1
}
# setup anaconda environment for orbit # setup anaconda environment for orbit
setup_conda_env() { setup_conda_env() {
# get environment name from input # get environment name from input
...@@ -195,6 +204,7 @@ print_help () { ...@@ -195,6 +204,7 @@ print_help () {
echo -e "\t-h, --help Display the help content." echo -e "\t-h, --help Display the help content."
echo -e "\t-i, --install Install the extensions inside Orbit." echo -e "\t-i, --install Install the extensions inside Orbit."
echo -e "\t-e, --extra [LIB] Install learning frameworks (rl_games, rsl_rl, sb3) as extra dependencies. Default is 'all'." echo -e "\t-e, --extra [LIB] Install learning frameworks (rl_games, rsl_rl, sb3) as extra dependencies. Default is 'all'."
echo -e "\t--install-deps [dep_type] Install dependencies for extensions (apt, rosdep, all) from each extension.toml. Default is 'all'."
echo -e "\t-f, --format Run pre-commit to format the code and check lints." echo -e "\t-f, --format Run pre-commit to format the code and check lints."
echo -e "\t-p, --python Run the python executable provided by Isaac Sim or virtual environment (if active)." echo -e "\t-p, --python Run the python executable provided by Isaac Sim or virtual environment (if active)."
echo -e "\t-s, --sim Run the simulator executable (isaac-sim.sh) provided by Isaac Sim." echo -e "\t-s, --sim Run the simulator executable (isaac-sim.sh) provided by Isaac Sim."
...@@ -254,6 +264,30 @@ while [[ $# -gt 0 ]]; do ...@@ -254,6 +264,30 @@ while [[ $# -gt 0 ]]; do
${python_exe} -m pip install -e ${ORBIT_PATH}/source/extensions/omni.isaac.orbit_tasks["${framework_name}"] ${python_exe} -m pip install -e ${ORBIT_PATH}/source/extensions/omni.isaac.orbit_tasks["${framework_name}"]
shift # past argument shift # past argument
;; ;;
--install-deps)
# install the deps for extensions in source/extensions directory
if [ -z "$2" ]; then
dep_type="all"
else
dep_type=$2
shift # past argument
fi
echo "[INFO] Installing ${dep_type} dependencies for extensions inside orbit repository..."
# recursively look into directories and install
# all extension dependencies
export -f extract_python_exe
export -f install_extension_deps
# check if dep_type is installed, if not "all"
if [ "$dep_type" = "all" ] || command -v "$dep_type" &>/dev/null; then
find -L "${ORBIT_PATH}/source/extensions" -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -I {} bash -c 'install_extension_deps "$1" "$2"' _ {} "${dep_type}"
else
echo "[ERROR] Not installing ${dep_type} deps, ${dep_type} not a known command"
exit 1
fi
# unset local variables
unset install_extension_deps
shift # past argument
;;
-c|--conda) -c|--conda)
# use default name if not provided # use default name if not provided
if [ -z "$2" ]; then if [ -z "$2" ]; then
......
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
A script with various methods of installing dependencies
defined in an extension.toml
"""
import argparse
import os
import shutil
import sys
import toml
from subprocess import SubprocessError, run
# add argparse arguments
parser = argparse.ArgumentParser(description="Utility to install dependencies based on an extension.toml")
parser.add_argument("type", type=str, choices=["all", "apt", "rosdep"], help="The type of packages to install")
parser.add_argument("path", type=str, help="The path to the extension which will have its deps installed")
def install_apt_packages(path):
"""
A function which attempts to install apt packages for Orbit extensions.
It looks in {extension_root}/config/extension.toml for [orbit_settings][apt_deps]
and then attempts to install them. Exits on failure to stop the build process
from continuing despite missing dependencies.
Args:
path: A path to the extension root
"""
try:
if shutil.which("apt"):
with open(f"{path}/config/extension.toml") as fd:
ext_toml = toml.load(fd)
if "orbit_settings" in ext_toml and "apt_deps" in ext_toml["orbit_settings"]:
deps = ext_toml["orbit_settings"]["apt_deps"]
print(f"[INFO] Installing the following apt packages: {deps}")
run_and_print(["apt-get", "update"])
run_and_print(["apt-get", "install", "-y"] + deps)
else:
print("[INFO] No apt packages to install")
else:
raise RuntimeError("Exiting because 'apt' is not a known command")
except SubprocessError as e:
print(f"[ERROR]: {str(e.stderr, encoding='utf-8')}")
sys.exit(1)
except Exception as e:
print(f"[ERROR]: {e}")
sys.exit(1)
def install_rosdep_packages(path):
"""
A function which attempts to install rosdep packages for Orbit extensions.
It looks in {extension_root}/config/extension.toml for [orbit_settings][ros_ws]
and then attempts to install all rosdeps under that workspace.
Exits on failure to stop the build process from continuing despite missing dependencies.
Args:
path: A path to the extension root
"""
try:
if shutil.which("rosdep"):
with open(f"{path}/config/extension.toml") as fd:
ext_toml = toml.load(fd)
if "orbit_settings" in ext_toml and "ros_ws" in ext_toml["orbit_settings"]:
ws_path = ext_toml["orbit_settings"]["ros_ws"]
if not os.path.exists("/etc/ros/rosdep/sources.list.d/20-default.list"):
run_and_print(["rosdep", "init"])
run_and_print(["rosdep", "update", "--rosdistro=humble"])
run_and_print([
"rosdep",
"install",
"--from-paths",
f"{path}/{ws_path}/src",
"--ignore-src",
"-y",
"--rosdistro=humble",
])
else:
print("[INFO] No rosdep packages to install")
else:
raise RuntimeError("Exiting because 'rosdep' is not a known command")
except SubprocessError as e:
print(f"[ERROR]: {str(e.stderr, encoding='utf-8')}")
sys.exit(1)
except Exception as e:
print(f"[ERROR]: {e}")
sys.exit(1)
def run_and_print(args):
"""
Runs a subprocess.run(args=args, capture_output=True, check=True),
and prints the output
"""
completed_process = run(args=args, capture_output=True, check=True)
print(f"{str(completed_process.stdout, encoding='utf-8')}")
def main():
args = parser.parse_args()
if args.type == "all":
install_apt_packages(args.path)
install_rosdep_packages(args.path)
elif args.type == "apt":
install_apt_packages(args.path)
elif args.type == "rosdep":
install_rosdep_packages(args.path)
else:
print(f"[ERROR] '{args.type}' type dependencies not installable")
sys.exit(1)
if __name__ == "__main__":
main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment