Skip to content

Commit

Permalink
Fix cmake program wrap (#9)
Browse files Browse the repository at this point in the history
* Fix broken ament_install_python function

The upstream package had copied this from catkin_virtualenv and
updated it for ament. But the templates were not copied, and some
of the cmake variables were broken and not correct.

This patch fixes this by copying the install template, fixing the
broken variables, and removing the "devel" logic as this doesn't
apply to ROS2 builds.

* Revert "Remove catkin_pkg in favor of ament_index_python (#4)"

This reverts commit 8cbdec1.

* Revert "Fix find_in_workspaces to work with bundle prefix paths (#3)"

This reverts commit 43a3c4d.

* Resolve workspace paths before searching workspaces

The workspace paths included the project name and this directory may
not even exist yet. This caused os.walk() to return an empty list.

Fix this by resolving the paths first to remove any ".." paths and
in turn remove the project name from the overall workspace path.

* Fix workspace path checks, and support bundle paths
  • Loading branch information
jprestwo authored Feb 21, 2025
1 parent 969f027 commit 0ed56d3
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 24 deletions.
12 changes: 2 additions & 10 deletions ament_cmake_virtualenv/cmake/ament_install_python.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,12 @@ will not work as expected.")
)
endif()

set(program_install_location ${AMENT_PACKAGE_SHARE_DESTINATION}/ament_virtualenv_scripts)

# For devel-space support, we generate a bash script that invokes the source script via the virtualenv's
# python interpreter.
set(devel_program ${AMENT_DEVEL_PREFIX}/${ARG_DESTINATION}/${program_basename})
configure_file(${ament_virtualenv_CMAKE_DIR}/templates/program.devel.in ${devel_program})
execute_process(
COMMAND ${AMENT_ENV} chmod +x ${devel_program}
)
set(program_install_location ament_virtualenv_scripts)

# For install-space support, we install the source script, and then generate a bash script to invoke it using
# the virtualenv's python interpreter.
set(install_program ${CMAKE_BINARY_DIR}/${program_basename})
configure_file(${ament_virtualenv_CMAKE_DIR}/templates/program.install.in ${install_program})
configure_file(${ament_cmake_virtualenv_DIR}/templates/program.install.in ${install_program})
execute_process(
COMMAND ${AMENT_ENV} chmod +x ${install_program}
)
Expand Down
17 changes: 17 additions & 0 deletions ament_cmake_virtualenv/cmake/templates/program.install.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash

if [ "${ARG_RENAME_PROCESS}" = "TRUE" ]; then
exec ${${PROJECT_NAME}_VENV_INSTALL_DIR}/bin/python - "$@" <<- EOF
import re
import sys
from setproctitle import setproctitle
program_path = "${CMAKE_INSTALL_PREFIX}/${program_install_location}/${program_basename}"
setproctitle(' '.join([program_path] + sys.argv[1:]))
exec(open(program_path).read())
EOF
else
exec ${${PROJECT_NAME}_VENV_INSTALL_DIR}/bin/python ${CMAKE_INSTALL_PREFIX}/${program_install_location}/${program_basename} "$@"
fi

121 changes: 109 additions & 12 deletions ament_virtualenv/ament_virtualenv/glob_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import sys
import os

from pathlib import Path
from typing import List
from catkin_pkg.package import Package
from ament_index_python.packages import get_package_share_directory, PackageNotFoundError
Expand All @@ -44,19 +45,108 @@
from Queue import Queue


def find_in_workspaces(project, file, workspaces=[]):
# Add default workspace search paths
ament_paths = os.environ.get('AMENT_PREFIX_PATH')
if ament_paths is not None:
# AMENT_PREFIX_PATH points at <prefix>/install
ament_paths = ament_paths.split(os.pathsep)
for path in ament_paths:
if ((os.path.sep + 'install') in path or
(os.path.sep + 'install_isolated') in path):
workspaces.append(os.path.join(path, '..'))
workspaces.append(os.path.join(path, '..', '..', 'src'))
break
if len(workspaces) == 0:
# if AMENT_PREFIX_PATH wasn't set, we can fall back on
# CMAKE_PREFIX_PATH (should contain the same information)
cmake_paths = os.environ.get('CMAKE_PREFIX_PATH')
if cmake_paths is not None:
# CMAKE_PREFIX_PATH points at <prefix>/install or <prefix>/install_isolated
cmake_paths = cmake_paths.split(os.pathsep)
for path in cmake_paths:
if ((os.path.sep + 'install') in path or
(os.path.sep + 'install_isolated') in path):
workspaces.append(os.path.join(path, '..'))
workspaces.append(os.path.join(path, '..', '..', 'src'))
break
if len(workspaces) == 0:
# COLCON_PREFIX_PATH points to the `install/` directory,
# which is fine when ament_python is used as build tool
# (ament_python copies the files right away),
# but ament_cmake does not copy the files until after the
# build, which is too late. So for ament_cmake we also
# need to add the neighboring `src/` folder to the seach
# (eg.: `install/../src/`)
colcon_paths = os.environ.get('COLCON_PREFIX_PATH')
if colcon_paths is not None:
colcon_paths = colcon_paths.split(os.pathsep)
for path in colcon_paths:
if (os.path.sep + 'install') in path or (os.path.sep + 'install_isolated') in path:
workspaces.append(path)
workspaces.append(os.path.join(path, '..', 'src'))
if len(workspaces) == 0:
# final (local) fallback: use working directory (usually src/<package>)
path = os.getcwd()
if (os.path.sep + 'src') in path:
workspaces.append(path)

