def setup_sdist(opts): LOGGER.info("Running setup.py sdist...") local('cd {} && python setup.py sdist'.format(repo_directory())) dist_dir = os.path.join(repo_directory(), 'dist') pkg = opts.package if opts.pypi_package_name: pkg = opts.pypi_package_name package = "{}-{}.tar.gz".format(pkg, opts.version) return os.path.join(dist_dir, package)
def load_configuration(package_dir=None, gitconfig_file=None): """ _load_configuration_ Load the cirrus.conf file and parse it into a nested dictionary like Configuration instance. :param package_dir: Location of cirrus managed package if not pwd :param gitconfig_file: Path to gitconfig if not ~/.gitconfig :returns: Configuration instance """ dirname = os.getcwd() if package_dir is not None: dirname = package_dir config_path = os.path.join(dirname, 'cirrus.conf') if not os.path.exists(config_path): repo_dir = repo_directory() if repo_dir is not None: config_path = os.path.join(repo_dir, 'cirrus.conf') if not os.path.exists(config_path): msg = "Couldnt find ./cirrus.conf, are you in a package directory?" raise RuntimeError(msg) config_instance = Configuration(config_path, gitconfig_file=gitconfig_file) config_instance.load() return config_instance
def load_configuration(package_dir=None, gitconfig_file=None): """ _load_configuration_ Load the cirrus.conf file and parse it into a nested dictionary like Configuration instance. :param package_dir: Location of cirrus managed package if not pwd :param gitconfig_file: Path to gitconfig if not ~/.gitconfig :returns: Configuration instance """ dirname = os.getcwd() if package_dir is not None: dirname = package_dir config_path = os.path.join(dirname, 'cirrus.conf') if not os.path.exists(config_path): repo_dir = repo_directory() if repo_dir is not None: config_path = os.path.join(repo_dir, 'cirrus.conf') if not os.path.exists(config_path): msg = "Couldnt find ./cirrus.conf, are you in a package directory?" raise RuntimeError(msg) config_instance = Configuration(config_path, gitconfig_file=gitconfig_file) config_instance.load() return config_instance
def list_feature_branches(opts): """ list unmerged feature branches """ repo_dir = repo_directory() print("unmerged feature branches:") with GitHubContext(repo_dir) as ghc: for x in ghc.iter_git_feature_branches(merged=False): print(x)
def show_release_status(opts): """check release status""" release = opts.release if release is None: release = current_branch(repo_directory()) result = release_status(release) if not result: # unmerged/tagged release => exit as error status sys.exit(1)
def __init__(self): super(Linter, self).__init__() self.config = load_configuration() self.linter_config = self.config.get( 'qc/{}'.format(type(self).__name__, {}), {}) self.working_dir = repo_directory() self.pass_threshold = 0 self.test_mode = False self.errors = {}
def list_feature_branches(opts): """ list unmerged feature branches """ repo_dir = repo_directory() print("unmerged feature branches:") with GitHubContext(repo_dir) as ghc: for x in ghc.iter_git_feature_branches(merged=False): print(x)
def make_new_version(opts): LOGGER.info("Updating package version...") if not highlander([opts.major, opts.minor, opts.micro]): msg = "Can only specify one of --major, --minor or --micro" LOGGER.error(msg) raise RuntimeError(msg) fields = ['major', 'minor', 'micro'] mask = [opts.major, opts.minor, opts.micro] field = [x for x in itertools.compress(fields, mask)][0] config = load_configuration() current_version = config.package_version() # need to be on the latest develop repo_dir = repo_directory() curr_branch = current_branch(repo_dir) # make sure repo is clean if has_unstaged_changes(repo_dir): msg = ("Error: Unstaged changes are present on the branch {}" "Please commit them or clean up before proceeding" ).format(curr_branch) LOGGER.error(msg) raise RuntimeError(msg) # update cirrus conf new_version = bump_version_field(current_version, field) msg = "Bumping version from {prev} to {new} on branch {branch}".format( prev=current_version, new=new_version, branch=curr_branch) LOGGER.info(msg) config.update_package_version(new_version) changes = ['cirrus.conf'] if opts.bump: reqs_file = os.path.join(repo_dir, 'requirements.txt') for pkg, version in opts.bump: LOGGER.info("Bumping dependency {} to {}".format(pkg, version)) bump_package(reqs_file, pkg, version) changes.append(reqs_file) # update __version__ or equivalent version_file, version_attr = config.version_file() if version_file is not None: LOGGER.info('Updating {0} attribute in {1}'.format( version_file, version_attr)) update_version(version_file, new_version, version_attr) changes.append(version_file) # update files changed msg = "cirrus release: version bumped for {0}".format(curr_branch) LOGGER.info('Committing files: {0}'.format(','.join(changes))) LOGGER.info(msg) commit_files_optional_push(repo_dir, msg, not opts.no_remote, *changes)
def __init__(self): super(Builder, self).__init__() self.plugin_parser = argparse.ArgumentParser() self.config = load_configuration() self.build_config = self.config.get('build', {}) self.working_dir = repo_directory() self.venv_name = self.build_config.get('virtualenv_name', 'venv') self.reqs_name = self.build_config.get('requirements_file', 'requirements.txt') self.extra_reqs = self.build_config.get('extra_requirements', []) self.python_bin = self.build_config.get('python', None) self.extra_reqs = self.str_to_list(self.extra_reqs) self.venv_path = os.path.join(self.working_dir, self.venv_name)
def __init__(self): super(Builder, self).__init__() self.plugin_parser = argparse.ArgumentParser() self.config = load_configuration() self.build_config = self.config.get('build', {}) self.working_dir = repo_directory() self.venv_name = self.build_config.get('virtualenv_name', 'venv') self.reqs_name = self.build_config.get('requirements_file', 'requirements.txt') self.extra_reqs = self.build_config.get('extra_requirements', []) self.python_bin = self.build_config.get('python', None) self.extra_reqs = self.str_to_list(self.extra_reqs) self.venv_path = os.path.join(self.working_dir, self.venv_name)
def nose_run(config, opts): """ _nose_test_ Locally activate vitrualenv and run tests via nose """ where = config.test_where(opts.suite) suite_conf = config.test_suite(opts.suite) test_opts = suite_conf.get('test_options') venv_path = os.path.join(repo_directory(), config.venv_name()) if opts.options: # command line overrides test_opts = opts.options activate = activate_command(venv_path) local('{0} && nosetests -w {1} {2}'.format(activate, where, test_opts if test_opts else ""))
def new_pr(opts): """ _new_pr_ Creates a pull request """ repo_dir = repo_directory() #parse notify adding '@' if necessary notifiees = [] if opts.notify is not None: notify = opts.notify.split(',') for user in notify: if not user.startswith('@'): tmp = '@{0}'.format(user) notifiees.append(tmp) pr_body = '{0} \n{1}'.format(' '.join(notifiees), opts.body) pr_info = {'title': opts.title, 'body': pr_body} pr_url = create_pull_request(repo_dir, pr_info) LOGGER.info("Created PR {0}".format(pr_url))
def new_feature_branch(opts): """ _new_feature_branch_ Checks out branch, creates new branch 'name', optionally pushes new branch to remote """ config = load_configuration() repo_dir = repo_directory() checkout_and_pull(repo_dir, config.gitflow_branch_name(), pull=not opts.no_remote) LOGGER.info("Checked out and pulled {0}".format( config.gitflow_branch_name())) branch_name = ''.join((config.gitflow_feature_prefix(), opts.name[0])) branch(repo_dir, branch_name, config.gitflow_branch_name()) LOGGER.info("Created branch {0}".format(branch_name)) if not opts.no_remote: push(repo_dir) LOGGER.info("Branch {0} pushed to remote".format(branch_name))
def tox_run(config, opts): """ tox test activate venv and run tox test suite """ suite_conf = config.test_suite(opts.suite) tox_ini = suite_conf.get('tox_ini') test_opts = suite_conf.get('test_options') venv_path = os.path.join(repo_directory(), config.venv_name()) if opts.options: # command line overrides test_opts = opts.options tox_command = "tox" if tox_ini: tox_command += " -c {}".format(tox_ini) if test_opts: tox_command += " {}".format(test_opts) activate = activate_command(venv_path) local('{0} && {1}'.format(activate, tox_command))
def new_pr(opts): """ _new_pr_ Creates a pull request """ repo_dir = repo_directory() #parse notify adding '@' if necessary notifiees = [] if opts.notify is not None: notify = opts.notify.split(',') for user in notify: if not user.startswith('@'): tmp = '@{0}'.format(user) notifiees.append(tmp) pr_body = '{0} \n{1}'.format(' '.join(notifiees), opts.body) pr_info = { 'title': opts.title, 'body': pr_body} pr_url = create_pull_request(repo_dir, pr_info) LOGGER.info("Created PR {0}".format(pr_url))
def merge_feature_branch(opts): """ merge current feature branch into develop """ config = load_configuration() main_branch = config.gitflow_branch_name() repo_dir = repo_directory() curr_branch = current_branch(repo_dir) LOGGER.info("Merging {} into {}".format(curr_branch, main_branch)) # make sure repo is clean if has_unstaged_changes(repo_dir): msg = ("Error: Unstaged changes are present on the feature branch {}" "Please commit them or clean up before proceeding" ).format(curr_branch) LOGGER.error(msg) raise RuntimeError(msg) checkout_and_pull(repo_dir, main_branch, pull=not opts.no_remote) with GitHubContext(repo_dir) as ghc: ghc.merge_branch(curr_branch) if not opts.no_remote: ghc.push_branch(main_branch) LOGGER.info("Branch {0} pushed to remote".format(main_branch))
def merge_feature_branch(opts): """ merge current feature branch into develop """ config = load_configuration() main_branch = config.gitflow_branch_name() repo_dir = repo_directory() curr_branch = current_branch(repo_dir) LOGGER.info("Merging {} into {}".format(curr_branch, main_branch)) # make sure repo is clean if has_unstaged_changes(repo_dir): msg = ( "Error: Unstaged changes are present on the feature branch {}" "Please commit them or clean up before proceeding" ).format(curr_branch) LOGGER.error(msg) raise RuntimeError(msg) checkout_and_pull(repo_dir, main_branch, pull=not opts.no_remote) with GitHubContext(repo_dir) as ghc: ghc.merge_branch(curr_branch) if not opts.no_remote: ghc.push_branch(main_branch) LOGGER.info("Branch {0} pushed to remote".format(main_branch))
def new_feature_branch(opts): """ _new_feature_branch_ Checks out branch, creates new branch 'name', optionally pushes new branch to remote """ config = load_configuration() repo_dir = repo_directory() checkout_and_pull( repo_dir, config.gitflow_branch_name(), pull=not opts.no_remote ) LOGGER.info("Checked out and pulled {0}".format( config.gitflow_branch_name())) branch_name = ''.join((config.gitflow_feature_prefix(), opts.name[0])) branch(repo_dir, branch_name, config.gitflow_branch_name()) LOGGER.info("Created branch {0}".format(branch_name)) if not opts.no_remote: push(repo_dir) LOGGER.info("Branch {0} pushed to remote".format(branch_name))
def execute_build(opts): """ _execute_build_ Execute the build in the current package context. - reads the config to check for custom build parameters - defaults to ./venv for virtualenv - defaults to ./requirements.txt for reqs - removes existing virtualenv if clean flag is set - builds the virtualenv - pip installs the requirements into it : param argparse.Namspace opts: A Namespace of build options """ working_dir = repo_directory() config = load_configuration() build_params = config.get('build', {}) # we have custom build controls in the cirrus.conf venv_name = build_params.get('virtualenv_name', 'venv') reqs_name = build_params.get('requirements_file', 'requirements.txt') extra_reqs = build_params.get('extra_requirements', '') python_bin = build_params.get('python', None) if opts.python: python_bin = opts.python extra_reqs = [x.strip() for x in extra_reqs.split(',') if x.strip()] if opts.extras: extra_reqs.extend(opts.extras) extra_reqs = set(extra_reqs) # dedupe venv_path = os.path.join(working_dir, venv_name) if is_anaconda(): conda_venv(venv_path, opts, python_bin) else: virtualenv_venv(venv_path, opts, python_bin) # custom pypi server pypi_server = config.pypi_url() pip_options = config.pip_options() pip_command_base = None if pypi_server is not None: pypirc = PypircFile() if pypi_server in pypirc.index_servers: pypi_url = pypirc.get_pypi_url(pypi_server) else: pypi_conf = get_pypi_auth() pypi_url = ( "https://{pypi_username}:{pypi_token}@{pypi_server}/simple" ).format(pypi_token=pypi_conf['token'], pypi_username=pypi_conf['username'], pypi_server=pypi_server) pip_command_base = ('{0}/bin/pip install -i {1}').format( venv_path, pypi_url) if opts.upgrade: cmd = ('{0} --upgrade ' '-r {1}').format(pip_command_base, reqs_name) else: cmd = ('{0} ' '-r {1}').format(pip_command_base, reqs_name) else: pip_command_base = '{0}/bin/pip install'.format(venv_path) # no pypi server if opts.upgrade: cmd = '{0} --upgrade -r {1}'.format(pip_command_base, reqs_name) else: cmd = '{0} -r {1}'.format(pip_command_base, reqs_name) if pip_options: cmd += " {} ".format(pip_options) try: local(cmd) except OSError as ex: msg = ("Error running pip install command during build\n" "Error was {0}\n" "Running command: {1}\n" "Working Dir: {2}\n" "Virtualenv: {3}\n" "Requirements: {4}\n").format(ex, cmd, working_dir, venv_path, reqs_name) LOGGER.error(msg) sys.exit(1) if extra_reqs: if opts.upgrade: commands = [ "{0} --upgrade -r {1}".format(pip_command_base, reqfile) for reqfile in extra_reqs ] else: commands = [ "{0} -r {1}".format(pip_command_base, reqfile) for reqfile in extra_reqs ] for cmd in commands: LOGGER.info("Installing extra requirements... {}".format(cmd)) try: local(cmd) except OSError as ex: msg = ("Error running pip install command extra " "requirements install: {}\n{}").format(reqfile, ex) LOGGER.error(msg) sys.exit(1) # setup for development if opts.nosetupdevelop: msg = "skipping python setup.py develop..." LOGGER.info(msg) else: LOGGER.info('running python setup.py develop...') activate = activate_command(venv_path) local('{} && python setup.py develop'.format(activate))
def new_release(opts): """ _new_release_ - Create a new release branch in the local repo - Edit the conf to bump the version - Edit the history file with release notes """ LOGGER.info("Creating new release...") if not highlander([opts.major, opts.minor, opts.micro]): msg = "Can only specify one of --major, --minor or --micro" LOGGER.error(msg) raise RuntimeError(msg) fields = ['major', 'minor', 'micro'] mask = [opts.major, opts.minor, opts.micro] field = [x for x in itertools.compress(fields, mask)][0] config = load_configuration() # version bump: current_version = config.package_version() new_version = bump_version_field(current_version, field) # release branch branch_name = "{0}{1}".format(config.gitflow_release_prefix(), new_version) LOGGER.info('release branch is {0}'.format(branch_name)) # need to be on the latest develop repo_dir = repo_directory() # make sure the branch doesnt already exist on remote if remote_branch_exists(repo_dir, branch_name): msg = ("Error: branch {branch_name} already exists on the remote repo " "Please clean up that branch before proceeding\n" "git branch -d {branch_name}\n" "git push origin --delete {branch_name}\n").format( branch_name=branch_name) LOGGER.error(msg) raise RuntimeError(msg) # make sure repo is clean if has_unstaged_changes(repo_dir): msg = ("Error: Unstaged changes are present on the branch " "Please commit them or clean up before proceeding") LOGGER.error(msg) raise RuntimeError(msg) main_branch = config.gitflow_branch_name() checkout_and_pull(repo_dir, main_branch, pull=not opts.no_remote) # create release branch branch(repo_dir, branch_name, main_branch) # update cirrus conf config.update_package_version(new_version) changes = ['cirrus.conf'] if opts.bump: reqs_file = os.path.join(repo_dir, 'requirements.txt') for pkg, version in opts.bump: LOGGER.info("Bumping dependency {} to {}".format(pkg, version)) bump_package(reqs_file, pkg, version) changes.append(reqs_file) # update release notes file relnotes_file, relnotes_sentinel = config.release_notes() if (relnotes_file is not None) and (relnotes_sentinel is not None): LOGGER.info('Updating release notes in {0}'.format(relnotes_file)) relnotes = "Release: {0} Created: {1}\n".format( new_version, datetime.datetime.utcnow().isoformat()) relnotes += build_release_notes(repo_dir, current_version, config.release_notes_format()) update_file(relnotes_file, relnotes_sentinel, relnotes) changes.append(relnotes_file) # update __version__ or equivalent version_file, version_attr = config.version_file() if version_file is not None: LOGGER.info('Updating {0} attribute in {1}'.format( version_file, version_attr)) update_version(version_file, new_version, version_attr) changes.append(version_file) # update files changed msg = "cirrus release: new release created for {0}".format(branch_name) LOGGER.info('Committing files: {0}'.format(','.join(changes))) LOGGER.info(msg) commit_files_optional_push(repo_dir, msg, not opts.no_remote, *changes) return (new_version, field)
def release_status(release): """ given a release branch name or tag, look at the status of that release based on git history and attempt to check wether it has been fully merged or tagged. returns True if the release looks to be successfully merged and tagged """ result = False with GitHubContext(repo_directory()) as ghc: LOGGER.info("Checking release status for {}".format(release)) # work out the branch and tag for the release rel_pfix = ghc.config.gitflow_release_prefix() develop_branch = ghc.config.gitflow_branch_name() master_branch = ghc.config.gitflow_master_name() origin_name = ghc.config.gitflow_origin_name() if release in (develop_branch, master_branch): unmerged = ghc.unmerged_releases() msg = ( "On Develop or Master Branch {}, " "checking for unmerged releases...\n" ).format(release) if unmerged: msg += ( "Found the following unmerged " "releases:\n{}\n" ).format('\n '.join(unmerged)) else: msg += "No unmerged releases found.\n" msg += ( "Please checkout a release branch and re run this command" " for more details about a specific release" ) LOGGER.info(msg) return False if not release.startswith(rel_pfix): msg = ( "Branch {release} doesnt look like a release branch\n" "Please checkout the release branch you want to check status of" ).format(release=release) LOGGER.error(msg) return False if rel_pfix in release: release_tag = release.split(rel_pfix)[1] release_branch = release else: release_branch = "{}{}".format(rel_pfix, release) release_tag = release LOGGER.info("Checking: branch={} tag={}".format(release_branch, release_tag)) branch_commit = ghc.find_release_commit(release_branch) tag_commit = ghc.find_release_commit(release_tag) LOGGER.info("Resolved Commits: branch={} tag={}".format(branch_commit, tag_commit)) # handles caser where tag or release is not present, eg typo if (not tag_commit) and (not branch_commit): msg = ( "Unable to find any branch or tag commits " "for release \'{}\'\n" "Are you sure this is a valid release?" ).format(release) LOGGER.error(msg) return False # check the tag commit is present on master and remote master tag_present = False if tag_commit: branches = ghc.commit_on_branches(tag_commit) remote_master = "remotes/{}/{}".format(origin_name, master_branch) on_master = master_branch in branches on_origin = remote_master in branches if on_master: LOGGER.info("Tag is present on local master {}...".format(master_branch)) tag_present = True if on_origin: LOGGER.info("Tag is present on remote master {}...".format(remote_master)) tag_present = True # look for common merge base containing the release name for master # and develop develop_merge = ghc.merge_base(release_tag, develop_branch) master_merge = ghc.merge_base(release_tag, master_branch) merge_on_develop = False merge_on_master = False if develop_merge: merge_on_develop = release_branch in ghc.git_show_commit(develop_merge) if master_merge: merge_on_master = release_branch in ghc.git_show_commit(master_merge) if merge_on_develop: LOGGER.info("Merge of {} is on {}".format(release_branch, develop_branch)) else: LOGGER.info("Merge of {} not found on {}".format(release_branch, develop_branch)) if merge_on_master: LOGGER.info("Merge of {} is on {}".format(release_branch, master_branch)) else: LOGGER.info("Merge of {} not found on {}".format(release_branch, master_branch)) if not all([tag_present, merge_on_develop, merge_on_master]): msg = ( "\nRelease branch {} was not found either as a tag or merge " "commit on develop branch: {} or master branch: {} branches\n" "This may mean that the release is still running on a CI platform or " "has errored out. \n" " => Tag Present: {}\n" " => Merged to develop: {}\n" " => Merged to master: {}\n" "\nFor troubleshooting please see: " "https://github.com/evansde77/cirrus/wiki/Troubleshooting#release\n" ).format( release_branch, develop_branch, master_branch, tag_present, merge_on_develop, merge_on_master ) LOGGER.error(msg) result = False else: msg = "Release {} looks to be successfully merged and tagged\n".format(release_branch) LOGGER.info(msg) result = True return result
def release_status(release): """ given a release branch name or tag, look at the status of that release based on git history and attempt to check wether it has been fully merged or tagged. returns True if the release looks to be successfully merged and tagged """ result = False with GitHubContext(repo_directory()) as ghc: LOGGER.info("Checking release status for {}".format(release)) # work out the branch and tag for the release rel_pfix = ghc.config.gitflow_release_prefix() develop_branch = ghc.config.gitflow_branch_name() master_branch = ghc.config.gitflow_master_name() origin_name = ghc.config.gitflow_origin_name() if rel_pfix in release: release_tag = release.split(rel_pfix)[1] release_branch = release else: release_branch = "{}{}".format(rel_pfix, release) release_tag = release LOGGER.info("Checking: branch={} tag={}".format( release_branch, release_tag)) branch_commit = ghc.find_release_commit(release_branch) tag_commit = ghc.find_release_commit(release_tag) LOGGER.info("Resolved Commits: branch={} tag={}".format( branch_commit, tag_commit)) # handles caser where tag or release is not present, eg typo if (not tag_commit) and (not branch_commit): msg = ("Unable to find any branch or tag commits " "for release \'{}\'\n" "Are you sure this is a valid release?").format(release) LOGGER.error(msg) return False # check the tag commit is present on master and remote master tag_present = False if tag_commit: branches = ghc.commit_on_branches(tag_commit) remote_master = "remotes/{}/{}".format(origin_name, master_branch) on_master = master_branch in branches on_origin = remote_master in branches if on_master: LOGGER.info("Tag is present on local master {}...".format( master_branch)) tag_present = True if on_origin: LOGGER.info("Tag is present on remote master {}...".format( remote_master)) tag_present = True # look for common merge base containing the release name for master # and develop develop_merge = ghc.merge_base(release_tag, develop_branch) master_merge = ghc.merge_base(release_tag, master_branch) merge_on_develop = False merge_on_master = False if develop_merge: merge_on_develop = release_branch in ghc.git_show_commit( develop_merge) if master_merge: merge_on_master = release_branch in ghc.git_show_commit( master_merge) if merge_on_develop: LOGGER.info("Merge of {} is on {}".format(release_branch, develop_branch)) else: LOGGER.info("Merge of {} not found on {}".format( release_branch, develop_branch)) if merge_on_master: LOGGER.info("Merge of {} is on {}".format(release_branch, master_branch)) else: LOGGER.info("Merge of {} not found on {}".format( release_branch, master_branch)) if not all([tag_present, merge_on_develop, merge_on_master]): msg = ( "\nRelease branch {} was not found either as a tag or merge " "commit on develop branch: {} or master branch: {} branches\n" "This may mean that the release is still running on a CI platform or " "has errored out. \n" " => Tag Present: {}\n" " => Merged to develop: {}\n" " => Merged to master: {}\n" "\nFor troubleshooting please see: " "https://github.com/evansde77/cirrus/wiki/Troubleshooting#release\n" ).format(release_branch, develop_branch, master_branch, tag_present, merge_on_develop, merge_on_master) LOGGER.error(msg) result = False else: msg = "Release {} looks to be successfully merged and tagged\n".format( release_branch) LOGGER.info(msg) result = True return result