def delete_cmd(track): tracks_dict = get_tracks_dict_raw() if track not in tracks_dict['tracks']: error("Track '{0}' does not exist.".format(track), exit=True) del tracks_dict['tracks'][track] info("Deleted track '{0}'.".format(track)) write_tracks_dict_raw(tracks_dict)
def main(sysargs=None): # Check that the current directory is a serviceable git/bloom repo ensure_clean_working_env() ensure_git_root() # Get tracks tracks_dict = get_tracks_dict_raw() if not tracks_dict['tracks']: error("No tracks configured, first create a track with " "'git-bloom-config new <track_name>'", exit=True) # Do argparse stuff parser = get_argument_parser([str(t) for t in tracks_dict['tracks']]) parser = add_global_arguments(parser) args = parser.parse_args(sysargs) handle_global_arguments(args) verify_track(args.track, tracks_dict['tracks'][args.track]) execute_track(args.track, tracks_dict['tracks'][args.track], args.release_increment, args.pretend, args.debug, args.unsafe) # Notify the user of success and next action suggestions print('\n\n') warning("Tip: Check to ensure that the debian tags created have the same " "version as the upstream version you are releasing.") info(fmt("@{gf}@!Everything went as expected, " "you should check that the new tags match your expectations, and " "then push to the release repo with:@|")) info(fmt(" git push --all && git push --tags " "@{kf}@!# You might have to add --force to the second command if you " "are over-writing existing flags"))
def generate_ros_distro_diff(track, repository, distro, distro_file_url, distro_file, distro_file_raw): with inbranch('upstream'): # Check for package.xml(s) try: from catkin_pkg.packages import find_packages except ImportError: debug(traceback.format_exc()) error("catkin_pkg was not detected, please install it.", file=sys.stderr, exit=True) packages = find_packages(os.getcwd()) if len(packages) == 0: warning("No packages found, will not generate 'package: path' entries for rosdistro.") track_dict = get_tracks_dict_raw()['tracks'][track] last_version = track_dict['last_version'] release_inc = track_dict['release_inc'] if repository not in distro_file['repositories']: global _user_provided_release_url distro_file['repositories'][repository] = {'url': _user_provided_release_url or ''} distro_file['repositories'][repository]['version'] = '{0}-{1}'.format(last_version, release_inc) if packages and (len(packages) > 1 or packages.keys()[0] != '.'): distro_file['repositories'][repository]['packages'] = {} for path, package in packages.iteritems(): if os.path.basename(path) == package.name: distro_file['repositories'][repository]['packages'][package.name] = None else: distro_file['repositories'][repository]['packages'][package.name] = path distro_file_name = os.path.join('release', distro_file_url.split('/')[-1]) distro_dump = yaml.dump(distro_file, indent=2, default_flow_style=False) if distro_file_raw != distro_dump: udiff = difflib.unified_diff(distro_file_raw.splitlines(), distro_dump.splitlines(), fromfile=distro_file_name, tofile=distro_file_name) temp_dir = tempfile.mkdtemp() version = distro_file['repositories'][repository]['version'] udiff_file = os.path.join(temp_dir, repository + '-' + version + '.patch') udiff_raw = '' info("Unified diff for the ROS distro file located at '{0}':".format(udiff_file)) for line in udiff: if line.startswith('@@'): udiff_raw += line line = fmt('@{cf}' + line) if line.startswith('+'): if not line.startswith('+++'): line += '\n' udiff_raw += line line = fmt('@{gf}' + line) if line.startswith('-'): if not line.startswith('---'): line += '\n' udiff_raw += line line = fmt('@{rf}' + line) if line.startswith(' '): line += '\n' udiff_raw += line info(line, use_prefix=False, end='') with open(udiff_file, 'w+') as f: f.write(udiff_raw) return udiff_file, distro_dump else: warning("This release resulted in no changes to the ROS distro file...") return None, None
def show(args): tracks_dict = get_tracks_dict_raw() if args.track not in tracks_dict['tracks']: error("Track '{0}' does not exist.".format(args.track), exit=True) info( yaml.dump({args.track: tracks_dict['tracks'][args.track]}, indent=2, default_flow_style=False))
def start_summary(track): global _original_version track_dict = get_tracks_dict_raw()['tracks'][track] if 'last_version' not in track_dict or 'release_inc' not in track_dict: _original_version = 'null' else: last_version = track_dict['last_version'] # Actually current version now release_inc = track_dict['release_inc'] _original_version = "{0}-{1}".format(last_version, release_inc)
def start_summary(track): global _original_version track_dict = get_tracks_dict_raw()["tracks"][track] if "last_version" not in track_dict or "release_inc" not in track_dict: _original_version = "null" else: last_version = track_dict["last_version"] # Actually current version now release_inc = track_dict["release_inc"] _original_version = "{0}-{1}".format(last_version, release_inc)
def copy_track(src, dst): tracks_dict = get_tracks_dict_raw() if src not in tracks_dict['tracks']: error("Track '{0}' does not exist.".format(src), exit=True) if dst in tracks_dict['tracks']: error("Track '{0}' already exists.".format(dst), exit=True) tracks_dict['tracks'][dst] = copy.deepcopy(tracks_dict['tracks'][src]) info("Saving '{0}' track.".format(dst)) write_tracks_dict_raw(tracks_dict)
def start_summary(track): global _original_version track_dict = get_tracks_dict_raw()['tracks'][track] if 'last_version' not in track_dict or 'release_inc' not in track_dict: _original_version = 'null' else: last_version = track_dict[ 'last_version'] # Actually current version now release_inc = track_dict['release_inc'] _original_version = "{0}-{1}".format(last_version, release_inc)
def show_current(): bloom_ls = ls_tree('bloom') bloom_files = [f for f, t in bloom_ls.iteritems() if t == 'file'] if 'bloom.conf' in bloom_files: info("Old bloom.conf file detected, up converting...") convert_old_bloom_conf() bloom_ls = ls_tree('bloom') bloom_files = [f for f, t in bloom_ls.iteritems() if t == 'file'] if 'tracks.yaml' in bloom_files: info(yaml.dump(get_tracks_dict_raw(), indent=2, default_flow_style=False))
def main(sysargs=None): from bloom.config import upconvert_bloom_to_config_branch upconvert_bloom_to_config_branch() # Check that the current directory is a serviceable git/bloom repo ensure_clean_working_env() ensure_git_root() # Get tracks tracks_dict = get_tracks_dict_raw() if not tracks_dict['tracks']: error( "No tracks configured, first create a track with " "'git-bloom-config new <track_name>'", exit=True) # Do argparse stuff parser = get_argument_parser([str(t) for t in tracks_dict['tracks']]) parser = add_global_arguments(parser) args = parser.parse_args(sysargs) handle_global_arguments(args) os.environ['BLOOM_TRACK'] = args.track verify_track(args.track, tracks_dict['tracks'][args.track]) git_clone = GitClone() with git_clone: quiet_git_clone_warning(True) disable_git_clone(True) execute_track(args.track, tracks_dict['tracks'][args.track], args.release_increment, args.pretend, args.debug, args.unsafe, interactive=args.interactive) disable_git_clone(False) quiet_git_clone_warning(False) git_clone.commit() # Notify the user of success and next action suggestions info('\n\n', use_prefix=False) warning("Tip: Check to ensure that the debian tags created have the same " "version as the upstream version you are releasing.") info( fmt("@{gf}@!Everything went as expected, " "you should check that the new tags match your expectations, and " "then push to the release repo with:@|")) info( fmt(" git push --all && git push --tags " "@{kf}@!# You might have to add --force to the second command if you " "are over-writing existing tags"))
def show_current(): bloom_ls = ls_tree(BLOOM_CONFIG_BRANCH) bloom_files = [f for f, t in bloom_ls.iteritems() if t == 'file'] if 'bloom.conf' in bloom_files: info("Old bloom.conf file detected, up converting...") convert_old_bloom_conf() bloom_ls = ls_tree(BLOOM_CONFIG_BRANCH) bloom_files = [f for f, t in bloom_ls.iteritems() if t == 'file'] if 'tracks.yaml' in bloom_files: info( yaml.dump(get_tracks_dict_raw(), indent=2, default_flow_style=False))
def list_tracks(repository, distro): release_repo = get_release_repo(repository, distro) tracks_dict = None with change_directory(release_repo.get_path()): if check_for_bloom_conf(repository): info("No tracks, but old style bloom.conf available for conversion") else: tracks_dict = get_tracks_dict_raw() if tracks_dict and tracks_dict['tracks'].keys(): info("Available tracks: " + str(tracks_dict['tracks'].keys())) else: error("Release repository has no tracks nor an old style bloom.conf file.", exit=True) return tracks_dict['tracks'].keys() if tracks_dict else None
def execute_track(track, track_dict, release_inc, pretend=True, debug=False, fast=False): info("Processing release track settings for '{0}'".format(track)) settings = process_track_settings(track_dict, release_inc) # setup extra settings archive_dir_path = tempfile.mkdtemp() settings['archive_dir_path'] = archive_dir_path if settings['release_tag'] != ':{none}': archive_file = '{name}-{release_tag}.tar.gz'.format(**settings) else: archive_file = '{name}.tar.gz'.format(**settings) settings['archive_path'] = os.path.join(archive_dir_path, archive_file) # execute actions info("", use_prefix=False) info("Executing release track '{0}'".format(track)) for action in track_dict['actions']: if 'bloom-export-upstream' in action and settings['vcs_type'] == 'tar': warning("Explicitly skipping bloom-export-upstream for tar.") settings['archive_path'] = settings['vcs_uri'] continue templated_action = template_str(action, settings) info(fmt("@{bf}@!==> @|@!" + sanitize(str(templated_action)))) if pretend: continue stdout = None stderr = None if bloom.util._quiet: stdout = subprocess.PIPE stderr = subprocess.STDOUT if debug and 'DEBUG' not in os.environ: os.environ['DEBUG'] = '1' if fast and 'BLOOM_UNSAFE' not in os.environ: os.environ['BLOOM_UNSAFE'] = '1' templated_action = templated_action.split() templated_action[0] = find_full_path(templated_action[0]) p = subprocess.Popen(templated_action, stdout=stdout, stderr=stderr, shell=False, env=os.environ.copy()) out, err = p.communicate() if bloom.util._quiet: info(out, use_prefix=False) ret = p.returncode if ret > 0: error(fmt(_error + "Error running command '@!{0}'@|") .format(templated_action), exit=True) info('', use_prefix=False) if not pretend: # Update the release_inc tracks_dict = get_tracks_dict_raw() tracks_dict['tracks'][track]['release_inc'] = settings['release_inc'] tracks_dict['tracks'][track]['last_version'] = settings['version'] write_tracks_dict_raw(tracks_dict, 'Updating release inc to: ' + str(settings['release_inc']))
def new(track, template=None, copy_track=None, overrides={}): """ Creates a new track :param track: Name of the track to create :param template: Template to base new track off :param copy_track: Track to copy values of, if '' then use any availabe track if one exists :param overrides: dict of entries to override default values """ tracks_dict = get_tracks_dict_raw() if track in tracks_dict['tracks']: error("Cannot create track '{0}' beause it exists.".format(track)) error("Run `git-bloom-config edit {0}` instead.".format(track), exit=True) track_dict = copy.copy(DEFAULT_TEMPLATE) template_dict = copy.copy(config_template[template]) if copy_track is not None: if template is not None: error("You cannot specify both a template and track to copy.", exit=True) if copy_track == '' and len(tracks_dict['tracks']) != 0: copy_track = list(reversed(sorted( tracks_dict['tracks'].keys())))[0] if copy_track and copy_track not in tracks_dict['tracks']: error("Cannot copy a track which does not exist: '{0}'".format( copy_track), exit=True) if copy_track: template_dict = tracks_dict['tracks'][copy_track] else: template_dict = {} for key in template_entry_order: if key in template_dict: track_dict[key].default = template_dict[key] if key in overrides: track_dict[key].default = overrides[key] if track_dict[key].default == ':{name}': track_dict[key].default = track ret = safe_input(str(track_dict[key])) if ret: track_dict[ key].default = ret # This type checks against self.values if ret in [':{none}', 'None']: track_dict[key].default = None track_dict[key] = track_dict[key].default tracks_dict['tracks'][track] = track_dict write_tracks_dict_raw(tracks_dict) info("Created '{0}' track.".format(track))
def list_tracks(repository, distro): release_repo = get_release_repo(repository, distro) tracks_dict = None with change_directory(release_repo.get_path()): upconvert_bloom_to_config_branch() if check_for_bloom_conf(repository): info( "No tracks, but old style bloom.conf available for conversion") else: tracks_dict = get_tracks_dict_raw() if tracks_dict and tracks_dict['tracks'].keys(): info("Available tracks: " + str(tracks_dict['tracks'].keys())) else: error( "Release repository has no tracks nor an old style bloom.conf file.", exit=True) return tracks_dict['tracks'].keys() if tracks_dict else None
def new(track, template=None, copy_track=None, overrides={}): """ Creates a new track :param track: Name of the track to create :param template: Template to base new track off :param copy_track: Track to copy values of, if '' then use any availabe track if one exists :param overrides: dict of entries to override default values """ tracks_dict = get_tracks_dict_raw() if track in tracks_dict['tracks']: error("Cannot create track '{0}' beause it exists.".format(track)) error("Run `git-bloom-config edit {0}` instead.".format(track), exit=True) track_dict = copy.copy(DEFAULT_TEMPLATE) template_dict = copy.copy(config_template[template]) if copy_track is not None: if template is not None: error("You cannot specify both a template and track to copy.", exit=True) if copy_track == '' and len(tracks_dict['tracks']) != 0: copy_track = tracks_dict['tracks'].keys()[0] if copy_track and copy_track not in tracks_dict['tracks']: error("Cannot copy a track which does not exist: '{0}'" .format(copy_track), exit=True) if copy_track: template_dict = tracks_dict['tracks'][copy_track] else: template_dict = {} for key in template_entry_order: if key in template_dict: track_dict[key].default = template_dict[key] if key in overrides: track_dict[key].default = overrides[key] if track_dict[key].default == ':{name}': track_dict[key].default = track ret = safe_input(str(track_dict[key])) if ret: track_dict[key].default = ret # This type checks against self.values if ret in [':{none}', 'None']: track_dict[key].default = None track_dict[key] = track_dict[key].default tracks_dict['tracks'][track] = track_dict write_tracks_dict_raw(tracks_dict) info("Created '{0}' track.".format(track))
def generate_ros_distro_diff(track, repository, distro, distro_file_url=ROS_DISTRO_FILE): distro_file_url = distro_file_url.format(distro) distro_file_raw = fetch_distro_file(distro_file_url) distro_file = yaml.load(distro_file_raw) with inbranch('upstream'): # Check for package.xml(s) try: from catkin_pkg.packages import find_packages except ImportError: error("catkin_pkg was not detected, please install it.", file=sys.stderr, exit=True) packages = find_packages(os.getcwd()) if len(packages) == 0: warning("No packages found, will not generate 'package: path' entries for rosdistro.") track_dict = get_tracks_dict_raw()['tracks'][track] last_version = track_dict['last_version'] release_inc = track_dict['release_inc'] distro_file['repositories'][repository]['version'] = '{0}-{1}'.format(last_version, release_inc) if packages and (len(packages) > 1 or packages.keys()[0] != '.'): distro_file['repositories'][repository]['packages'] = {} for path, package in packages.iteritems(): distro_file['repositories'][repository]['packages'][package.name] = path distro_file_name = distro_file_url.split('/')[-1] # distro_dump_orig = yaml.dump(distro_file_orig, indent=2, default_flow_style=False) distro_dump = yaml.dump(distro_file, indent=2, default_flow_style=False) udiff = difflib.unified_diff(distro_file_raw.splitlines(), distro_dump.splitlines(), fromfile=distro_file_name, tofile=distro_file_name) if udiff: info("Unified diff for the ROS distro file located at '{0}':".format(distro_file_url)) for line in udiff: if line.startswith('@@'): line = fmt('@{cf}' + line) if line.startswith('+'): if not line.startswith('+++'): line += '\n' line = fmt('@{gf}' + line) if line.startswith('-'): if not line.startswith('---'): line += '\n' line = fmt('@{rf}' + line) if line.startswith(' '): line += '\n' info(line, use_prefix=False, end='') else: warning("This release resulted in no changes to the ROS distro file...")
def edit(track): tracks_dict = get_tracks_dict_raw() if track not in tracks_dict['tracks']: error("Track '{0}' does not exist.".format(track), exit=True) # Ensure the track is complete track_dict = tracks_dict['tracks'][track] update_track(track_dict) # Prompt for updates for key in template_entry_order: pe = DEFAULT_TEMPLATE[key] pe.default = tracks_dict['tracks'][track][key] ret = raw_input(str(pe)) if ret: pe.default = ret # This type checks against self.values if ret in [':{none}', 'None']: pe.default = None tracks_dict['tracks'][track][key] = pe.default tracks_dict['tracks'][track] = track_dict info("Saving '{0}' track.".format(track)) write_tracks_dict_raw(tracks_dict)
def convert_old_bloom_conf(prefix=None): prefix = prefix if prefix is not None else 'convert' tracks_dict = get_tracks_dict_raw() track = prefix track_count = 0 while track in tracks_dict['tracks']: track_count += 1 track = prefix + str(track_count) track_dict = copy.copy(DEFAULT_TEMPLATE) cmd = 'git config -f bloom.conf bloom.upstream' upstream_repo = check_output(cmd, shell=True).strip() cmd = 'git config -f bloom.conf bloom.upstreamtype' upstream_type = check_output(cmd, shell=True).strip() try: cmd = 'git config -f bloom.conf bloom.upstreambranch' upstream_branch = check_output(cmd, shell=True).strip() except subprocess.CalledProcessError: upstream_branch = '' for key in template_entry_order: if key == 'vcs_uri': track_dict[key] = upstream_repo continue if key == 'vcs_type': track_dict[key] = upstream_type continue if key == 'vcs_uri': track_dict[key] = upstream_branch or None continue track_dict[key] = track_dict[key].default debug('Converted bloom.conf:') with open('bloom.conf', 'r') as f: debug(f.read()) debug('To this track:') debug(str({track: track_dict})) tracks_dict['tracks'][track] = track_dict write_tracks_dict_raw(tracks_dict) execute_command('git rm bloom.conf', shell=True) execute_command('git commit -m "Removed bloom.conf"', shell=True) # Now move the old bloom branch into master upconvert_bloom_to_config_branch()
def execute_track(track, track_dict, release_inc, pretend=True, debug=False, fast=False): info("Processing release track settings for '{0}'".format(track)) settings = process_track_settings(track_dict, release_inc) # setup extra settings archive_dir_path = tempfile.mkdtemp() settings['archive_dir_path'] = archive_dir_path if settings['release_tag'] != ':{none}': archive_file = '{name}-{release_tag}.tar.gz'.format(**settings) else: archive_file = '{name}.tar.gz'.format(**settings) settings['archive_path'] = os.path.join(archive_dir_path, archive_file) # execute actions info("", use_prefix=False) info("Executing release track '{0}'".format(track)) for action in track_dict['actions']: if 'bloom-export-upstream' in action and settings['vcs_type'] == 'tar': warning("Explicitly skipping bloom-export-upstream for tar.") settings['archive_path'] = settings['vcs_uri'] continue templated_action = template_str(action, settings) info(fmt("@{bf}@!==> @|@!" + sanitize(str(templated_action)))) if pretend: continue stdout = None stderr = None if bloom.util._quiet: stdout = subprocess.PIPE stderr = subprocess.STDOUT if debug and 'DEBUG' not in os.environ: os.environ['DEBUG'] = '1' if fast and 'BLOOM_UNSAFE' not in os.environ: os.environ['BLOOM_UNSAFE'] = '1' templated_action = templated_action.split() templated_action[0] = find_full_path(templated_action[0]) p = subprocess.Popen(templated_action, stdout=stdout, stderr=stderr, shell=False, env=os.environ.copy()) out, err = p.communicate() if bloom.util._quiet: info(out, use_prefix=False) ret = p.returncode if ret > 0: if 'bloom-generate' in templated_action[0] and ret == code.GENERATOR_NO_ROSDEP_KEY_FOR_DISTRO: error(fmt(_error + "The following generator action reported that it is missing one or more")) error(fmt(" @|rosdep keys, but that the key exists in other platforms:")) error(fmt("@|'@!{0}'@|").format(templated_action)) info('', use_prefix=False) error(fmt("@|If you are @!@_@{rf}absolutely@| sure that this key is unavailable for the platform in")) error(fmt("@|question, the generator can be skipped and you can proceed with the release.")) if maybe_continue('n', 'Skip generator action and continue with release'): info("\nAction skipped, continuing with release.\n") continue info('', use_prefix=False) error(fmt(_error + "Error running command '@!{0}'@|") .format(templated_action), exit=True) info('', use_prefix=False) if not pretend: # Update the release_inc tracks_dict = get_tracks_dict_raw() tracks_dict['tracks'][track]['release_inc'] = settings['release_inc'] tracks_dict['tracks'][track]['last_version'] = settings['version'] write_tracks_dict_raw(tracks_dict, 'Updating release inc to: ' + str(settings['release_inc']))
def update_summary(track, repository, distro): global _original_version track_dict = get_tracks_dict_raw()['tracks'][track] last_version = track_dict['last_version'] # Actually current version now release_inc = track_dict['release_inc'] version = "{0}-{1}".format(last_version, release_inc) summary_file = get_summary_file() msg = """\ ## {repository} - {version} User `{user}@{hostname}` released the packages in the `{repository}` repository by running `{cmd}` on `{date}` """.format( **{ 'repository': repository, 'date': get_rfc_2822_date(datetime.datetime.now()), 'user': getpass.getuser(), 'hostname': socket.gethostname(), 'cmd': ' '.join(sys.argv), 'version': version }) packages = [p.name for p in get_packages().values()] if len(packages) > 1: msg += "These packages were released:\n" for p in sorted(packages): msg += "- `{0}`\n".format(p) else: package_name = packages[0] msg += "The `{0}` package was released.\n".format(package_name) ignored_packages = get_ignored_packages() if ignored_packages: msg += "\nThese packages were explicitly ignored:\n" for ip in ignored_packages: msg += "- `{0}`\n".format(ip) summary_file = get_summary_file() release_file = get_release_file(distro) reps = release_file.repositories distro_version = reps[repository].version if repository in reps else None msg += """ Version of package(s) in repository `{repo}`: - rosdistro version: `{rosdistro_pv}` - old version: `{old_pv}` - new version: `{new_pv}` Versions of tools used: - bloom version: `{bloom_v}` - catkin_pkg version: `{catkin_pkg_v}` - rosdep version: `{rosdep_v}` - rosdistro version: `{rosdistro_v}` - vcstools version: `{vcstools_v}` """.format(**dict( repo=repository, rosdistro_pv=distro_version or 'null', old_pv=_original_version, new_pv=version, bloom_v=bloom.__version__, catkin_pkg_v=catkin_pkg.__version__, # Until https://github.com/ros-infrastructure/rosdistro/issues/16 rosdistro_v=pkg_resources.require("rosdistro")[0].version, rosdep_v=rosdep2.__version__, vcstools_v=vcstools.__version__.version)) summary_file.write(msg)
def generate_ros_distro_diff(track, repository, distro): release_dict = get_release_file(distro).get_data() # Get packages packages = get_packages() if len(packages) == 0: warning( "No packages found, will not generate 'package: path' entries for rosdistro." ) # Get version track_dict = get_tracks_dict_raw()['tracks'][track] last_version = track_dict['last_version'] release_inc = track_dict['release_inc'] version = '{0}-{1}'.format(last_version, release_inc) # Create a repository if there isn't already one if repository not in release_dict['repositories']: global _user_provided_release_url release_dict['repositories'][repository] = { 'url': _user_provided_release_url } # Update the repository repo = release_dict['repositories'][repository] if 'tags' not in repo: repo['tags'] = {} repo['tags']['release'] = 'release/%s/{package}/{version}' % distro repo['version'] = version if 'packages' not in repo: repo['packages'] = {} for path, pkg in packages.items(): if pkg.name not in repo['packages']: repo['packages'][pkg.name] = {} repo['packages'][ pkg.name]['subfolder'] = path # This will be shortened # Remove any missing packages for pkg_name in dict(repo['packages']): if pkg_name not in [p.name for p in packages.values()]: if pkg_name in repo['packages']: del repo['packages'][pkg_name] # Do the diff distro_file_name = get_relative_release_file_path(distro) updated_release_file = rosdistro.ReleaseFile('distro', release_dict) distro_dump = yaml_from_release_file(updated_release_file) distro_file_raw = load_url_to_file_handle( get_release_file_url(distro)).read() if distro_file_raw != distro_dump: udiff = difflib.unified_diff(distro_file_raw.splitlines(), distro_dump.splitlines(), fromfile=distro_file_name, tofile=distro_file_name) temp_dir = tempfile.mkdtemp() udiff_file = os.path.join(temp_dir, repository + '-' + version + '.patch') udiff_raw = '' info("Unified diff for the ROS distro file located at '{0}':".format( udiff_file)) for line in udiff: if line.startswith('@@'): udiff_raw += line line = fmt('@{cf}' + sanitize(line)) if line.startswith('+'): if not line.startswith('+++'): line += '\n' udiff_raw += line line = fmt('@{gf}' + sanitize(line)) if line.startswith('-'): if not line.startswith('---'): line += '\n' udiff_raw += line line = fmt('@{rf}' + sanitize(line)) if line.startswith(' '): line += '\n' udiff_raw += line info(line, use_prefix=False, end='') with open(udiff_file, 'w+') as f: f.write(udiff_raw) return updated_release_file else: warning( "This release resulted in no changes to the ROS distro file...") return None
def open_pull_request(track, repository, distro, interactive): global _rosdistro_index_commit # Get the diff distribution_file = get_distribution_file(distro) if repository in distribution_file.repositories and \ distribution_file.repositories[repository].release_repository is not None: orig_version = distribution_file.repositories[repository].release_repository.version else: orig_version = None updated_distribution_file = generate_ros_distro_diff(track, repository, distro) if updated_distribution_file is None: # There were no changes, no pull request required return None version = updated_distribution_file.repositories[repository].release_repository.version updated_distro_file_yaml = yaml_from_distribution_file(updated_distribution_file) # Determine if the distro file is hosted on github... base_org, base_repo, base_branch, base_path = get_gh_info(get_disitrbution_file_url(distro)) if None in [base_org, base_repo, base_branch, base_path]: warning("Automated pull request only available via github.com") return # Get the github interface gh = get_github_interface() if gh is None: return None # Determine the head org/repo for the pull request head_org = gh.username # The head org will always be gh user head_repo = None # Check if the github user and the base org are the same if gh.username == base_org: # If it is, then a fork is not necessary head_repo = base_repo else: info(fmt("@{bf}@!==> @|@!Checking on GitHub for a fork to make the pull request from...")) # It is not, so a fork will be required # Check if a fork already exists on the user's account with the same name base_full_name = '{base_org}/{base_repo}'.format(**locals()) try: repo_data = gh.get_repo(gh.username, base_repo) if repo_data.get('fork', False): # Check if it is a fork # If it is, check that it is a fork of the destination parent = repo_data.get('parent', {}).get('full_name', None) if parent == base_full_name: # This is a valid fork head_repo = base_repo except GithubException as exc: debug("Received GithubException while checking for fork: {exc}".format(**locals())) pass # 404 or unauthorized, but unauthorized should have been caught above # If not head_repo, then either the fork has a different name, or there isn't one if head_repo is None: info(fmt("@{bf}@!==> @|@!" + "{head_org}/{base_repo} is not a fork, searching...".format(**locals()))) # First we should look at every repository for the user and see if they are a fork user_repos = gh.list_repos(gh.username) for repo in user_repos: # If it is a fork and the parent is base_org/base_repo if repo.get('fork', False) and repo.get('parent', {}).get('full_name', '') == base_full_name: # Then this is a valid fork head_repo = repo['name'] # If not head_repo still, a fork does not exist and must be created if head_repo is None: warning("Could not find a fork of {base_full_name} on the {gh.username} GitHub account." .format(**locals())) warning("Would you like to create one now?") if not maybe_continue(): warning("Skipping the pull request...") return # Create a fork try: gh.create_fork(base_org, base_repo) # Will raise if not successful head_repo = base_repo except GithubException as exc: error("Aborting pull request: {0}".format(exc)) return info(fmt("@{bf}@!==> @|@!" + "Using this fork to make a pull request from: {head_org}/{head_repo}".format(**locals()))) # Clone the fork info(fmt("@{bf}@!==> @|@!" + "Cloning {0}/{1}...".format(head_org, head_repo))) new_branch = None title = "{0}: {1} in '{2}' [bloom]".format(repository, version, base_path) track_dict = get_tracks_dict_raw()['tracks'][track] body = u"""\ Increasing version of package(s) in repository `{repository}` to `{version}`: - upstream repository: {upstream_repo} - release repository: {release_repo} - distro file: `{distro_file}` - bloom version: `{bloom_version}` - previous version for package: `{orig_version}` """.format( repository=repository, orig_version=orig_version or 'null', version=version, distro_file=base_path, bloom_version=bloom.__version__, upstream_repo=track_dict['vcs_uri'], release_repo=updated_distribution_file.repositories[repository].release_repository.url, ) body += get_changelog_summary(generate_release_tag(distro)) with temporary_directory() as temp_dir: def _my_run(cmd, msg=None): if msg: info(fmt("@{bf}@!==> @|@!" + sanitize(msg))) else: info(fmt("@{bf}@!==> @|@!" + sanitize(str(cmd)))) from subprocess import check_call check_call(cmd, shell=True) # Use the oauth token to clone rosdistro_url = 'https://{gh.token}:[email protected]/{base_org}/{base_repo}.git'.format(**locals()) rosdistro_fork_url = 'https://{gh.token}:[email protected]/{head_org}/{head_repo}.git'.format(**locals()) _my_run('mkdir -p {base_repo}'.format(**locals())) with change_directory(base_repo): _my_run('git init') branches = [x['name'] for x in gh.list_branches(head_org, head_repo)] new_branch = 'bloom-{repository}-{count}' count = 0 while new_branch.format(repository=repository, count=count) in branches: count += 1 new_branch = new_branch.format(repository=repository, count=count) # Final check info(fmt("@{cf}Pull Request Title: @{yf}" + sanitize(title))) info(fmt("@{cf}Pull Request Body : \n@{yf}" + sanitize(body))) msg = fmt("@!Open a @|@{cf}pull request@| @!@{kf}from@| @!'@|@!@{bf}" + "{head_repo}/{head_repo}:{new_branch}".format(**locals()) + "@|@!' @!@{kf}into@| @!'@|@!@{bf}" + "{base_org}/{base_repo}:{base_branch}".format(**locals()) + "@|@!'?") info(msg) if interactive and not maybe_continue(): warning("Skipping the pull request...") return _my_run('git checkout -b {new_branch}'.format(**locals())) _my_run('git pull {rosdistro_url} {base_branch}'.format(**locals()), "Pulling latest rosdistro branch") if _rosdistro_index_commit is not None: _my_run('git reset --hard {_rosdistro_index_commit}'.format(**globals())) with open('{0}'.format(base_path), 'w') as f: info(fmt("@{bf}@!==> @|@!Writing new distribution file: ") + str(base_path)) f.write(updated_distro_file_yaml) _my_run('git add {0}'.format(base_path)) _my_run('git commit -m "{0}"'.format(title)) _my_run('git push {rosdistro_fork_url} {new_branch}'.format(**locals()), "Pushing changes to fork") # Open the pull request return gh.create_pull_request(base_org, base_repo, base_branch, head_org, new_branch, title, body)
def perform_release(repository, track, distro, new_track, interactive, pretend): release_repo = get_release_repo(repository, distro) with change_directory(release_repo.get_path()): # Check to see if the old bloom.conf exists if check_for_bloom_conf(repository): # Convert to a track info("Old bloom.conf file detected.") info(fmt("@{gf}@!==> @|Converting to bloom.conf to track")) convert_old_bloom_conf(None if new_track else distro) upconvert_bloom_to_config_branch() # Check that the track is valid tracks_dict = get_tracks_dict_raw() # If new_track, create the new track first if new_track: if not track: error("You must specify a track when creating a new one.", exit=True) if track in tracks_dict['tracks']: warning("Track '{0}' exists, editing...".format(track)) edit_track_cmd(track) tracks_dict = get_tracks_dict_raw() else: # Create a new track called <track>, # copying an existing track if possible, # and overriding the ros_distro warning("Creating track '{0}'...".format(track)) overrides = {'ros_distro': distro} new_track_cmd(track, copy_track='', overrides=overrides) tracks_dict = get_tracks_dict_raw() if track and track not in tracks_dict['tracks']: error("Given track '{0}' does not exist in release repository.". format(track)) error("Available tracks: " + str(tracks_dict['tracks'].keys()), exit=True) elif not track: tracks = tracks_dict['tracks'].keys() # Error out if there are no tracks if len(tracks) == 0: error("Release repository has no tracks.") info("Manually clone the repository:") info(" git clone {0}".format(release_repo.get_url())) info("And then create a new track:") info(" git-bloom-config new <track name>") error("Run again after creating a track.", exit=True) # Error out if there is more than one track if len(tracks) != 1: error("No track specified and there is not just one track.") error("Please specify one of the available tracks: " + str(tracks), exit=True) # Get the only track track = tracks[0] start_summary(track) # Ensure the track is complete track_dict = tracks_dict['tracks'][track] track_dict = update_track(track_dict) tracks_dict['tracks'][track] = track_dict # Set the release repositories' remote if given release_repo_url = track_dict.get('release_repo_url', None) if release_repo_url is not None: info( fmt("@{gf}@!==> @|") + "Setting release repository remote url to '{0}'".format( release_repo_url)) cmd = 'git remote set-url origin ' + release_repo_url info(fmt("@{bf}@!==> @|@!") + str(cmd)) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Setting the remote url failed, exiting.", exit=True) # Check for push permissions try: info( fmt("@{gf}@!==> @|Testing for push permission on release repository" )) cmd = 'git remote -v' info(fmt("@{bf}@!==> @|@!") + str(cmd)) subprocess.check_call(cmd, shell=True) # Dry run will authenticate, but not push cmd = 'git push --dry-run' info(fmt("@{bf}@!==> @|@!") + str(cmd)) subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Cannot push to remote release repository.", exit=True) # Write the track config before releasing write_tracks_dict_raw(tracks_dict) # Run the release info( fmt("@{gf}@!==> @|") + "Releasing '{0}' using release track '{1}'".format( repository, track)) cmd = 'git-bloom-release ' + str(track) if pretend: cmd += ' --pretend' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Release failed, exiting.", exit=True) info( fmt(_success) + "Released '{0}' using release track '{1}' successfully".format( repository, track)) # Commit the summary update_summary(track, repository, distro) commit_summary() # Check for pushing if interactive: info("Releasing complete, push?") if not maybe_continue(): error("User answered no to continue prompt, aborting.", exit=True) # Push changes to the repository info( fmt("@{gf}@!==> @|") + "Pushing changes to release repository for '{0}'".format( repository)) cmd = 'git push --all' if pretend: cmd += ' --dry-run' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error( "Pushing changes failed, would you like to add '--force' to 'git push --all'?" ) if not maybe_continue(): error("Pushing changes failed, exiting.", exit=True) cmd += ' --force' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing changes failed, exiting.", exit=True) info(fmt(_success) + "Pushed changes successfully") # Push tags to the repository info( fmt("@{gf}@!==> @|") + "Pushing tags to release repository for '{0}'".format(repository)) cmd = 'git push --tags' if pretend: cmd += ' --dry-run' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error( "Pushing changes failed, would you like to add '--force' to 'git push --tags'?" ) if not maybe_continue(): error("Pushing tags failed, exiting.", exit=True) cmd += ' --force' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing tags failed, exiting.", exit=True) info(fmt(_success) + "Pushed tags successfully") # Propose github pull request info( fmt("@{gf}@!==> @|") + "Generating pull request to distro file located at '{0}'".format( get_release_file_url(distro))) try: pull_request_url = open_pull_request(track, repository, distro) if pull_request_url: info( fmt(_success) + "Pull request opened at: {0}".format(pull_request_url)) if 'BLOOM_NO_WEBBROWSER' in os.environ and platform.system( ) not in ['Darwin']: webbrowser.open(pull_request_url) else: info( "The release of your packages was successful, but the pull request failed." ) info( "Please manually open a pull request by editing the file here: '{0}'" .format(get_release_file_url(distro))) info(fmt(_error) + "No pull request opened.") except Exception as e: debug(traceback.format_exc()) error("Failed to open pull request: {0} - {1}".format( type(e).__name__, e), exit=True)
def execute_track(track, track_dict, release_inc, pretend=True, debug=False, fast=False): info("Processing release track settings for '{0}'".format(track)) settings = process_track_settings(track_dict, release_inc) # setup extra settings archive_dir_path = tempfile.mkdtemp() settings['archive_dir_path'] = archive_dir_path if settings['release_tag'] != ':{none}': archive_file = '{name}-{release_tag}.tar.gz'.format(**settings) else: archive_file = '{name}.tar.gz'.format(**settings) settings['archive_path'] = os.path.join(archive_dir_path, archive_file) # execute actions info("", use_prefix=False) info("Executing release track '{0}'".format(track)) for action in track_dict['actions']: if 'bloom-export-upstream' in action and settings['vcs_type'] == 'tar': warning("Explicitly skipping bloom-export-upstream for tar.") settings['archive_path'] = settings['vcs_uri'] continue templated_action = template_str(action, settings) info(fmt("@{bf}@!==> @|@!" + sanitize(str(templated_action)))) if pretend: continue stdout = None stderr = None if bloom.util._quiet: stdout = subprocess.PIPE stderr = subprocess.STDOUT if debug and 'DEBUG' not in os.environ: os.environ['DEBUG'] = '1' if fast and 'BLOOM_UNSAFE' not in os.environ: os.environ['BLOOM_UNSAFE'] = '1' templated_action = templated_action.split() templated_action[0] = find_full_path(templated_action[0]) p = subprocess.Popen(templated_action, stdout=stdout, stderr=stderr, shell=False, env=os.environ.copy()) out, err = p.communicate() if bloom.util._quiet: info(out, use_prefix=False) ret = p.returncode if ret > 0: if 'bloom-generate' in templated_action[ 0] and ret == code.GENERATOR_NO_ROSDEP_KEY_FOR_DISTRO: error( fmt(_error + "The following generator action reported that it is missing one or more" )) error( fmt(" @|rosdep keys, but that the key exists in other platforms:" )) error(fmt("@|'@!{0}'@|").format(templated_action)) info('', use_prefix=False) error( fmt("@|If you are @!@_@{rf}absolutely@| sure that this key is unavailable for the platform in" )) error( fmt("@|question, the generator can be skipped and you can proceed with the release." )) if maybe_continue( 'n', 'Skip generator action and continue with release'): info("\nAction skipped, continuing with release.\n") continue info('', use_prefix=False) error(fmt(_error + "Error running command '@!{0}'@|").format( templated_action), exit=True) info('', use_prefix=False) if not pretend: # Update the release_inc tracks_dict = get_tracks_dict_raw() tracks_dict['tracks'][track]['release_inc'] = settings['release_inc'] tracks_dict['tracks'][track]['last_version'] = settings['version'] # if release tag is set to ask and a custom value is used if settings['version'] != settings['release_tag']: tracks_dict['tracks'][track]['last_release'] = settings[ 'release_tag'] write_tracks_dict_raw( tracks_dict, 'Updating release inc to: ' + str(settings['release_inc']))
def show(args): tracks_dict = get_tracks_dict_raw() if args.track not in tracks_dict['tracks']: error("Track '{0}' does not exist.".format(args.track), exit=True) info(yaml.dump({args.track: tracks_dict['tracks'][args.track]}, indent=2, default_flow_style=False))
def update_summary(track, repository, distro): global _original_version track_dict = get_tracks_dict_raw()['tracks'][track] last_version = track_dict['last_version'] # Actually current version now release_inc = track_dict['release_inc'] version = "{0}-{1}".format(last_version, release_inc) summary_file = get_summary_file() msg = """\ ## {repository} ({distro}) - {version} User `{user}@{hostname}` released the packages in the `{repository}` repository into the \ `{distro}` distro by running `{cmd}` on `{date}` """.format( repository=repository, distro=distro, date=get_rfc_2822_date(datetime.datetime.now()), user=getpass.getuser(), hostname=socket.gethostname(), cmd=' '.join(sys.argv), version=version ) packages = [p.name for p in get_packages().values()] if len(packages) > 1: msg += "These packages were released:\n" for p in sorted(packages): msg += "- `{0}`\n".format(p) else: package_name = packages[0] msg += "The `{0}` package was released.\n".format(package_name) ignored_packages = get_ignored_packages() if ignored_packages: msg += "\nThese packages were explicitly ignored:\n" for ip in ignored_packages: msg += "- `{0}`\n".format(ip) summary_file = get_summary_file() release_file = get_distribution_file(distro) reps = release_file.repositories distro_version = None if repository in reps and reps[repository].release_repository is not None: distro_version = reps[repository].release_repository.version msg += """ Version of package(s) in repository `{repo}`: - rosdistro version: `{rosdistro_pv}` - old version: `{old_pv}` - new version: `{new_pv}` Versions of tools used: - bloom version: `{bloom_v}` - catkin_pkg version: `{catkin_pkg_v}` - rosdep version: `{rosdep_v}` - rosdistro version: `{rosdistro_v}` - vcstools version: `{vcstools_v}` """.format( repo=repository, rosdistro_pv=distro_version or 'null', old_pv=_original_version, new_pv=version, bloom_v=bloom.__version__, catkin_pkg_v=catkin_pkg.__version__, # Until https://github.com/ros-infrastructure/rosdistro/issues/16 rosdistro_v=pkg_resources.require("rosdistro")[0].version, rosdep_v=rosdep2.__version__, vcstools_v=vcstools.__version__.version ) summary_file.write(msg)
def perform_release(repository, track, distro, new_track, interactive, pretend, pull_request_only): release_repo = get_release_repo(repository, distro) with change_directory(release_repo.get_path()): # Check to see if the old bloom.conf exists if check_for_bloom_conf(repository): # Convert to a track info("Old bloom.conf file detected.") info(fmt("@{gf}@!==> @|Converting to bloom.conf to track")) convert_old_bloom_conf(None if new_track else distro) upconvert_bloom_to_config_branch() # Check that the track is valid tracks_dict = get_tracks_dict_raw() # If new_track, create the new track first if new_track: if not track: error("You must specify a track when creating a new one.", exit=True) if track in tracks_dict['tracks']: warning("Track '{0}' exists, editing...".format(track)) edit_track_cmd(track) tracks_dict = get_tracks_dict_raw() else: # Create a new track called <track>, # copying an existing track if possible, # and overriding the ros_distro warning("Creating track '{0}'...".format(track)) overrides = {'ros_distro': distro} new_track_cmd(track, copy_track='', overrides=overrides) tracks_dict = get_tracks_dict_raw() if track and track not in tracks_dict['tracks']: error("Given track '{0}' does not exist in release repository." .format(track)) error("Available tracks: " + str(tracks_dict['tracks'].keys()), exit=True) elif not track: tracks = tracks_dict['tracks'].keys() # Error out if there are no tracks if len(tracks) == 0: error("Release repository has no tracks.") info("Manually clone the repository:") info(" git clone {0}".format(release_repo.get_url())) info("And then create a new track:") info(" git-bloom-config new <track name>") error("Run again after creating a track.", exit=True) # Error out if there is more than one track if len(tracks) != 1: error("No track specified and there is not just one track.") error("Please specify one of the available tracks: " + str(tracks), exit=True) # Get the only track track = tracks[0] start_summary(track) if not pull_request_only: _perform_release(repository, track, distro, new_track, interactive, pretend, tracks_dict) # Propose github pull request info(fmt("@{gf}@!==> @|") + "Generating pull request to distro file located at '{0}'" .format(get_disitrbution_file_url(distro))) try: pull_request_url = open_pull_request(track, repository, distro) if pull_request_url: info(fmt(_success) + "Pull request opened at: {0}".format(pull_request_url)) if 'BLOOM_NO_WEBBROWSER' not in os.environ and platform.system() in ['Darwin']: webbrowser.open(pull_request_url) else: info("The release of your packages was successful, but the pull request failed.") info("Please manually open a pull request by editing the file here: '{0}'" .format(get_disitrbution_file_url(distro))) info(fmt(_error) + "No pull request opened.") except Exception as e: debug(traceback.format_exc()) error("Failed to open pull request: {0} - {1}".format(type(e).__name__, e), exit=True)
def generate_ros_distro_diff(track, repository, distro): distribution_dict = get_distribution_file(distro).get_data() # Get packages packages = get_packages() if len(packages) == 0: warning("No packages found, will not generate 'package: path' entries for rosdistro.") # Get version track_dict = get_tracks_dict_raw()['tracks'][track] last_version = track_dict['last_version'] release_inc = track_dict['release_inc'] version = '{0}-{1}'.format(last_version, release_inc) # Create a repository if there isn't already one if repository not in distribution_dict['repositories']: global _user_provided_release_url distribution_dict['repositories'][repository] = {} # Create a release entry if there isn't already one if 'release' not in distribution_dict['repositories'][repository]: distribution_dict['repositories'][repository]['release'] = { 'url': _user_provided_release_url } # Update the repository repo = distribution_dict['repositories'][repository]['release'] if 'tags' not in repo: repo['tags'] = {} repo['tags']['release'] = generate_release_tag(distro) repo['version'] = version if 'packages' not in repo: repo['packages'] = [] for path, pkg in packages.items(): if pkg.name not in repo['packages']: repo['packages'].append(pkg.name) # Remove any missing packages packages_being_released = [p.name for p in packages.values()] for pkg_name in list(repo['packages']): if pkg_name not in packages_being_released: repo['packages'].remove(pkg_name) repo['packages'].sort() # Do the diff distro_file_name = get_relative_distribution_file_path(distro) updated_distribution_file = rosdistro.DistributionFile(distro, distribution_dict) distro_dump = yaml_from_distribution_file(updated_distribution_file) distro_file_raw = load_url_to_file_handle(get_disitrbution_file_url(distro)).read() if distro_file_raw != distro_dump: # Calculate the diff udiff = difflib.unified_diff(distro_file_raw.splitlines(), distro_dump.splitlines(), fromfile=distro_file_name, tofile=distro_file_name) temp_dir = tempfile.mkdtemp() udiff_file = os.path.join(temp_dir, repository + '-' + version + '.patch') udiff_raw = '' info("Unified diff for the ROS distro file located at '{0}':".format(udiff_file)) for line in udiff: if line.startswith('@@'): udiff_raw += line line = fmt('@{cf}' + sanitize(line)) if line.startswith('+'): if not line.startswith('+++'): line += '\n' udiff_raw += line line = fmt('@{gf}' + sanitize(line)) if line.startswith('-'): if not line.startswith('---'): line += '\n' udiff_raw += line line = fmt('@{rf}' + sanitize(line)) if line.startswith(' '): line += '\n' udiff_raw += line info(line, use_prefix=False, end='') # Assert that only this repository is being changed distro_file_yaml = yaml.load(distro_file_raw) distro_yaml = yaml.load(distro_dump) if 'repositories' in distro_file_yaml: distro_file_repos = distro_file_yaml['repositories'] for repo in distro_yaml['repositories']: if repo == repository: continue if repo not in distro_file_repos or distro_file_repos[repo] != distro_yaml['repositories'][repo]: error("This generated pull request modifies a repository entry other than the one being released.") error("This likely occured because the upstream rosdistro changed during this release.") error("This pull request will abort, please re-run this command with the -p option to try again.", exit=True) # Write the diff out to file with open(udiff_file, 'w+') as f: f.write(udiff_raw) # Return the diff return updated_distribution_file else: warning("This release resulted in no changes to the ROS distro file...") return None
def generate_substitutions_from_package( package, os_name, os_version, ros_distro, installation_prefix='/usr', pkgrel=0, peer_packages=None, releaser_history=None, fallback_resolver=None ): tracks_dict = get_tracks_dict_raw() peer_packages = peer_packages or [] data = {} # Name, Version, Description data['Name'] = package.name data['Version'] = package.version data['Description'] = archify_string(package.description) # License if not package.licenses or not package.licenses[0]: error("No license set for package '{0}', aborting.".format(package.name), exit=True) data['Licenses'] = package.licenses # Websites websites = [str(url) for url in package.urls if url.type == 'website'] data['Homepage'] = websites[0] if websites else '' if data['Homepage'] == '': warning("No homepage set") # Package Release Number # Bloom's release number starts at 0 however Arch Linux expects it to starts at 1 by convention. data['Pkgrel'] = str(int(pkgrel)+1) # Package name data['Package'] = sanitize_package_name(package.name) # Installation prefix data['InstallationPrefix'] = installation_prefix # Resolve dependencies depends = package.run_depends + package.buildtool_export_depends build_depends = package.build_depends + package.buildtool_depends + package.test_depends unresolved_keys = depends + build_depends + package.replaces + package.conflicts # The installer key is not considered here, but it is checked when the keys are checked before this resolved_deps = resolve_dependencies(unresolved_keys, os_name, os_version, ros_distro, peer_packages + [d.name for d in package.replaces + package.conflicts], fallback_resolver) data['Depends'] = sorted( set(format_depends(depends, resolved_deps)) ) data['BuildDepends'] = sorted( set(format_depends(build_depends, resolved_deps)) ) data['Replaces'] = sorted( set(format_depends(package.replaces, resolved_deps)) ) data['Conflicts'] = sorted( set(format_depends(package.conflicts, resolved_deps)) ) # Set the distribution data['Distribution'] = os_version # Use the time stamp to set the date strings stamp = datetime.datetime.now(tz.tzlocal()) data['Date'] = stamp.strftime('%a %b %d %Y') data['ROSDistribution'] = ros_distro # Maintainers maintainers = [] for m in package.maintainers: maintainers.append(str(m)) data['Maintainer'] = maintainers[0] data['Maintainers'] = ', '.join(maintainers) # Changelog if releaser_history: sorted_releaser_history = sorted(releaser_history, key=lambda k: LooseVersion(k), reverse=True) sorted_releaser_history = sorted(sorted_releaser_history, key=lambda k: strptime(releaser_history.get(k)[0], '%a %b %d %Y'), reverse=True) changelogs = [(v, releaser_history[v]) for v in sorted_releaser_history] else: # Ensure at least a minimal changelog changelogs = [] print("Version!",package.version) if package.version + '-' + str(pkgrel) not in [x[0] for x in changelogs]: changelogs.insert(0, ( package.version + '-' + str(pkgrel), ( data['Date'], package.maintainers[0].name, package.maintainers[0].email ) )) data['changelogs'] = changelogs # Summarize dependencies summarize_dependency_mapping(data, depends, build_depends, resolved_deps) def convertToUnicode(obj): if sys.version_info.major == 2: if isinstance(obj, str): return unicode(obj.decode('utf8')) elif isinstance(obj, unicode): return obj else: if isinstance(obj, bytes): return str(obj.decode('utf8')) elif isinstance(obj, str): return obj if isinstance(obj, list): for i, val in enumerate(obj): obj[i] = convertToUnicode(val) return obj elif isinstance(obj, type(None)): return None elif isinstance(obj, tuple): obj_tmp = list(obj) for i, val in enumerate(obj_tmp): obj_tmp[i] = convertToUnicode(obj_tmp[i]) return tuple(obj_tmp) elif isinstance(obj, int): return obj elif isinstance(obj, int): return obj raise RuntimeError('need to deal with type %s' % (str(type(obj)))) for item in data.items(): data[item[0]] = convertToUnicode(item[1]) return data
def perform_release(repository, track, distro, new_track, interactive, pretend, ssh_pull_request): release_repo = get_release_repo(repository, distro) with change_directory(release_repo.get_path()): # Check to see if the old bloom.conf exists if check_for_bloom_conf(repository): # Convert to a track info("Old bloom.conf file detected.") info(fmt("@{gf}@!==> @|Converting to bloom.conf to track")) convert_old_bloom_conf(None if new_track else distro) upconvert_bloom_to_config_branch() # Check that the track is valid tracks_dict = get_tracks_dict_raw() # If new_track, create the new track first if new_track: if not track: error("You must specify a track when creating a new one.", exit=True) if track in tracks_dict['tracks']: warning("Track '{0}' exists, editing...".format(track)) edit_track_cmd(track) tracks_dict = get_tracks_dict_raw() else: # Create a new track called <track>, # copying an existing track if possible, # and overriding the ros_distro warning("Creating track '{0}'...".format(track)) overrides = {'ros_distro': distro} new_track_cmd(track, copy_track='', overrides=overrides) tracks_dict = get_tracks_dict_raw() if track and track not in tracks_dict['tracks']: error("Given track '{0}' does not exist in release repository." .format(track)) error("Available tracks: " + str(tracks_dict['tracks'].keys()), exit=True) elif not track: tracks = tracks_dict['tracks'].keys() # Error out if there are no tracks if len(tracks) == 0: error("Release repository has no tracks.") info("Manually clone the repository:") info(" git clone {0}".format(release_repo.get_url())) info("And then create a new track:") info(" git-bloom-config new <track name>") error("Run again after creating a track.", exit=True) # Error out if there is more than one track if len(tracks) != 1: error("No track specified and there is not just one track.") error("Please specify one of the available tracks: " + str(tracks), exit=True) # Get the only track track = tracks[0] start_summary(track) # Ensure the track is complete track_dict = tracks_dict['tracks'][track] track_dict = update_track(track_dict) tracks_dict['tracks'][track] = track_dict # Set the release repositories' remote if given release_repo_url = track_dict.get('release_repo_url', None) if release_repo_url is not None: info(fmt("@{gf}@!==> @|") + "Setting release repository remote url to '{0}'" .format(release_repo_url)) cmd = 'git remote set-url origin ' + release_repo_url info(fmt("@{bf}@!==> @|@!") + str(cmd)) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Setting the remote url failed, exiting.", exit=True) # Check for push permissions try: info(fmt( "@{gf}@!==> @|Testing for push permission on release repository" )) cmd = 'git remote -v' info(fmt("@{bf}@!==> @|@!") + str(cmd)) subprocess.check_call(cmd, shell=True) # Dry run will authenticate, but not push cmd = 'git push --dry-run' info(fmt("@{bf}@!==> @|@!") + str(cmd)) subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Cannot push to remote release repository.", exit=True) # Write the track config before releasing write_tracks_dict_raw(tracks_dict) # Run the release info(fmt("@{gf}@!==> @|") + "Releasing '{0}' using release track '{1}'" .format(repository, track)) cmd = 'git-bloom-release ' + str(track) if pretend: cmd += ' --pretend' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Release failed, exiting.", exit=True) info(fmt(_success) + "Released '{0}' using release track '{1}' successfully" .format(repository, track)) # Commit the summary update_summary(track, repository, distro) commit_summary() # Check for pushing if interactive: info("Releasing complete, push?") if not maybe_continue(): error("User answered no to continue prompt, aborting.", exit=True) # Push changes to the repository info(fmt("@{gf}@!==> @|") + "Pushing changes to release repository for '{0}'" .format(repository)) cmd = 'git push --all' if pretend: cmd += ' --dry-run' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing changes failed, would you like to add '--force' to 'git push --all'?") if not maybe_continue(): error("Pushing changes failed, exiting.", exit=True) cmd += ' --force' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing changes failed, exiting.", exit=True) info(fmt(_success) + "Pushed changes successfully") # Push tags to the repository info(fmt("@{gf}@!==> @|") + "Pushing tags to release repository for '{0}'" .format(repository)) cmd = 'git push --tags' if pretend: cmd += ' --dry-run' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing changes failed, would you like to add '--force' to 'git push --tags'?") if not maybe_continue(): error("Pushing tags failed, exiting.", exit=True) cmd += ' --force' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing tags failed, exiting.", exit=True) info(fmt(_success) + "Pushed tags successfully") # Propose github pull request info(fmt("@{gf}@!==> @|") + "Generating pull request to distro file located at '{0}'" .format(get_disitrbution_file_url(distro))) try: pull_request_url = open_pull_request(track, repository, distro, ssh_pull_request) if pull_request_url: info(fmt(_success) + "Pull request opened at: {0}".format(pull_request_url)) if 'BLOOM_NO_WEBBROWSER' in os.environ and platform.system() not in ['Darwin']: webbrowser.open(pull_request_url) else: info("The release of your packages was successful, but the pull request failed.") info("Please manually open a pull request by editing the file here: '{0}'" .format(get_disitrbution_file_url(distro))) info(fmt(_error) + "No pull request opened.") except Exception as e: debug(traceback.format_exc()) error("Failed to open pull request: {0} - {1}".format(type(e).__name__, e), exit=True)
def generate_ros_distro_diff(track, repository, distro): distribution_dict = get_distribution_file(distro).get_data() # Get packages packages = get_packages() if len(packages) == 0: warning("No packages found, will not generate 'package: path' entries for rosdistro.") # Get version track_dict = get_tracks_dict_raw()['tracks'][track] last_version = track_dict['last_version'] release_inc = track_dict['release_inc'] version = '{0}-{1}'.format(last_version, release_inc) # Create a repository if there isn't already one if repository not in distribution_dict['repositories']: global _user_provided_release_url distribution_dict['repositories'][repository] = {} # Create a release entry if there isn't already one if 'release' not in distribution_dict['repositories'][repository]: distribution_dict['repositories'][repository]['release'] = { 'url': _user_provided_release_url } # Update the repository repo = distribution_dict['repositories'][repository]['release'] if 'tags' not in repo: repo['tags'] = {} repo['tags']['release'] = 'release/%s/{package}/{version}' % distro repo['version'] = version if 'packages' not in repo: repo['packages'] = [] for path, pkg in packages.items(): if pkg.name not in repo['packages']: repo['packages'].append(pkg.name) # Remove any missing packages packages_being_released = [p.name for p in packages.values()] for pkg_name in list(repo['packages']): if pkg_name not in packages_being_released: repo['packages'].remove(pkg_name) repo['packages'].sort() # Do the diff distro_file_name = get_relative_distribution_file_path(distro) updated_distribution_file = rosdistro.DistributionFile(distro, distribution_dict) distro_dump = yaml_from_distribution_file(updated_distribution_file) distro_file_raw = load_url_to_file_handle(get_disitrbution_file_url(distro)).read() if distro_file_raw != distro_dump: udiff = difflib.unified_diff(distro_file_raw.splitlines(), distro_dump.splitlines(), fromfile=distro_file_name, tofile=distro_file_name) temp_dir = tempfile.mkdtemp() udiff_file = os.path.join(temp_dir, repository + '-' + version + '.patch') udiff_raw = '' info("Unified diff for the ROS distro file located at '{0}':".format(udiff_file)) for line in udiff: if line.startswith('@@'): udiff_raw += line line = fmt('@{cf}' + sanitize(line)) if line.startswith('+'): if not line.startswith('+++'): line += '\n' udiff_raw += line line = fmt('@{gf}' + sanitize(line)) if line.startswith('-'): if not line.startswith('---'): line += '\n' udiff_raw += line line = fmt('@{rf}' + sanitize(line)) if line.startswith(' '): line += '\n' udiff_raw += line info(line, use_prefix=False, end='') with open(udiff_file, 'w+') as f: f.write(udiff_raw) return updated_distribution_file else: warning("This release resulted in no changes to the ROS distro file...") return None
def generate_ros_distro_diff(track, repository, distro): release_dict = get_release_file(distro).get_data() # Get packages packages = get_packages() if len(packages) == 0: warning("No packages found, will not generate 'package: path' entries for rosdistro.") # Get version track_dict = get_tracks_dict_raw()["tracks"][track] last_version = track_dict["last_version"] release_inc = track_dict["release_inc"] version = "{0}-{1}".format(last_version, release_inc) # Create a repository if there isn't already one if repository not in release_dict["repositories"]: global _user_provided_release_url release_dict["repositories"][repository] = {"url": _user_provided_release_url} # Update the repository repo = release_dict["repositories"][repository] if "tags" not in repo: repo["tags"] = {} repo["tags"]["release"] = "release/%s/{package}/{version}" % distro repo["version"] = version if "packages" not in repo: repo["packages"] = {} for path, pkg in packages.items(): if pkg.name not in repo["packages"]: repo["packages"][pkg.name] = {} repo["packages"][pkg.name]["subfolder"] = path # This will be shortened # Remove any missing packages for pkg_name in dict(repo["packages"]): if pkg_name not in [p.name for p in packages.values()]: if pkg_name in repo["packages"]: del repo["packages"][pkg_name] # Do the diff distro_file_name = get_relative_release_file_path(distro) updated_release_file = rosdistro.ReleaseFile("distro", release_dict) distro_dump = yaml_from_release_file(updated_release_file) distro_file_raw = load_url_to_file_handle(get_release_file_url(distro)).read() if distro_file_raw != distro_dump: udiff = difflib.unified_diff( distro_file_raw.splitlines(), distro_dump.splitlines(), fromfile=distro_file_name, tofile=distro_file_name ) temp_dir = tempfile.mkdtemp() udiff_file = os.path.join(temp_dir, repository + "-" + version + ".patch") udiff_raw = "" info("Unified diff for the ROS distro file located at '{0}':".format(udiff_file)) for line in udiff: if line.startswith("@@"): udiff_raw += line line = fmt("@{cf}" + sanitize(line)) if line.startswith("+"): if not line.startswith("+++"): line += "\n" udiff_raw += line line = fmt("@{gf}" + sanitize(line)) if line.startswith("-"): if not line.startswith("---"): line += "\n" udiff_raw += line line = fmt("@{rf}" + sanitize(line)) if line.startswith(" "): line += "\n" udiff_raw += line info(line, use_prefix=False, end="") with open(udiff_file, "w+") as f: f.write(udiff_raw) return updated_release_file else: warning("This release resulted in no changes to the ROS distro file...") return None
def perform_release(repository, track, distro, new_track, interactive): release_repo = get_release_repo(repository, distro) with change_directory(release_repo.get_path()): # Check for push permissions try: info(fmt("@{gf}@!==> @|Testing for push permission on release repository")) check_output('git push', shell=True) except subprocess.CalledProcessError: error("Cannot push to remote release repository.", exit=True) # Check to see if the old bloom.conf exists if check_for_bloom_conf(repository): # Convert to a track info("Old bloom.conf file detected.") info(fmt("@{gf}@!==> @|Converting to bloom.conf to track")) convert_old_bloom_conf(None if new_track else distro) # Check that the track is valid tracks_dict = get_tracks_dict_raw() # If new_track, create the new track first if new_track: if not track: error("You must specify a track when creating a new one.", exit=True) overrides = {'ros_distro': distro} if track in tracks_dict['tracks']: warning("Track '{0}' exists, editing instead...".format(track)) edit_track_cmd(track) else: # Create a new track called <track>, # copying an existing track if possible, # and overriding the ros_distro new_track_cmd(track, copy_track='', overrides=overrides) tracks_dict = get_tracks_dict_raw() if track and track not in tracks_dict['tracks']: error("Given track '{0}' does not exist in release repository." .format(track)) error("Available tracks: " + str(tracks_dict['tracks'].keys()), exit=True) elif not track: tracks = tracks_dict['tracks'].keys() # Error out if there are no tracks if len(tracks) == 0: error("Release repository has no tracks.") info("Manually clone the repository:") info(" git clone {0}".format(release_repo.get_url())) info("And then create a new track:") info(" git-bloom-config new <track name>") error("Run again after creating a track.", exit=True) # Error out if there is more than one track if len(tracks) != 1: error("No track specified and there is not just one track.") error("Please specify one of the available tracks: " + str(tracks), exit=True) # Get the only track track = tracks[0] # Ensure the track is complete track_dict = tracks_dict['tracks'][track] update_track(track_dict) tracks_dict['tracks'][track] = track_dict write_tracks_dict_raw(tracks_dict) # Run the release info(fmt("@{gf}@!==> @|") + "Releasing '{0}' using release track '{1}'" .format(repository, track)) cmd = 'git-bloom-release ' + str(track) info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Release failed, exiting.", exit=True) info(fmt(_success) + "Released '{0}' using release track '{1}' successfully" .format(repository, track)) # Check for pushing if interactive: info("Releasing complete, push?") if not maybe_continue(): error("User answered no to continue prompt, aborting.", exit=True) # Push changes to the repository info(fmt("@{gf}@!==> @|") + "Pushing changes to release repository for '{0}'" .format(repository)) cmd = 'git push --all' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing changes failed, would you like to add '--force' to 'git push --all'?") if not maybe_continue(): error("Pushing changes failed, exiting.", exit=True) cmd += ' --force' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing changes failed, exiting.", exit=True) info(fmt(_success) + "Pushed changes successfully") # Push tags to the repository info(fmt("@{gf}@!==> @|") + "Pushing tags to release repository for '{0}'" .format(repository)) cmd = 'git push --tags' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing changes failed, would you like to add '--force' to 'git push --tags'?") if not maybe_continue(): error("Pushing tags failed, exiting.", exit=True) cmd += ' --force' info(fmt("@{bf}@!==> @|@!" + str(cmd))) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError: error("Pushing tags failed, exiting.", exit=True) info(fmt(_success) + "Pushed tags successfully") # Propose github pull request info(fmt("@{gf}@!==> @|") + "Generating pull request to distro file located at '{0}'" .format(ROS_DISTRO_FILE).format(distro)) generate_ros_distro_diff(track, repository, distro) info("In the future this will create a pull request for you, done for now...") info(fmt(_success) + "Pull request opened at: '{0}'".format('Not yet Implemented'))
def generate_ros_distro_diff(track, repository, distro): distribution_dict = get_distribution_file(distro).get_data() # Get packages packages = get_packages() if len(packages) == 0: warning("No packages found, will not generate 'package: path' entries for rosdistro.") # Get version track_dict = get_tracks_dict_raw()['tracks'][track] last_version = track_dict['last_version'] release_inc = track_dict['release_inc'] version = '{0}-{1}'.format(last_version, release_inc).encode('utf-8') # Create a repository if there isn't already one if repository not in distribution_dict['repositories']: global _user_provided_release_url distribution_dict['repositories'][repository] = {} # Create a release entry if there isn't already one if 'release' not in distribution_dict['repositories'][repository]: distribution_dict['repositories'][repository]['release'.encode('utf-8')] = { 'url'.encode('utf-8'): _user_provided_release_url } # Update the repository repo = distribution_dict['repositories'][repository]['release'] if 'tags' not in repo: repo['tags'.encode('utf-8')] = {} repo['tags']['release'.encode('utf-8')] = generate_release_tag(distro) repo['version'.encode('utf-8')] = version if 'packages' not in repo: repo['packages'.encode('utf-8')] = [] for path, pkg in packages.items(): if pkg.name not in repo['packages']: repo['packages'].append(pkg.name) # Remove any missing packages packages_being_released = [p.name for p in packages.values()] for pkg_name in list(repo['packages']): if pkg_name not in packages_being_released: repo['packages'].remove(pkg_name) repo['packages'].sort() def get_repository_info_from_user(): data = {} while True: vcs_type = safe_input('VCS type [git, svn, hg, bzr]: ') if vcs_type in ['git', 'svn', 'hg', 'bzr']: break error("'{0}' is not a valid vcs type.".format(vcs_type)) if not maybe_continue(msg='Try again'): return {} data['type'] = vcs_type while True: url = safe_input('VCS url: ') if url: break error("Nothing entered for url.") if not maybe_continue(msg='Try again'): return {} data['url'] = url while True: version = safe_input('VCS version [commit, tag, branch, etc]: ') if version: break error("Nothing entered for version.") if not maybe_continue(msg='Try again'): return {} data['version'] = version return data # Ask for doc entry if 'BLOOM_DONT_ASK_FOR_DOCS' not in os.environ: docs = distribution_dict['repositories'][repository].get('doc', {}) if not docs and maybe_continue(msg='Would you like to add documentation information for this repository?'): info("Please enter your repository information for the doc generation job.") info("This information should point to the repository from which documentation should be generated.") docs = get_repository_info_from_user() distribution_dict['repositories'][repository]['doc'] = docs # Ask for source entry if 'BLOOM_DONT_ASK_FOR_SOURCE' not in os.environ: source = distribution_dict['repositories'][repository].get('source', {}) if not source and maybe_continue(msg='Would you like to add source information for this repository?'): info("Please enter information which points ot the active development branch for this repository.") info("This information is used to run continuous integration jobs and for developers to checkout from.") source = get_repository_info_from_user() distribution_dict['repositories'][repository]['source'] = source # Ask for maintainership information if 'BLOOM_DONT_ASK_FOR_MAINTENANCE_STATUS' not in os.environ: status = distribution_dict['repositories'][repository].get('status', None) description = distribution_dict['repositories'][repository].get('status_description', None) if status is None and maybe_continue(msg='Would you like to add a maintenance status for this repository?'): info("Please enter a maintenance status.") info("Valid maintenance statuses:") info("- developed: active development is in progress") info("- maintained: no new development, but bug fixes and pull requests are addressed") info("- end-of-life: should not be used, will disapear at some point") while True: status = safe_input('Status: ') if status in ['developed', 'maintained', 'end-of-life']: break error("'{0}' is not a valid status.".format(status)) if not maybe_continue(msg='Try again'): status = None break if status is not None: info("You can also enter a status description.") info("This is usually reserved for giving a reason when a status is 'end-of-life'.") if description is not None: info("Current status description: {0}".format(description)) description_in = safe_input('Status Description [press Enter for no change]: ') if description_in: description = description_in if status is not None: distribution_dict['repositories'][repository]['status'] = status if description is not None: distribution_dict['repositories'][repository]['status_description'] = description # Do the diff distro_file_name = get_relative_distribution_file_path(distro) updated_distribution_file = rosdistro.DistributionFile(distro, distribution_dict) distro_dump = yaml_from_distribution_file(updated_distribution_file) distro_file_raw = load_url_to_file_handle(get_disitrbution_file_url(distro)).read() if distro_file_raw != distro_dump: # Calculate the diff udiff = difflib.unified_diff(distro_file_raw.splitlines(), distro_dump.splitlines(), fromfile=distro_file_name, tofile=distro_file_name) temp_dir = tempfile.mkdtemp() udiff_file = os.path.join(temp_dir, repository + '-' + version + '.patch') udiff_raw = '' info("Unified diff for the ROS distro file located at '{0}':".format(udiff_file)) for line in udiff: if line.startswith('@@'): udiff_raw += line line = fmt('@{cf}' + sanitize(line)) if line.startswith('+'): if not line.startswith('+++'): line += '\n' udiff_raw += line line = fmt('@{gf}' + sanitize(line)) if line.startswith('-'): if not line.startswith('---'): line += '\n' udiff_raw += line line = fmt('@{rf}' + sanitize(line)) if line.startswith(' '): line += '\n' udiff_raw += line info(line, use_prefix=False, end='') # Assert that only this repository is being changed distro_file_yaml = yaml.load(distro_file_raw) distro_yaml = yaml.load(distro_dump) if 'repositories' in distro_file_yaml: distro_file_repos = distro_file_yaml['repositories'] for repo in distro_yaml['repositories']: if repo == repository: continue if repo not in distro_file_repos or distro_file_repos[repo] != distro_yaml['repositories'][repo]: error("This generated pull request modifies a repository entry other than the one being released.") error("This likely occured because the upstream rosdistro changed during this release.") error("This pull request will abort, please re-run this command with the -p option to try again.", exit=True) # Write the diff out to file with open(udiff_file, 'w+') as f: f.write(udiff_raw) # Return the diff return updated_distribution_file else: warning("This release resulted in no changes to the ROS distro file...") return None