diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..279452a02 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +# Keep Docker build contexts lean for CloudXR compose builds. +# Ignore everything by default, then allow only what current Dockerfiles need. + +* + +# Allow Docker metadata files. +!.dockerignore + +# Allow CloudXR Dockerfiles and runtime assets. +!deps/cloudxr/ + +# Allow build artifacts and tests consumed by Dockerfile.test and Dockerfile.runtime. +!install/ +!examples/ + +# Allow building from source for ros2 workflows +!cmake/ +!deps/ +!src/ +!CMakeLists.txt +!VERSION diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 5c1f91411..c596c6b58 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -165,7 +165,22 @@ jobs: ngc-cli-api-key: ${{ secrets.NGC_TELEOP_CORE_GITHUB_SERVICE_KEY }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4.0.0 + + - name: Cache teleop_ros2 base stage + uses: docker/build-push-action@v7 + with: + context: . + file: examples/teleop_ros2/Dockerfile + target: base + build-args: | + ROS_DISTRO=${{ matrix.ros_distro }} + PYTHON_VERSION=${{ matrix.python_version }} + tags: teleop_ros2_base:${{ matrix.ros_distro }}-py${{ matrix.python_version }} + load: true + push: false + cache-from: type=gha,scope=teleop-ros2-base-${{ matrix.ros_distro }}-py${{ matrix.python_version }} + cache-to: type=gha,mode=max,scope=teleop-ros2-base-${{ matrix.ros_distro }}-py${{ matrix.python_version }} - name: Build teleop_ros2 image run: | @@ -208,7 +223,7 @@ jobs: tar -xvf isaacteleop-install.tar -C install - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4.0.0 - name: Run Tests with CloudXR env: diff --git a/cmake/IsaacTeleopVersion.cmake b/cmake/IsaacTeleopVersion.cmake index b6efc6746..993e66b0c 100644 --- a/cmake/IsaacTeleopVersion.cmake +++ b/cmake/IsaacTeleopVersion.cmake @@ -14,7 +14,6 @@ # * RC (CI + branch release/X.Y.x): branch must match base version; Python version X.Y.PATCHrc, no local. # * Local working copy (non-CI): Python version X.Y+local and CMake version X.Y (patch omitted). function(isaac_teleop_read_version version_file out_cmake_version_var out_pyproject_version_var) - find_package(Git REQUIRED) get_filename_component(_isaac_teleop_version_file "${version_file}" ABSOLUTE) if(NOT EXISTS "${_isaac_teleop_version_file}") message(FATAL_ERROR "Version file not found: ${_isaac_teleop_version_file}") @@ -22,79 +21,12 @@ function(isaac_teleop_read_version version_file out_cmake_version_var out_pyproj file(READ "${_isaac_teleop_version_file}" _isaac_teleop_version_base) string(STRIP "${_isaac_teleop_version_base}" _isaac_teleop_version_base) - execute_process( - COMMAND "${GIT_EXECUTABLE}" -C "${CMAKE_CURRENT_SOURCE_DIR}" rev-parse --show-toplevel - OUTPUT_VARIABLE _isaac_teleop_git_root - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - RESULT_VARIABLE _isaac_teleop_git_root_result - ) - if(NOT _isaac_teleop_git_root_result EQUAL 0 OR _isaac_teleop_git_root STREQUAL "") - message(FATAL_ERROR "Failed to determine git root. Ensure this is a git repository and git is available.") - endif() - execute_process( - COMMAND "${GIT_EXECUTABLE}" -C "${_isaac_teleop_git_root}" rev-list -n 1 HEAD -- "${_isaac_teleop_version_file}" - OUTPUT_VARIABLE _isaac_teleop_version_commit - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - RESULT_VARIABLE _isaac_teleop_version_commit_result - ) - if(NOT _isaac_teleop_version_commit_result EQUAL 0 OR _isaac_teleop_version_commit STREQUAL "") - message(FATAL_ERROR "Failed to locate last commit for version file: ${_isaac_teleop_version_file}") - endif() - execute_process( - COMMAND "${GIT_EXECUTABLE}" -C "${_isaac_teleop_git_root}" rev-list --count "${_isaac_teleop_version_commit}..HEAD" - OUTPUT_VARIABLE _isaac_teleop_git_count - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - RESULT_VARIABLE _isaac_teleop_git_count_result - ) - if(NOT _isaac_teleop_git_count_result EQUAL 0) - message(FATAL_ERROR "Failed to count commits since ${_isaac_teleop_version_commit}.") - endif() - if(NOT _isaac_teleop_git_count MATCHES "^[0-9]+$") - message(FATAL_ERROR "Invalid git commit count: '${_isaac_teleop_git_count}'") - endif() - - execute_process( - COMMAND "${GIT_EXECUTABLE}" -C "${_isaac_teleop_git_root}" rev-parse --abbrev-ref HEAD - OUTPUT_VARIABLE _isaac_teleop_git_branch - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - RESULT_VARIABLE _isaac_teleop_git_branch_result - ) - if(NOT _isaac_teleop_git_branch_result EQUAL 0 OR _isaac_teleop_git_branch STREQUAL "") - message(FATAL_ERROR "Failed to determine git branch name.") - endif() - if(_isaac_teleop_git_branch STREQUAL "HEAD") - if(DEFINED ENV{GITHUB_REF_NAME} AND NOT "$ENV{GITHUB_REF_NAME}" STREQUAL "") - set(_isaac_teleop_git_branch "$ENV{GITHUB_REF_NAME}") - elseif(DEFINED ENV{GITHUB_HEAD_REF} AND NOT "$ENV{GITHUB_HEAD_REF}" STREQUAL "") - set(_isaac_teleop_git_branch "$ENV{GITHUB_HEAD_REF}") - elseif(DEFINED ENV{CI_COMMIT_REF_NAME} AND NOT "$ENV{CI_COMMIT_REF_NAME}" STREQUAL "") - set(_isaac_teleop_git_branch "$ENV{CI_COMMIT_REF_NAME}") - endif() - endif() - - execute_process( - COMMAND "${GIT_EXECUTABLE}" -C "${_isaac_teleop_git_root}" describe --tags --exact-match - OUTPUT_VARIABLE _isaac_teleop_git_tag - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - RESULT_VARIABLE _isaac_teleop_git_tag_result - ) - if(NOT _isaac_teleop_git_tag_result EQUAL 0) - set(_isaac_teleop_git_tag "") - endif() - string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.x" _isaac_teleop_version_match "${_isaac_teleop_version_base}") if(NOT _isaac_teleop_version_match) message(FATAL_ERROR "Base version must be in MAJOR.MINOR.x format; actual content: '${_isaac_teleop_version_base}'") endif() set(_isaac_teleop_version_major "${CMAKE_MATCH_1}") set(_isaac_teleop_version_minor "${CMAKE_MATCH_2}") - set(_isaac_teleop_version_patch "${_isaac_teleop_git_count}") - set(_isaac_teleop_version "${_isaac_teleop_version_major}.${_isaac_teleop_version_minor}.${_isaac_teleop_version_patch}") set(_isaac_teleop_is_ci FALSE) if(DEFINED ENV{CI} AND NOT "$ENV{CI}" STREQUAL "") @@ -104,46 +36,122 @@ function(isaac_teleop_read_version version_file out_cmake_version_var out_pyproj endif() endif() - set(_isaac_teleop_pyproject_version "") + set(_isaac_teleop_git_count "0") + set(_isaac_teleop_git_branch "") + set(_isaac_teleop_git_tag "") + set(_isaac_teleop_version "${_isaac_teleop_version_major}.${_isaac_teleop_version_minor}") set(_isaac_teleop_build_kind "local") + set(_isaac_teleop_pyproject_version "${_isaac_teleop_version_major}.${_isaac_teleop_version_minor}+local") + + if(_isaac_teleop_is_ci) + find_package(Git REQUIRED) - if(_isaac_teleop_is_ci AND NOT _isaac_teleop_git_tag STREQUAL "") - if(NOT _isaac_teleop_git_tag MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)$") - message(FATAL_ERROR "Invalid release tag format: '${_isaac_teleop_git_tag}' (expected vMAJOR.MINOR.PATCH)") + execute_process( + COMMAND "${GIT_EXECUTABLE}" -C "${CMAKE_CURRENT_SOURCE_DIR}" rev-parse --show-toplevel + OUTPUT_VARIABLE _isaac_teleop_git_root + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE _isaac_teleop_git_root_result + ) + if(NOT _isaac_teleop_git_root_result EQUAL 0 OR _isaac_teleop_git_root STREQUAL "") + message(FATAL_ERROR "Failed to determine git root. Ensure this is a git repository and git is available.") endif() - set(_isaac_teleop_tag_version "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}") - if(NOT _isaac_teleop_tag_version STREQUAL "${_isaac_teleop_version}") - message(FATAL_ERROR "Release tag ${_isaac_teleop_git_tag} does not match calculated version ${_isaac_teleop_version}.") + execute_process( + COMMAND "${GIT_EXECUTABLE}" -C "${_isaac_teleop_git_root}" rev-list -n 1 HEAD -- "${_isaac_teleop_version_file}" + OUTPUT_VARIABLE _isaac_teleop_version_commit + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE _isaac_teleop_version_commit_result + ) + if(NOT _isaac_teleop_version_commit_result EQUAL 0 OR _isaac_teleop_version_commit STREQUAL "") + message(FATAL_ERROR "Failed to locate last commit for version file: ${_isaac_teleop_version_file}") endif() - set(_isaac_teleop_pyproject_version "${_isaac_teleop_tag_version}") - set(_isaac_teleop_build_kind "release") - elseif(_isaac_teleop_is_ci AND _isaac_teleop_git_branch MATCHES "^release/([0-9]+)\\.([0-9]+)\\.x$") - if(NOT "${CMAKE_MATCH_1}" STREQUAL "${_isaac_teleop_version_major}" OR NOT "${CMAKE_MATCH_2}" STREQUAL "${_isaac_teleop_version_minor}") - message(FATAL_ERROR "Release branch ${_isaac_teleop_git_branch} does not match base version ${_isaac_teleop_version_base}.") + execute_process( + COMMAND "${GIT_EXECUTABLE}" -C "${_isaac_teleop_git_root}" rev-list --count "${_isaac_teleop_version_commit}..HEAD" + OUTPUT_VARIABLE _isaac_teleop_git_count + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE _isaac_teleop_git_count_result + ) + if(NOT _isaac_teleop_git_count_result EQUAL 0) + message(FATAL_ERROR "Failed to count commits since ${_isaac_teleop_version_commit}.") endif() - set(_isaac_teleop_pyproject_version "${_isaac_teleop_version_major}.${_isaac_teleop_version_minor}.${_isaac_teleop_version_patch}rc1") - set(_isaac_teleop_build_kind "rc") - elseif(_isaac_teleop_is_ci AND _isaac_teleop_git_branch STREQUAL "main") - set(_isaac_teleop_pyproject_version "${_isaac_teleop_version_major}.${_isaac_teleop_version_minor}.${_isaac_teleop_version_patch}a1") - set(_isaac_teleop_build_kind "alpha") - elseif(_isaac_teleop_is_ci) - string(TOLOWER "${_isaac_teleop_git_branch}" _isaac_teleop_label) - string(REGEX REPLACE "[^a-z0-9._-]" "." _isaac_teleop_label "${_isaac_teleop_label}") # replace disallowed chars with dots - string(REGEX REPLACE "[._-]+" "." _isaac_teleop_label "${_isaac_teleop_label}") # collapse separator runs to a single dot - string(REGEX REPLACE "^[._-]+" "" _isaac_teleop_label "${_isaac_teleop_label}") # trim leading separators - string(REGEX REPLACE "[._-]+$" "" _isaac_teleop_label "${_isaac_teleop_label}") # trim trailing separators - if(_isaac_teleop_label STREQUAL "") - set(_isaac_teleop_label "unknown") + if(NOT _isaac_teleop_git_count MATCHES "^[0-9]+$") + message(FATAL_ERROR "Invalid git commit count: '${_isaac_teleop_git_count}'") + endif() + + execute_process( + COMMAND "${GIT_EXECUTABLE}" -C "${_isaac_teleop_git_root}" rev-parse --abbrev-ref HEAD + OUTPUT_VARIABLE _isaac_teleop_git_branch + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE _isaac_teleop_git_branch_result + ) + if(NOT _isaac_teleop_git_branch_result EQUAL 0 OR _isaac_teleop_git_branch STREQUAL "") + message(FATAL_ERROR "Failed to determine git branch name.") + endif() + if(_isaac_teleop_git_branch STREQUAL "HEAD") + if(DEFINED ENV{GITHUB_REF_NAME} AND NOT "$ENV{GITHUB_REF_NAME}" STREQUAL "") + set(_isaac_teleop_git_branch "$ENV{GITHUB_REF_NAME}") + elseif(DEFINED ENV{GITHUB_HEAD_REF} AND NOT "$ENV{GITHUB_HEAD_REF}" STREQUAL "") + set(_isaac_teleop_git_branch "$ENV{GITHUB_HEAD_REF}") + elseif(DEFINED ENV{CI_COMMIT_REF_NAME} AND NOT "$ENV{CI_COMMIT_REF_NAME}" STREQUAL "") + set(_isaac_teleop_git_branch "$ENV{CI_COMMIT_REF_NAME}") + endif() + endif() + + execute_process( + COMMAND "${GIT_EXECUTABLE}" -C "${_isaac_teleop_git_root}" describe --tags --exact-match + OUTPUT_VARIABLE _isaac_teleop_git_tag + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE _isaac_teleop_git_tag_result + ) + if(NOT _isaac_teleop_git_tag_result EQUAL 0) + set(_isaac_teleop_git_tag "") + endif() + + set(_isaac_teleop_version_patch "${_isaac_teleop_git_count}") + set(_isaac_teleop_version "${_isaac_teleop_version_major}.${_isaac_teleop_version_minor}.${_isaac_teleop_version_patch}") + + if(NOT _isaac_teleop_git_tag STREQUAL "") + if(NOT _isaac_teleop_git_tag MATCHES "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)$") + message(FATAL_ERROR "Invalid release tag format: '${_isaac_teleop_git_tag}' (expected vMAJOR.MINOR.PATCH)") + endif() + set(_isaac_teleop_tag_version "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}") + if(NOT _isaac_teleop_tag_version STREQUAL "${_isaac_teleop_version}") + message(FATAL_ERROR "Release tag ${_isaac_teleop_git_tag} does not match calculated version ${_isaac_teleop_version}.") + endif() + set(_isaac_teleop_pyproject_version "${_isaac_teleop_tag_version}") + set(_isaac_teleop_build_kind "release") + elseif(_isaac_teleop_git_branch MATCHES "^release/([0-9]+)\\.([0-9]+)\\.x$") + if(NOT "${CMAKE_MATCH_1}" STREQUAL "${_isaac_teleop_version_major}" OR NOT "${CMAKE_MATCH_2}" STREQUAL "${_isaac_teleop_version_minor}") + message(FATAL_ERROR "Release branch ${_isaac_teleop_git_branch} does not match base version ${_isaac_teleop_version_base}.") + endif() + set(_isaac_teleop_pyproject_version "${_isaac_teleop_version_major}.${_isaac_teleop_version_minor}.${_isaac_teleop_version_patch}rc1") + set(_isaac_teleop_build_kind "rc") + elseif(_isaac_teleop_git_branch STREQUAL "main") + set(_isaac_teleop_pyproject_version "${_isaac_teleop_version_major}.${_isaac_teleop_version_minor}.${_isaac_teleop_version_patch}a1") + set(_isaac_teleop_build_kind "alpha") + else() + string(TOLOWER "${_isaac_teleop_git_branch}" _isaac_teleop_label) + string(REGEX REPLACE "[^a-z0-9._-]" "." _isaac_teleop_label "${_isaac_teleop_label}") # replace disallowed chars with dots + string(REGEX REPLACE "[._-]+" "." _isaac_teleop_label "${_isaac_teleop_label}") # collapse separator runs to a single dot + string(REGEX REPLACE "^[._-]+" "" _isaac_teleop_label "${_isaac_teleop_label}") # trim leading separators + string(REGEX REPLACE "[._-]+$" "" _isaac_teleop_label "${_isaac_teleop_label}") # trim trailing separators + if(_isaac_teleop_label STREQUAL "") + set(_isaac_teleop_label "unknown") + endif() + set(_isaac_teleop_pyproject_version "${_isaac_teleop_version_major}.${_isaac_teleop_version_minor}.${_isaac_teleop_version_patch}.dev0+${_isaac_teleop_label}") + set(_isaac_teleop_build_kind "dev") endif() - set(_isaac_teleop_pyproject_version "${_isaac_teleop_version_major}.${_isaac_teleop_version_minor}.${_isaac_teleop_version_patch}.dev0+${_isaac_teleop_label}") - set(_isaac_teleop_build_kind "dev") - else() - set(_isaac_teleop_pyproject_version "${_isaac_teleop_version_major}.${_isaac_teleop_version_minor}+local") - set(_isaac_teleop_build_kind "local") - set(_isaac_teleop_version "${_isaac_teleop_version_major}.${_isaac_teleop_version_minor}") endif() set(${out_cmake_version_var} "${_isaac_teleop_version}" PARENT_SCOPE) set(${out_pyproject_version_var} "${_isaac_teleop_pyproject_version}" PARENT_SCOPE) - message(STATUS "IsaacTeleop version: ${_isaac_teleop_version} (${_isaac_teleop_version_base} + ${_isaac_teleop_git_count} commits) python: ${_isaac_teleop_pyproject_version} kind: ${_isaac_teleop_build_kind}") + if(_isaac_teleop_is_ci) + message(STATUS "IsaacTeleop version: ${_isaac_teleop_version} (${_isaac_teleop_version_base} + ${_isaac_teleop_git_count} commits) python: ${_isaac_teleop_pyproject_version} kind: ${_isaac_teleop_build_kind}") + else() + message(STATUS "IsaacTeleop version: ${_isaac_teleop_version} (${_isaac_teleop_version_base}; local build) python: ${_isaac_teleop_pyproject_version} kind: ${_isaac_teleop_build_kind}") + endif() endfunction() diff --git a/deps/cloudxr/Dockerfile.test b/deps/cloudxr/Dockerfile.test index fb706dd6f..69f8fc777 100644 --- a/deps/cloudxr/Dockerfile.test +++ b/deps/cloudxr/Dockerfile.test @@ -21,12 +21,12 @@ WORKDIR /app # Install uv for fast Python package management COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv -# Copy the install directory (contains wheels, libs, native test binaries, etc.) -COPY install/ /app/install/ - # Copy test files COPY examples/oxr/python/ /app/tests/ +# Copy the install directory (contains wheels, libs, native test binaries, etc.) +COPY install/ /app/install/ + # Install Python dependencies using uv WORKDIR /app/tests RUN uv venv --python $PYTHON_VERSION /app/venv && \ @@ -39,7 +39,5 @@ ENV PATH="/app/venv/bin:$PATH" ENV PYTHONPATH="/app/install/lib:$PYTHONPATH" ENV XR_RUNTIME_JSON="/openxr/openxr_cloudxr.json" -WORKDIR /app/tests - # Default command runs the test script CMD ["python", "test_extensions.py"] diff --git a/deps/cloudxr/docker-compose.test.yaml b/deps/cloudxr/docker-compose.test.yaml index b631cc194..48635b8a4 100644 --- a/deps/cloudxr/docker-compose.test.yaml +++ b/deps/cloudxr/docker-compose.test.yaml @@ -31,8 +31,13 @@ services: capabilities: [ gpu ] # Test runner container - # Build with: docker build -t isaacteleop-tests:latest -f deps/cloudxr/Dockerfile.test . + # Build handled via docker compose using this service's build definition. isaacteleop-tests: + build: + context: ${CXR_BUILD_CONTEXT:?CXR_BUILD_CONTEXT must point to repository root} + dockerfile: deps/cloudxr/Dockerfile.test + args: + PYTHON_VERSION: image: isaacteleop-tests:latest pull_policy: never network_mode: host diff --git a/examples/teleop_ros2/Dockerfile b/examples/teleop_ros2/Dockerfile index a0fe4352d..338b92d13 100644 --- a/examples/teleop_ros2/Dockerfile +++ b/examples/teleop_ros2/Dockerfile @@ -64,9 +64,7 @@ ARG PYTHON_VERSION RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ - clang-format \ cmake \ - git \ pkg-config \ && rm -rf /var/lib/apt/lists/* diff --git a/scripts/run_tests_with_cloudxr.sh b/scripts/run_tests_with_cloudxr.sh index a86b8e24a..b4ced5617 100755 --- a/scripts/run_tests_with_cloudxr.sh +++ b/scripts/run_tests_with_cloudxr.sh @@ -248,22 +248,24 @@ fi export EXPECTED_ISAACTELEOP_VERSION log_info "Expected isaacteleop version from wheel artifact: $EXPECTED_ISAACTELEOP_VERSION" -# Build test container -log_info "Building test container..." +# Build CloudXR runtime + test services via compose +log_info "Building CloudXR runtime and test containers..." -BUILD_ARGS="-q" +COMPOSE_BUILD_ARGS=() if [ "$FORCE_BUILD" = true ]; then - BUILD_ARGS="$BUILD_ARGS --no-cache" + COMPOSE_BUILD_ARGS+=(--no-cache) fi -docker build \ - $BUILD_ARGS \ - --build-arg PYTHON_VERSION="$PYTHON_VERSION" \ - -t isaacteleop-tests:latest \ - -f deps/cloudxr/Dockerfile.test \ - . +docker compose \ + -p "$COMPOSE_PROJECT" \ + --env-file "$ENV_DEFAULT" \ + ${ENV_LOCAL:+--env-file "$ENV_LOCAL"} \ + ${ENV_TEST:+--env-file "$ENV_TEST"} \ + -f "$COMPOSE_RUNTIME" \ + -f "$COMPOSE_TEST" \ + build "${COMPOSE_BUILD_ARGS[@]}" cloudxr-runtime isaacteleop-tests -log_success "Test container built successfully" +log_success "CloudXR runtime and test containers built successfully" # Start CloudXR runtime services log_info "Starting CloudXR runtime services..." @@ -275,7 +277,7 @@ docker compose \ ${ENV_TEST:+--env-file "$ENV_TEST"} \ -f "$COMPOSE_RUNTIME" \ -f "$COMPOSE_TEST" \ - up --build -d cloudxr-runtime + up -d cloudxr-runtime # Wait for CloudXR runtime to be healthy log_info "Waiting for CloudXR runtime to be healthy..."