# Above, all paths required an "install/" or "src/" folder in order to qualify
# as a workspace. This only applies to local workspaces but does not take into
# account any installed bundles. We should prefer local workspaces, but we
# should also include the bundle path in case the workspace found above either
# doesn't contain the package in question or if its ignored. Since the first
# match is used we are safe to append the bundle path unconditionally.
try:
path = get_package_share_directory(project)
workspaces.append(path)
except PackageNotFoundError:
pass

if len(workspaces) == 0:
raise RuntimeError(
"[ament_virtualenv] Failed to find any workspaces." +
"\nAMENT_PREFIX_PATH=" + os.environ.get('AMENT_PREFIX_PATH', 'NOT SET') +
"\nCMAKE_PREFIX_PATH=" + os.environ.get('CMAKE_PREFIX_PATH', 'NOT SET') +
"\nCOLCON_PREFIX_PATH=" + os.environ.get('COLCON_PREFIX_PATH', 'NOT SET') +
"\nCWD=" + os.getcwd()
)

# The paths in "workspaces" will look something like (depending on logic above)
# <prefix>/install/<project>/../
# The issue here is <project> dir may not actually exist so below when we walk the
# directories it will ignore that folder since it doesn't exist. To fix this we
# need to resolve the paths so they point to valid directories, ignoring the di
# of the packages we're building which may not exist.
workspaces = [str(Path(path).resolve()) for path in workspaces]

# now search the workspaces
for workspace in (workspaces or []):
for d, dirs, files in os.walk(workspace, topdown=True, followlinks=True):
if (('CATKIN_IGNORE' in files) or
('COLCON_IGNORE' in files) or
('AMENT_IGNORE' in files)):
del dirs[:]
continue
dirname = os.path.basename(d)
if dirname == project and file in files:
return os.path.join(workspace, d, file)
# none found:
return None
#


AMENT_VIRTUALENV_TAGNAME = "pip_requirements"


def parse_exported_requirements(package: Package) -> List[str]:
requirements_list = []
for export in package.exports:
if export.tagname == AMENT_VIRTUALENV_TAGNAME:
package_path = get_package_share_directory(package.name)
if os.path.exists(f"{package_path}/{export.content}"):
requirements_path = f"{package_path}/{export.content}"
else:
requirements_path = None

requirements_path = find_in_workspaces(
project=package.name,
file=export.content
)
if not requirements_path:
print(
("[ERROR] ament_virtualenv "
Expand All @@ -75,12 +165,19 @@ def parse_exported_requirements(package: Package) -> List[str]:

def process_package(package_name, soft_fail=True):
# type: (str) -> List[str], List[str]
try:
package_path = get_package_share_directory(package_name)
except PackageNotFoundError:
# This is used to parse all dependencies listed in package.xml which
# may be dependences that are not ROS packages.
return [], []
workspaces = []
package_path = find_in_workspaces(
project=package_name,
file="package.xml",
workspaces=workspaces
)
if not package_path:
if not soft_fail:
raise RuntimeError("Failed to find package.xml for package " +
package_name + ' in ' + ';'.join(workspaces))
else:
# This is not an ament dependency
return [], []
else:
package = parse_package(package_path)
dependencies = package.build_depends + package.test_depends
Expand Down
Empty file.
2 changes: 0 additions & 2 deletions ament_virtualenv/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
packages=find_packages(exclude=['test']),
data_files=[
('share/' + package_name, ['package.xml']),
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
],
install_requires=['setuptools'],
zip_safe=False,
Expand Down

0 comments on commit 0ed56d3

Please sign in to comment.