def rewrite_python_shebangs(root_dir): """Recursively change #!/usr/bin/pythonX shebangs to #!/usr/bin/env pythonX :param str root_dir: Directory that will be crawled for shebangs. """ file_pattern = re.compile(r"") argless_shebang_pattern = re.compile(r"\A#!.*(python\S*)$", re.MULTILINE) shebang_pattern_with_args = re.compile( r"\A#!.*(python\S*)[ \t\f\v]+(\S+)$", re.MULTILINE) file_utils.replace_in_file(root_dir, file_pattern, argless_shebang_pattern, r"#!/usr/bin/env \1") # The above rewrite will barf if the shebang includes any args to python. # For example, if the shebang was `#!/usr/bin/python3 -Es`, just replacing # that with `#!/usr/bin/env python3 -Es` isn't going to work as `env` # doesn't support arguments like that. # # The solution is to replace the shebang with one pointing to /bin/sh, and # then exec the original shebang with included arguments. This requires # some quoting hacks to ensure the file can be interpreted by both sh as # well as python, but it's better than shipping our own `env`. file_utils.replace_in_file( root_dir, file_pattern, shebang_pattern_with_args, r"""#!/bin/sh\n''''exec \1 \2 -- "$0" "$@" # '''""", )
def _use_in_snap_python(self): # Fix all shebangs to use the in-snap python. mangling.rewrite_python_shebangs(self.installdir) # Also replace all the /usr/bin/python calls in etc/catkin/profile.d/ # files with the in-snap python profile_d_path = os.path.join(self.rosdir, "etc", "catkin", "profile.d") file_utils.replace_in_file(profile_d_path, re.compile(r""), re.compile(r"/usr/bin/python"), r"python")
def test_replace_in_file(self, tmp_work_path, file_path, contents, expected): (tmp_work_path / "bin").mkdir() with open(file_path, "w") as f: f.write(contents) file_utils.replace_in_file("bin", re.compile(r""), re.compile(r"#!.*python"), r"#!/usr/bin/env python") with open(file_path, "r") as f: assert f.read() == expected
def _rewrite_cmake_paths(self, new_path_callable): def _rewrite_paths(match): paths = match.group(1).strip().split(";") for i, path in enumerate(paths): # Offer the opportunity to rewrite this path if it's absolute. if os.path.isabs(path): paths[i] = new_path_callable(path) return '"' + ";".join(paths) + '"' # Looking for any path-like string file_utils.replace_in_file( self.rosdir, re.compile(r".*Config.cmake$"), re.compile(r'"(.*?/.*?)"'), _rewrite_paths, )
def _ruby_install(self, builddir): self._ruby_tar.provision(builddir, clean_target=False, keep_tarball=True) self._run(["./configure", "--disable-install-rdoc", "--prefix=/"], cwd=builddir) self._run(["make", "-j{}".format(self.parallel_build_count)], cwd=builddir) self._run(["make", "install", "DESTDIR={}".format(self.installdir)], cwd=builddir) # Fix all shebangs to use the in-snap ruby file_utils.replace_in_file( self.installdir, re.compile(r""), re.compile(r"^#!.*ruby"), r"#!/usr/bin/env ruby", )
def _prepare_build(self): self._use_in_snap_python() # Each Catkin package distributes .cmake files so they can be found via # find_package(). However, the Ubuntu packages pulled down as # dependencies contain .cmake files pointing to system paths (e.g. # /usr/lib, /usr/include, etc.). They need to be rewritten to point to # the install directory. def _new_path(path): if not path.startswith(self.installdir): # Not using os.path.join here as `path` is absolute. return self.installdir + path return path self._rewrite_cmake_paths(_new_path) # Also rewrite any occurrence of $SNAPCRAFT_STAGE to be our install # directory (this may be the case if stage-snaps were used). file_utils.replace_in_file( self.rosdir, re.compile(r".*Config.cmake$"), re.compile(r"\$ENV{SNAPCRAFT_STAGE}"), self.installdir, )
def _fix_prefixes(self): installdir_pattern = re.compile(r"^{}".format(self.installdir)) new_prefix = "$SNAP_COLCON_ROOT" def _rewrite_prefix(match): # Group 1 is the variable definition, group 2 is the path, which we may need # to modify. path = match.group(3).strip(" \n\t'\"") # Bail early if this isn't even a path, or if it's already been rewritten if os.path.sep not in path or new_prefix in path: return match.group() # If the path doesn't start with the installdir, then it needs to point to # the underlay given that the upstream ROS packages are expecting to be in # /opt/ros/. if not path.startswith(self.installdir): path = os.path.join(new_prefix, path.lstrip("/")) return match.expand( '\\1\\2"{}"\\4'.format(installdir_pattern.sub(new_prefix, path)) ) # Set the AMENT_CURRENT_PREFIX throughout to the in-snap prefix file_utils.replace_in_file( self.installdir, re.compile(r""), re.compile(r"(\${)(AMENT_CURRENT_PREFIX:=)(.*)(})"), _rewrite_prefix, ) # Set the COLCON_CURRENT_PREFIX (if it's in the installdir) to the in-snap # prefix file_utils.replace_in_file( self.installdir, re.compile(r""), re.compile( r"()(COLCON_CURRENT_PREFIX=)(['\"].*{}.*)()".format(self.installdir) ), _rewrite_prefix, ) # Set the _colcon_prefix_sh_COLCON_CURRENT_PREFIX throughout to the in-snap # prefix file_utils.replace_in_file( self.installdir, re.compile(r""), re.compile(r"()(_colcon_prefix_sh_COLCON_CURRENT_PREFIX=)(.*)()"), _rewrite_prefix, ) # Set the _colcon_package_sh_COLCON_CURRENT_PREFIX throughout to the in-snap # prefix file_utils.replace_in_file( self.installdir, re.compile(r""), re.compile(r"()(_colcon_package_sh_COLCON_CURRENT_PREFIX=)(.*)()"), _rewrite_prefix, ) # Set the _colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX throughout to the in-snap # prefix file_utils.replace_in_file( self.installdir, re.compile(r""), re.compile(r"()(_colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX=)(.*)()"), _rewrite_prefix, ) # Set the _colcon_python_executable throughout to use the in-snap python file_utils.replace_in_file( self.installdir, re.compile(r""), re.compile(r"()(_colcon_python_executable=)(.*)()"), _rewrite_prefix, )