2626import sys
2727import os
2828
29+ from pathlib import Path
2930from typing import List
3031from catkin_pkg .package import Package
3132from ament_index_python .packages import get_package_share_directory , PackageNotFoundError
4445 from Queue import Queue
4546
4647
48+ def find_in_workspaces (project , file , workspaces = []):
49+ # Add default workspace search paths
50+ ament_paths = os .environ .get ('AMENT_PREFIX_PATH' )
51+ if ament_paths is not None :
52+ # AMENT_PREFIX_PATH points at <prefix>/install
53+ ament_paths = ament_paths .split (os .pathsep )
54+ for path in ament_paths :
55+ if ((os .path .sep + 'install' ) in path or
56+ (os .path .sep + 'install_isolated' ) in path ):
57+ workspaces .append (os .path .join (path , '..' ))
58+ workspaces .append (os .path .join (path , '..' , '..' , 'src' ))
59+ break
60+ if len (workspaces ) == 0 :
61+ # if AMENT_PREFIX_PATH wasn't set, we can fall back on
62+ # CMAKE_PREFIX_PATH (should contain the same information)
63+ cmake_paths = os .environ .get ('CMAKE_PREFIX_PATH' )
64+ if cmake_paths is not None :
65+ # CMAKE_PREFIX_PATH points at <prefix>/install or <prefix>/install_isolated
66+ cmake_paths = cmake_paths .split (os .pathsep )
67+ for path in cmake_paths :
68+ if ((os .path .sep + 'install' ) in path or
69+ (os .path .sep + 'install_isolated' ) in path ):
70+ workspaces .append (os .path .join (path , '..' ))
71+ workspaces .append (os .path .join (path , '..' , '..' , 'src' ))
72+ break
73+ if len (workspaces ) == 0 :
74+ # COLCON_PREFIX_PATH points to the `install/` directory,
75+ # which is fine when ament_python is used as build tool
76+ # (ament_python copies the files right away),
77+ # but ament_cmake does not copy the files until after the
78+ # build, which is too late. So for ament_cmake we also
79+ # need to add the neighboring `src/` folder to the seach
80+ # (eg.: `install/../src/`)
81+ colcon_paths = os .environ .get ('COLCON_PREFIX_PATH' )
82+ if colcon_paths is not None :
83+ colcon_paths = colcon_paths .split (os .pathsep )
84+ for path in colcon_paths :
85+ if (os .path .sep + 'install' ) in path or (os .path .sep + 'install_isolated' ) in path :
86+ workspaces .append (path )
87+ workspaces .append (os .path .join (path , '..' , 'src' ))
88+ if len (workspaces ) == 0 :
89+ # final (local) fallback: use working directory (usually src/<package>)
90+ path = os .getcwd ()
91+ if (os .path .sep + 'src' ) in path :
92+ workspaces .append (path )
93+
94+ # Above, all paths required an "install/" or "src/" folder in order to qualify
95+ # as a workspace. This only applies to local workspaces but does not take into
96+ # account any installed bundles. We should prefer local workspaces, but we
97+ # should also include the bundle path in case the workspace found above either
98+ # doesn't contain the package in question or if its ignored. Since the first
99+ # match is used we are safe to append the bundle path unconditionally.
100+ try :
101+ path = get_package_share_directory (project )
102+ workspaces .append (path )
103+ except PackageNotFoundError :
104+ pass
105+
106+ if len (workspaces ) == 0 :
107+ raise RuntimeError (
108+ "[ament_virtualenv] Failed to find any workspaces." +
109+ "\n AMENT_PREFIX_PATH=" + os .environ .get ('AMENT_PREFIX_PATH' , 'NOT SET' ) +
110+ "\n CMAKE_PREFIX_PATH=" + os .environ .get ('CMAKE_PREFIX_PATH' , 'NOT SET' ) +
111+ "\n COLCON_PREFIX_PATH=" + os .environ .get ('COLCON_PREFIX_PATH' , 'NOT SET' ) +
112+ "\n CWD=" + os .getcwd ()
113+ )
114+
115+ # The paths in "workspaces" will look something like (depending on logic above)
116+ # <prefix>/install/<project>/../
117+ # The issue here is <project> dir may not actually exist so below when we walk the
118+ # directories it will ignore that folder since it doesn't exist. To fix this we
119+ # need to resolve the paths so they point to valid directories, ignoring the di
120+ # of the packages we're building which may not exist.
121+ workspaces = [str (Path (path ).resolve ()) for path in workspaces ]
122+
123+ # now search the workspaces
124+ for workspace in (workspaces or []):
125+ for d , dirs , files in os .walk (workspace , topdown = True , followlinks = True ):
126+ if (('CATKIN_IGNORE' in files ) or
127+ ('COLCON_IGNORE' in files ) or
128+ ('AMENT_IGNORE' in files )):
129+ del dirs [:]
130+ continue
131+ dirname = os .path .basename (d )
132+ if dirname == project and file in files :
133+ return os .path .join (workspace , d , file )
134+ # none found:
135+ return None
136+ #
137+
138+
47139AMENT_VIRTUALENV_TAGNAME = "pip_requirements"
48140
49141
50142def parse_exported_requirements (package : Package ) -> List [str ]:
51143 requirements_list = []
52144 for export in package .exports :
53145 if export .tagname == AMENT_VIRTUALENV_TAGNAME :
54- package_path = get_package_share_directory (package .name )
55- if os .path .exists (f"{ package_path } /{ export .content } " ):
56- requirements_path = f"{ package_path } /{ export .content } "
57- else :
58- requirements_path = None
59-
146+ requirements_path = find_in_workspaces (
147+ project = package .name ,
148+ file = export .content
149+ )
60150 if not requirements_path :
61151 print (
62152 ("[ERROR] ament_virtualenv "
@@ -75,12 +165,19 @@ def parse_exported_requirements(package: Package) -> List[str]:
75165
76166def process_package (package_name , soft_fail = True ):
77167 # type: (str) -> List[str], List[str]
78- try :
79- package_path = get_package_share_directory (package_name )
80- except PackageNotFoundError :
81- # This is used to parse all dependencies listed in package.xml which
82- # may be dependences that are not ROS packages.
83- return [], []
168+ workspaces = []
169+ package_path = find_in_workspaces (
170+ project = package_name ,
171+ file = "package.xml" ,
172+ workspaces = workspaces
173+ )
174+ if not package_path :
175+ if not soft_fail :
176+ raise RuntimeError ("Failed to find package.xml for package " +
177+ package_name + ' in ' + ';' .join (workspaces ))
178+ else :
179+ # This is not an ament dependency
180+ return [], []
84181 else :
85182 package = parse_package (package_path )
86183 dependencies = package .build_depends + package .test_depends
0 commit comments