def update_core(data, branch=None): msg = "Ok to update coredev versions.cfg/checkouts.cfg?" if branch: msg = "Ok to update coredev {0} versions.cfg/checkouts.cfg?".format(branch) if ask(msg, default=True): root_path = os.path.join(os.getcwd(), '../../') g = git.Git(root_path) g.pull() # make sure buildout.coredev is up-to-date package_name = data['name'] new_version = data['version'] update_versions(package_name, new_version) if package_name not in ALWAYS_CHECKED_OUT_PACKAGES: remove_from_checkouts(package_name) # git commit message = "{0} {1}".format(package_name, new_version) g.add('versions.cfg') g.add('checkouts.cfg') print("Committing changes.") g.commit(message=message) msg = "Ok to push coredev?" if branch: msg = "Ok to push coredev {0}?".format(branch) if ask(msg, default=True): print("Pushing changes to server.") g.push()
def zest_releaser_check(data): """Check the completeness of MANIFEST.in before the release. This is an entry point for zest.releaser. See the documentation at http://zestreleaser.readthedocs.org/en/latest/entrypoints.html """ from zest.releaser.utils import ask source_tree = data['workingdir'] if not is_package(source_tree): # You can use zest.releaser on things that are not Python packages. # It's pointless to run check-manifest in those circumstances. # See https://github.com/mgedmin/check-manifest/issues/9 for details. return if not ask("Do you want to run check-manifest?"): return try: if not check_manifest(source_tree): if not ask("MANIFEST.in is not in order. " " Do you want to continue despite that?", default=False): sys.exit(1) except Failure as e: error(str(e)) if not ask("Something bad happened. " " Do you want to continue despite that?", default=False): sys.exit(2)
def _upload_distributions(self, package): # See if creating an sdist (and maybe a wheel) actually works. # Also, this makes the sdist (and wheel) available for plugins. # And for twine, who will just upload the created files. logger.info( "Making a source distribution of a fresh tag checkout (in %s).", self.data['tagworkingdir']) result = utils.execute_command(utils.setup_py('sdist')) utils.show_interesting_lines(result) if self.pypiconfig.create_wheel(): logger.info("Making a wheel of a fresh tag checkout (in %s).", self.data['tagworkingdir']) result = utils.execute_command(utils.setup_py('bdist_wheel')) utils.show_interesting_lines(result) if not self.pypiconfig.is_pypi_configured(): logger.error( "You must have a properly configured %s file in " "your home dir to upload to a Python package index.", pypi.DIST_CONFIG_FILE) if utils.ask("Do you want to continue without uploading?", default=False): return sys.exit(1) # Run extra entry point self._run_hooks('before_upload') # Get list of all files to upload. files_in_dist = [ os.path.join('dist', filename) for filename in os.listdir('dist') ] # Get servers/repositories. servers = self.pypiconfig.distutils_servers() register = self.pypiconfig.register_package() for server in servers: if register: question = "Register and upload" else: question = "Upload" default = True exact = False if server == 'pypi' and not package_in_pypi(package): logger.info("This package is NOT registered on PyPI.") # We are not yet on pypi. To avoid an 'Oops..., # sorry!' when registering and uploading an internal # package we default to False here. default = False exact = True if utils.ask("%s to %s" % (question, server), default=default, exact=exact): if register: logger.info("Registering...") # We only need the first file, it has all the needed info self._retry_twine('register', server, files_in_dist[0]) for filename in files_in_dist: self._retry_twine('upload', server, filename) self._close_all_repositories()
def ask_what_to_release(self): """Show changes both in CHANGES.rst and on git history For that checkout the repository, show both changes to see if everything worth writing in CHANGES.rst from git history is already there. """ logger.info('') msg = 'What to release' logger.info(msg) logger.info('-' * len(msg)) to_release = [] for distribution_path in self.distributions: dist_name = distribution_path.split('/')[-1] repo = Repo(distribution_path) git_changes = get_compact_git_history( repo, self.last_tags[dist_name], self.branch, ) cleaned_git_changes = filter_git_history(git_changes) # a git history without any meaningful commit should not be # released if cleaned_git_changes == '': continue logger.info(DISTRIBUTION.format(distribution_path)) change_log_path = '{0}/CHANGES.rst'.format(repo.working_tree_dir) try: changes = self._grab_changelog(change_log_path) except IOError: logger.debug('Changelog not found, skipping.') continue self.changelogs[dist_name] = changes[2:] # nice to have: show them side-by-side logger.info('') logger.info(cleaned_git_changes) logger.info('') logger.info('') logger.info(''.join(changes)) msg = '{0}: write the above git history on CHANGES.rst?' if self.test and ask(msg.format(dist_name)): changelog = UpdateDistChangelog( distribution_path, branch=self.branch, ) changelog.write_changes(history=cleaned_git_changes) elif not self.test and \ ask('Is the change log ready for release?'): to_release.append(distribution_path) if not self.test: self.distributions = to_release logger.debug('Distributions: ') logger.debug('\n'.join(self.distributions))
def zest_releaser_check(data): """Check the completeness of MANIFEST.in before the release. This is an entry point for zest.releaser. See the documentation at https://zestreleaser.readthedocs.io/en/latest/entrypoints.html """ from zest.releaser.utils import ask source_tree = data['workingdir'] if not is_package(source_tree): # You can use zest.releaser on things that are not Python packages. # It's pointless to run check-manifest in those circumstances. # See https://github.com/mgedmin/check-manifest/issues/9 for details. return if not ask("Do you want to run check-manifest?"): return ui = UI() try: if not check_manifest(source_tree, ui=ui): if not ask( "MANIFEST.in has problems." " Do you want to continue despite that?", default=False): sys.exit(1) except Failure as e: ui.error(str(e)) if not ask( "Something bad happened." " Do you want to continue despite that?", default=False): sys.exit(2)
def _upload_distributions(self, package, sdist_options, pypiconfig): # See if creating an egg actually works. logger.info("Making an egg of a fresh tag checkout.") print system(utils.setup_py('sdist ' + sdist_options)) # First ask if we want to upload to pypi, which should always # work, also without collective.dist. use_pypi = package_in_pypi(package) if use_pypi: logger.info("This package is registered on PyPI.") else: logger.warn("This package is NOT registered on PyPI.") if pypiconfig.is_old_pypi_config(): pypi_command = 'register sdist %s upload' % sdist_options shell_command = utils.setup_py(pypi_command) if use_pypi: default = True exact = False else: # We are not yet on pypi. To avoid an 'Oops..., # sorry!' when registering and uploading an internal # package we default to False here. default = False exact = True if utils.ask("Register and upload to PyPI", default=default, exact=exact): logger.info("Running: %s", shell_command) result = system(shell_command) utils.show_first_and_last_lines(result) # If collective.dist is installed (or we are using # python2.6 or higher), the user may have defined # other servers to upload to. for server in pypiconfig.distutils_servers(): if pypi.new_distutils_available(): commands = ('register', '-r', server, 'sdist', sdist_options, 'upload', '-r', server) else: ## This would be logical, given the lines above: #commands = ('mregister', '-r', server, 'sdist', # sdist_options, 'mupload', '-r', server) ## But according to the collective.dist documentation ## it should be this (with just one '-r'): commands = ('mregister', 'sdist', sdist_options, 'mupload', '-r', server) shell_command = utils.setup_py(' '.join(commands)) default = True exact = False if server == 'pypi' and not use_pypi: # We are not yet on pypi. To avoid an 'Oops..., # sorry!' when registering and uploading an internal # package we default to False here. default = False exact = True if utils.ask("Register and upload to %s" % server, default=default, exact=exact): logger.info("Running: %s", shell_command) result = system(shell_command) utils.show_first_and_last_lines(result)
def _upload_distributions(self, package): # See if creating an sdist (and maybe a wheel) actually works. # Also, this makes the sdist (and wheel) available for plugins. # And for twine, who will just upload the created files. logger.info( "Making a source distribution of a fresh tag checkout (in %s).", self.data['tagworkingdir']) result = utils.execute_command(utils.setup_py('sdist')) utils.show_interesting_lines(result) if self.pypiconfig.create_wheel(): logger.info("Making a wheel of a fresh tag checkout (in %s).", self.data['tagworkingdir']) result = utils.execute_command(utils.setup_py('bdist_wheel')) utils.show_interesting_lines(result) if not self.pypiconfig.is_pypi_configured(): logger.error( "You must have a properly configured %s file in " "your home dir to upload to a Python package index.", pypi.DIST_CONFIG_FILE) if utils.ask("Do you want to continue without uploading?", default=False): return sys.exit(1) # Run extra entry point self._run_hooks('before_upload') # Get list of all files to upload. files_in_dist = sorted([ os.path.join('dist', filename) for filename in os.listdir('dist')] ) # Get servers/repositories. servers = self.pypiconfig.distutils_servers() register = self.pypiconfig.register_package() for server in servers: default = True exact = False if server == 'pypi' and not package_in_pypi(package): logger.info("This package does NOT exist yet on PyPI.") # We are not yet on pypi. To avoid an 'Oops..., # sorry!' when registering and uploading an internal # package we default to False here. default = False exact = True question = "Upload" if register: question = "Register and upload" if utils.ask("%s to %s" % (question, server), default=default, exact=exact): if register: logger.info("Registering...") # We only need the first file, it has all the needed info self._retry_twine('register', server, files_in_dist[:1]) self._retry_twine('upload', server, files_in_dist)
def zester(data): main_files = os.listdir(data['workingdir']) if 'setup.py' not in main_files and 'setup.cfg' not in main_files: return from zest.releaser.utils import ask if ask("Run pyroma on the package before tagging?"): rating = run('directory', os.path.abspath(data['workingdir'])) if rating < 8: if not ask("Continue?"): sys.exit(1)
def run_pyroma(data): import sys from zest.releaser.utils import ask if not ask("Run pyroma on the package before uploading?"): return try: from pyroma import run result = run(data['tagdir']) if result != 10: if not ask("Continue?"): sys.exit(1) except ImportError: if not ask("pyroma not available. Continue?"): sys.exit(1)
def zester(data): main_files = set(os.listdir(data["workingdir"])) config_files = {"setup.py", "setup.cfg", "pyproject.toml"} # If there are no standard Python config files in the main files # it's likely not a Python project, so just return. if not config_files & main_files: return from zest.releaser.utils import ask if ask("Run pyroma on the package before tagging?"): rating = run("directory", os.path.abspath(data["workingdir"])) if rating < 8: if not ask("Continue?"): sys.exit(1)
def available_tags(self): base = self._base_from_svn() tags_name = self._tags_name if tags_name is None: # Suggest to create a tags dir with the default plural /tags name. print("tags dir does not exist at %s" % base + 'tags') if utils.ask("Shall I create it"): cmd = 'svn mkdir %stags -m "Creating tags directory."' % (base) logger.info("Running %r", cmd) print(execute_command(cmd)) tags_name = self._tags_name assert tags_name == 'tags' else: sys.exit(1) tag_info = execute_command( 'svn list --non-interactive %s%s' % (base, tags_name)) network_errors = [ 'Could not resolve hostname', 'E670008', 'Repository moved', 'Unable to connect', ] found_errors = [1 for network_error in network_errors if network_error in tag_info] if found_errors: logger.error('Network problem: %s', tag_info) sys.exit(1) tags = [line.replace('/', '').strip() for line in tag_info.split('\n')] tags = [tag for tag in tags if tag] # filter empty ones logger.debug("Available tags: %r", tags) return tags
def _diff_and_commit(self, commit_msg=''): """Show diff and offer commit. commit_msg is optional. If it is not there, we get the commit_msg from self.data. That is the usual mode and is at least used in prerelease and postrelease. If it is not there either, we ask. """ if not commit_msg: if 'commit_msg' not in self.data: # Ask until we get a non-empty commit message. while not commit_msg: commit_msg = utils.get_input( "What is the commit message? ") else: commit_msg = self.data['commit_msg'] diff_cmd = self.vcs.cmd_diff() diff = execute_command(diff_cmd) if sys.version.startswith('2.6.2'): # python2.6.2 bug... http://bugs.python.org/issue5170 This is the # spot it can surface as we show a part of the changelog which can # contain every kind of character. The rest is mostly ascii. print("Diff results:") print(diff) else: # Common case logger.info("The '%s':\n\n%s\n", diff_cmd, diff) if utils.ask("OK to commit this"): msg = commit_msg % self.data msg = self.update_commit_message(msg) commit_cmd = self.vcs.cmd_commit(msg) commit = execute_command(commit_cmd) logger.info(commit)
def compile_in_tag(data): """Compile all po files in the tag. We expect to get a dictionary from zest.releaser, with a tagdir. When an exception occurs during finding/compiling, and we were indeed called as an entry point of zest.releaser, we ask the user what to do: continue with the release or not. """ tagdir = data.get('tagdir') if not tagdir: logger.warn("Aborted compiling of po files: no tagdir found in data.") return logger.info('Finding and compiling po files in %s', tagdir) try: find_lc_messages(tagdir) except Exception: logger.warn('Finding and compiling aborted after exception.', exc_info=True) if data and ask: # We were called as an entry point of zest.releaser. if not ask("Error compiling po file. This could mean some " "languages have no working translations. Do you want " "to continue with the release?"): sys.exit(1)
def _run_pyroma(data): # pragma: no cover """Run pyroma (used to perform checks before releasing a new version). """ import sys from zest.releaser.utils import ask if not ask("Run pyroma on the package before uploading?"): return try: from pyroma import run result = run(data['tagdir']) if result != 10: if not ask("Continue?"): sys.exit(1) except ImportError: if not ask("pyroma not available. Continue?"): sys.exit(1)
def available_tags(self): base = self._base_from_svn() tags_name = self._tags_name if tags_name is None: # Suggest to create a tags dir with the default plural /tags name. print "tags dir does not exist at %s" % base + 'tags' if utils.ask("Shall I create it"): cmd = 'svn mkdir %stags -m "Creating tags directory."' % (base) logger.info("Running %r", cmd) print system(cmd) tags_name = self._tags_name assert tags_name == 'tags' else: sys.exit(0) tag_info = system('svn list %s%s' % (base, tags_name)) if 'Could not resolve hostname' in tag_info or \ 'Repository moved' in tag_info or 'E670008' in tag_info: logger.error('Network problem: %s', tag_info) sys.exit() tags = [line.replace('/', '').strip() for line in tag_info.split('\n')] tags = [tag for tag in tags if tag] # filter empty ones logger.debug("Available tags: %r", tags) return tags
def main(): logging.basicConfig(level=utils.loglevel(), format="%(levelname)s: %(message)s") check_for_files() directories = development_eggs() if not directories: sys.exit() old_situation = url_list() tags = determine_tags(directories) remove_old_part() add_new_part(tags) new_situation = url_list() diff = list(difflib.ndiff(old_situation, new_situation)) logger.debug("Diff: %s", diff) check_stable() # XXX The diff is too ugly to put in the history file or the # commit message. msg = ["Stabilized buildout to most recent svn tags of our packages:"] msg += diff insert_msg_into_history(msg) msg = '\n'.join(msg) # show diff, offer commit vcs = zest.releaser.choose.version_control() diff_cmd = vcs.cmd_diff() diff = getoutput(diff_cmd) logger.info("The '%s':\n\n%s\n" % (diff_cmd, diff)) if utils.ask("OK to commit this"): commit_cmd = vcs.cmd_commit(msg) commit = getoutput(commit_cmd) logger.info(commit)
def _grab_history(self): """Calculate the needed history/changelog changes Every history heading looks like '1.0 b4 (1972-12-25)'. Extract them, check if the first one matches the version and whether it has a the current date. """ default_location = None config = self.setup_cfg.config if config and config.has_option('zest.releaser', 'history_file'): default_location = config.get('zest.releaser', 'history_file') history_file = self.vcs.history_file(location=default_location) if not history_file: logger.warn("No history file found") self.data['history_lines'] = None self.data['history_file'] = None self.data['history_encoding'] = None return logger.debug("Checking %s", history_file) history_lines, history_encoding = read_text_file(history_file) history_lines = history_lines.split('\n') headings = utils.extract_headings_from_history(history_lines) if not len(headings): logger.error("No detectable version heading in the history " "file %s", history_file) sys.exit(1) good_heading = self.data['history_header'] % self.data # ^^^ history_header is a string with %(abc)s replacements. line = headings[0]['line'] previous = history_lines[line] history_lines[line] = good_heading logger.debug("Set heading from %r to %r.", previous, good_heading) history_lines[line + 1] = utils.fix_rst_heading( heading=good_heading, below=history_lines[line + 1]) logger.debug("Set line below heading to %r", history_lines[line + 1]) self.data['history_lines'] = history_lines self.data['history_file'] = history_file self.data['history_encoding'] = history_encoding # TODO: add line number where an extra changelog entry can be # inserted. # Look for 'Nothing changed yet' under the latest header. Not # nice if this text ends up in the changelog. Did nothing happen? start = headings[0]['line'] if len(headings) > 1: end = headings[1]['line'] else: end = -1 for line in history_lines[start:end]: if self.data['nothing_changed_yet'] in line: if not utils.ask( "WARNING: Changelog contains %r. Are you sure you " "want to release?" % self.data['nothing_changed_yet'], default=False): logger.info("You can use the 'lasttaglog' command to " "see the commits since the last tag.") sys.exit(0) break
def _check_required(self): """Look for required text under the latest header. This can be a list, in which case only one item needs to be there. """ if self.data['history_file'] is None: return required = self.data.get('required_changelog_text') if not required: return if isinstance(required, six.string_types): required = [required] history_last_release = self.data['history_last_release'] for text in required: if text in history_last_release: # Found it, all is fine. return pretty_required = '"{}"'.format('", "'.join(required)) if not utils.ask( "WARNING: Changelog should contain at least one of " "these required strings: {}. Are you sure you " "want to release?".format(pretty_required), default=False): sys.exit(1)
def available_tags(self): base = self._base_from_svn() tags_name = self._tags_name if tags_name is None: # Suggest to create a tags dir with the default plural /tags name. print("tags dir does not exist at %s" % base + 'tags') if utils.ask("Shall I create it"): cmd = 'svn mkdir %stags -m "Creating tags directory."' % (base) logger.info("Running %r", cmd) print(execute_command(cmd)) tags_name = self._tags_name assert tags_name == 'tags' else: sys.exit(0) tag_info = execute_command('svn list %s%s' % (base, tags_name)) network_errors = [ 'Could not resolve hostname', 'E670008', 'Repository moved', 'Unable to connect', ] found_errors = [1 for network_error in network_errors if network_error in tag_info] if found_errors: logger.error('Network problem: %s', tag_info) sys.exit(1) tags = [line.replace('/', '').strip() for line in tag_info.split('\n')] tags = [tag for tag in tags if tag] # filter empty ones logger.debug("Available tags: %r", tags) return tags
def change_log(): utils.parse_options() utils.configure_logging() logger.info('Starting changelogrelease.') if not utils.ask("OK to update ChangeLog"): return if not BUILDOUT: logger.warn("No BUILDOUT environment variable") return vcs = baserelease.Basereleaser().vcs package = vcs.name buildoutpackage = os.path.split(os.path.abspath(BUILDOUT))[1] if package == buildoutpackage: logger.warn("It's the buildout") return # Current buildout history_lines, history_encoding = getHistoryLines(vcs) headings = extractHeadings(history_lines) if not headings: return changelogs, version = getCurrentChangeLogs(history_lines, headings) # Master Buildout history_lines, history_encoding = getBuildoutHistoryLines() headings = extractHeadings(history_lines) if not headings: return updateBuildoutChangeLogs(history_lines, history_encoding, headings, changelogs, package, version) upgradeBuildoutVersion(package, version)
def available_tags(self): base = self._base_from_svn() tags_name = self._tags_name if tags_name is None: # Suggest to create a tags dir with the default plural /tags name. print "tags dir does not exist at %s" % base + 'tags' if utils.ask("Shall I create it"): cmd = 'svn mkdir %stags -m "Creating tags directory."' % (base) logger.info("Running %r", cmd) print system(cmd) tags_name = self._tags_name assert tags_name == 'tags' else: sys.exit(0) tag_info = system('svn list %s%s' % (base, tags_name)) if 'Could not resolve hostname' in tag_info or \ 'Repository moved' in tag_info: logger.error('Network problem: %s', tag_info) sys.exit() tags = [line.replace('/', '').strip() for line in tag_info.split('\n')] tags = [tag for tag in tags if tag] # filter empty ones logger.debug("Available tags: %r", tags) return tags
def show_changelog_entries(data): # Find changelog # TODO: Figure out how to catch malformed rst if data['history_file'] is not None: changelog = Changelog(file_location=data['history_file']) # Get top release's entry entries = changelog.latest() if entries is None: if not ask("Unable to parse changelog. Continue?", default=True): sys.exit() return print "Changelog entries for version %s." % data['new_version'] for entry in entries: print entry if not ask("Continue?", default=True): sys.exit()
def check_towncrier(data, check_sanity=True, do_draft=True): """Check if towncrier can and should be run. This is a zest.releaser entrypoint that is called during prerelease, postrelease, and bumpversion. Not in all cases are all checks useful, so there are some options. For example, postrelease should not complain that there are no news fragments. """ if TOWNCRIER_MARKER in data: # We have already been called. return data[TOWNCRIER_MARKER] if not data.get('update_history', True): # Someone has instructed zest.releaser to not update the history, # and it was not us, because our marker was not set, # so we should not update the history either. logger.debug( 'update_history is already False, so towncrier will not be run.') data[TOWNCRIER_MARKER] = False return False # Check if towncrier should be applied. result = _is_towncrier_wanted() if not result: logger.debug('towncrier is not wanted.') else: result = _towncrier_executable() if result: logger.debug('towncrier should be run.') # zest.releaser should not update the history. # towncrier will do that. data['update_history'] = False if check_sanity: _report_newsfragments_sanity() if do_draft: # Do a draft. cmd = deepcopy(result) cmd.extend([ '--draft', '--version', data.get('new_version', 't.b.d.'), '--yes', ]) # We would like to pass ['--package' 'package name'] as well, # but that is not yet in a release of towncrier. logger.info( 'Doing dry-run of towncrier to see what would be changed: %s', utils.format_command(cmd)) print(utils.execute_command(cmd)) else: print( dedent(""" According to the pyproject.toml file, towncrier is used to update the changelog. The problem is: we cannot find the towncrier executable. Please make sure it is on your PATH.""")) if not utils.ask('Do you want to continue anyway?', default=False): sys.exit(1) data[TOWNCRIER_MARKER] = result return result
def _grab_history(self): """Calculate the needed history/changelog changes Every history heading looks like '1.0 b4 (1972-12-25)'. Extract them, check if the first one matches the version and whether it has a the current date. """ default_location = None config = self.setup_cfg.config if config and config.has_option('zest.releaser', 'history_file'): default_location = config.get('zest.releaser', 'history_file') history_file = self.vcs.history_file(location=default_location) if not history_file: logger.warn("No history file found") self.data['history_lines'] = None self.data['history_file'] = None self.data['history_encoding'] = None return logger.debug("Checking %s", history_file) history_lines, history_encoding = read_text_file(history_file) history_lines = history_lines.split('\n') headings = utils.extract_headings_from_history(history_lines) if not len(headings): logger.error( "No detectable version heading in the history " "file %s", history_file) sys.exit(1) good_heading = self.data['history_header'] % self.data # ^^^ history_header is a string with %(abc)s replacements. line = headings[0]['line'] previous = history_lines[line] history_lines[line] = good_heading logger.debug("Set heading from %r to %r.", previous, good_heading) history_lines[line + 1] = utils.fix_rst_heading( heading=good_heading, below=history_lines[line + 1]) logger.debug("Set line below heading to %r", history_lines[line + 1]) self.data['history_lines'] = history_lines self.data['history_file'] = history_file self.data['history_encoding'] = history_encoding # TODO: add line number where an extra changelog entry can be # inserted. # Look for 'Nothing changed yet' under the latest header. Not # nice if this text ends up in the changelog. Did nothing happen? start = headings[0]['line'] if len(headings) > 1: end = headings[1]['line'] else: end = -1 for line in history_lines[start:end]: if self.data['nothing_changed_yet'] in line: if not utils.ask( "WARNING: Changelog contains %r. Are you sure you " "want to release?" % self.data['nothing_changed_yet'], default=False): logger.info("You can use the 'lasttaglog' command to " "see the commits since the last tag.") sys.exit(0) break
def _upload_distributions(self, package): # See if creating an sdist (and maybe a wheel) actually works. # Also, this makes the sdist (and wheel) available for plugins. # And for twine, who will just upload the created files. logger.info( "Making a source distribution of a fresh tag checkout (in %s).", self.data['tagworkingdir']) result = utils.execute_command(utils.setup_py('sdist')) utils.show_interesting_lines(result) if self.pypiconfig.create_wheel(): logger.info("Making a wheel of a fresh tag checkout (in %s).", self.data['tagworkingdir']) result = utils.execute_command(utils.setup_py('bdist_wheel')) utils.show_interesting_lines(result) if not self.pypiconfig.is_pypi_configured(): logger.error( "You must have a properly configured %s file in " "your home dir to upload to a Python package index.", pypi.DIST_CONFIG_FILE) if utils.ask("Do you want to continue without uploading?", default=False): return sys.exit(1) # Run extra entry point self._run_hooks('before_upload') # Get list of all files to upload. files_in_dist = sorted([ os.path.join('dist', filename) for filename in os.listdir('dist') ]) register = self.pypiconfig.register_package() # If TWINE_REPOSITORY_URL is set, use it. if self.pypiconfig.twine_repository_url(): if not self._ask_upload( package, self.pypiconfig.twine_repository_url(), register): return if register: self._retry_twine("register", None, files_in_dist[:1]) self._retry_twine("upload", None, files_in_dist) # Only upload to the server specified in the environment return # Upload to the repository in the environment or .pypirc servers = self.pypiconfig.distutils_servers() for server in servers: if not self._ask_upload(package, server, register): continue if register: logger.info("Registering...") # We only need the first file, it has all the needed info self._retry_twine('register', server, files_in_dist[:1]) self._retry_twine('upload', server, files_in_dist)
def cleanup_changelog(data): """Cleanup empty headers. We call this twice: in prereleaser.before and prereleaser.middle. In 'before', we are too early and zest.releaser has not looked for the history file yet. But we try 'CHANGES.rst' ourselves. In 'middle' we are a bit too late, as zest.releaser has already complained when it found the NOTHING_CHANGED_YET value in the history. So we call this twice, which should be fine. """ # The history_file is probably not set yet, as we are called too early. # That might change subtly in future zest.releaser versions, so let's check # it anyway. history_file = data.get('history_file') if history_file: contents = '\n'.join(data['history_lines']) encoding = data['history_encoding'] else: # We do not want to copy the logic from zest.releaser that tries to # find the history file, but we can check the most obvious spot. history_file = 'CHANGES.rst' if not os.path.exists(history_file): print('Cannot cleanup history, will try again later.') return contents, encoding = read_text_file(history_file) orig_contents = contents changed = False for header in ALL_HEADERS: if header in contents: contents = contents.replace(header, '') changed = True if not changed: return write_text_file( history_file, contents, encoding=encoding) print("Cleaned up empty headers from history file {}".format(history_file)) # Update the data, otherwise our work may get overwritten. data['history_lines'] = contents.split('\n') if not os.path.isdir('.git'): print('Not a git checkout, cannot commit.') return g = git.Git('.') message = "Cleaned up empty headers from changelog.\n\n[ci skip]" print(g.diff(history_file)) msg = "Commit changes?" if not ask(msg, default=True): # Restore original contents. write_text_file( history_file, orig_contents, encoding=encoding) sys.exit() print("Committing changes.") print(g.add(history_file)) print(g.commit(message=message))
def _push(self): """Offer to push changes, if needed.""" push_cmds = self.vcs.push_commands() if not push_cmds: return if utils.ask("OK to push commits to the server?"): for push_cmd in push_cmds: output = execute_command(push_cmd) logger.info(output)
def zest_releaser_check(data): """Check the completeness of MANIFEST.in before the release. This is an entry point for zest.releaser. See the documentation at http://zestreleaser.readthedocs.org/en/latest/entrypoints.html """ from zest.releaser.utils import ask if not ask("Do you want to run check-manifest?"): return try: if not check_manifest(data['workingdir']): if not ask("MANIFEST.in is not in order. " " Do you want to continue despite that?", default=False): sys.exit(1) except Failure, e: error(e) if not ask("Something bad happened. " " Do you want to continue despite that?", default=False): sys.exit(2)
def version_control(): """Return an object that provides the version control interface. Base this on the detected version control system. We look for .git, .svn, etcetera in the current directory. We might be in a directory a few levels down from the repository root. So if we find nothing here, we go up a few directories. As safety valve we use a maximum to avoid an endless loop in case there is a logic error. """ path = os.path.abspath(os.curdir) q = "You are NOT in the root of the repository. Do you want to go there?" for level in range(8): curdir_contents = os.listdir(path) if '.svn' in curdir_contents: # Maybe chdir to the found root. if level != 0 and utils.ask(q, default=True): os.chdir(path) return svn.Subversion(path) elif '.hg' in curdir_contents: if level != 0 and utils.ask(q, default=True): os.chdir(path) return hg.Hg(path) elif '.bzr' in curdir_contents: if level != 0 and utils.ask(q, default=True): os.chdir(path) return bzr.Bzr(path) elif '.git' in curdir_contents: if level != 0 and utils.ask(q, default=True): os.chdir(path) return git.Git(path) # Get parent. newpath = os.path.abspath(os.path.join(path, os.pardir)) if newpath == path: # We are at the system root. We cannot go up anymore. break path = newpath logger.critical('No version control system detected.') sys.exit(1)
def _info_if_tag_already_exists(self): if self.data['tag_already_exists']: # Safety feature. version = self.data['version'] tag = self.data['tag'] q = ("There is already a tag %s, show " "if there are differences?" % version) if utils.ask(q): diff_command = self.vcs.cmd_diff_last_commit_against_tag(tag) print(utils.format_command(diff_command)) print(execute_command(diff_command))
def show_changelog_entries(data): # Find changelog # TODO: Figure out how to catch malformed rst if data['history_file'] is None: print "History file not found" if not ask("Continue?", default=True): sys.exit() else: changelog = Changelog(file_location=data['history_file']) # Get top release's entry entries = changelog.latest() if entries is None: if not ask("Unable to parse changelog. Continue?", default=True): sys.exit() return print "Changelog entries for version %s." % data['new_version'] for entry in entries: print entry if not ask("Continue?", default=True): sys.exit()
def _push(self): """Offer to push changes, if needed.""" push_cmds = self.vcs.push_commands() if not push_cmds: return default_anwer = self.pypiconfig.push_changes() if utils.ask("OK to push commits to the server?", default=default_anwer): for push_cmd in push_cmds: output = execute_command(push_cmd) logger.info(output)
def _check_travis(data): # pragma: no cover """Check if Travis reports that everything is ok. (used to perform checks before releasing a new version). """ import json import sys from zest.releaser.utils import system, ask if not ask('Check with Travis before releasing?'): return try: # Python 3 from urllib.request import urlopen def get(url): return urlopen(url).read().decode('utf-8') except ImportError: # Python 2 from urllib2 import urlopen def get(url): return urlopen(url).read() url = 'https://api.github.com/repos/%s/%s/status/%s' username = '******' repo = 'pint' commit = system('git rev-parse HEAD') try: result = json.loads(get(url % (username, repo, commit)))['state'] print('Travis says: %s' % result) if result != 'success': if not ask('Do you want to continue anyway?', default=False): sys.exit(1) except Exception: print('Could not determine the commit state with Travis.') if ask('Do you want to continue anyway?', default=False): sys.exit(1)
def _check_if_tag_already_exists(self): """Check if tag already exists and show the difference if so""" version = self.data["version"] if self.vcs.tag_exists(version): self.data["tag_already_exists"] = True q = "There is already a tag %s, show " "if there are differences?" % version if utils.ask(q): diff_command = self.vcs.cmd_diff_last_commit_against_tag(version) print(diff_command) print(execute_command(diff_command)) else: self.data["tag_already_exists"] = False
def _check_if_tag_already_exists(self): """Check if tag already exists and show the difference if so""" version = self.data['version'] if self.vcs.tag_exists(version): self.data['tag_already_exists'] = True q = ("There is already a tag %s, show " "if there are differences?" % version) if utils.ask(q): diff_command = self.vcs.cmd_diff_last_commit_against_tag( version) print diff_command print system(diff_command) else: self.data['tag_already_exists'] = False
def _check_if_tag_already_exists(self): """Check if tag already exists and show the difference if so""" version = self.data['version'] if self.vcs.tag_exists(version): self.data['tag_already_exists'] = True q = ("There is already a tag %s, show " "if there are differences?" % version) if utils.ask(q): diff_command = self.vcs.cmd_diff_last_commit_against_tag( version) print(diff_command) print(execute_command(diff_command)) else: self.data['tag_already_exists'] = False
def _upload_distributions(self, package): # See if creating an sdist (and maybe a wheel) actually works. # Also, this makes the sdist (and wheel) available for plugins. # And for twine, who will just upload the created files. logger.info( "Making a source distribution of a fresh tag checkout (in %s).", self.data['tagworkingdir']) result = utils.execute_command(utils.setup_py('sdist')) utils.show_interesting_lines(result) if self.pypiconfig.create_wheel(): logger.info("Making a wheel of a fresh tag checkout (in %s).", self.data['tagworkingdir']) result = utils.execute_command(utils.setup_py('bdist_wheel')) utils.show_interesting_lines(result) if not self.pypiconfig.is_pypi_configured(): logger.warn( "You must have a properly configured %s file in " "your home dir to upload to a package index.", pypi.DIST_CONFIG_FILE) return # Get list of all files to upload. files_in_dist = [ os.path.join('dist', filename) for filename in os.listdir('dist') ] # Run extra entry point self._run_hooks('before_upload') # Get servers/repositories. if self.pypiconfig.is_old_pypi_config(): servers = ['pypi'] else: # The user may have defined other servers to upload to. servers = self.pypiconfig.distutils_servers() for server in servers: if self.pypiconfig.register_package(): logger.info("Registering...") # We only need the first file, it has all the needed info self._retry_twine('register', server, files_in_dist[0]) question = "Upload" default = True exact = False if utils.ask("%s to %s" % (question, server), default=default, exact=exact): for filename in files_in_dist: self._retry_twine('upload', server, filename) self._close_all_repositories()
def update_core(data): if ask("Ok to update coredev versions.cfg/checkouts.cfg?", default=True): package_name = data['name'] new_version = data['version'] update_versions(package_name, new_version) update_checkouts(package_name) # git commit root_path = os.path.join(os.getcwd(), '../../') message = "%s %s" % (package_name, new_version) g = git.Git(root_path) g.add('versions.cfg') g.add('checkouts.cfg') print "Commiting changes." g.commit(message=message) print "Pushing changes to server." g.push()
def _diff_and_commit(self): diff_cmd = self.vcs.cmd_diff() diff = system(diff_cmd) if sys.version.startswith('2.6.2'): # python2.6.2 bug... http://bugs.python.org/issue5170 This is the # spot it can surface as we show a part of the changelog which can # contain every kind of character. The rest is mostly ascii. print "Diff results:" print diff else: # Common case logger.info("The '%s':\n\n%s\n" % (diff_cmd, diff)) if utils.ask("OK to commit this"): msg = self.data['commit_msg'] % self.data commit_cmd = self.vcs.cmd_commit(msg) commit = system(commit_cmd) logger.info(commit)
def _make_tag(self): if self.data["tag_already_exists"]: return cmds = self.vcs.cmd_create_tag(self.data["version"]) if not isinstance(cmds, list): cmds = [cmds] if len(cmds) == 1: print("Tag needed to proceed, you can use the following command:") for cmd in cmds: print(cmd) if utils.ask("Run this command"): print(execute_command(cmd)) else: # all commands are needed in order to proceed normally print("Please create a tag for %s yourself and rerun." % (self.data["version"],)) sys.exit(1) if not self.vcs.tag_exists(self.data["version"]): print("\nFailed to create tag %s!" % (self.data["version"],)) sys.exit(1)
def _my_make_tag(self): tag_name = normalize_tag_version(self.data["version"]) if self.data["tag_already_exists"]: return cmds = self.vcs.cmd_create_tag(tag_name) if not isinstance(cmds, list): cmds = [cmds] if len(cmds) == 1: print_("Tag needed to proceed, you can use the following " "command:") for cmd in cmds: print_(cmd) if ask("Run this command"): print_(os.system(cmd)) else: # all commands are needed in order to proceed normally print_("Please create a tag for %s yourself and rerun." % self.data["version"]) sys.exit() if not self.vcs.tag_exists(tag_name): print_("\nFailed to create tag %s!" % tag_name) sys.exit()
def _make_tag(self): if self.data['tag_already_exists']: return cmds = self.vcs.cmd_create_tag(self.data['version']) if not isinstance(cmds, list): cmds = [cmds] if len(cmds) == 1: print "Tag needed to proceed, you can use the following command:" for cmd in cmds: print cmd if utils.ask("Run this command"): print system(cmd) else: # all commands are needed in order to proceed normally print "Please create a tag for %s yourself and rerun." % \ (self.data['version'],) sys.exit() if not self.vcs.tag_exists(self.data['version']): print "\nFailed to create tag %s!" % (self.data['version'],) sys.exit()
def _check_nothing_changed(self): """Look for 'Nothing changed yet' under the latest header. Not nice if this text ends up in the changelog. Did nothing happen? """ if self.data['history_file'] is None: return nothing_yet = self.data['nothing_changed_yet'] if nothing_yet not in self.data['history_last_release']: return # We want quotes around the text, but also want to avoid # printing text with a u'unicode marker' in front... pretty_nothing_changed = '"{}"'.format(nothing_yet) if not utils.ask("WARNING: Changelog contains {}. Are you sure you " "want to release?".format(pretty_nothing_changed), default=False): logger.info("You can use the 'lasttaglog' command to " "see the commits since the last tag.") sys.exit(1)
def _make_tag(self): if self.data['tag_already_exists']: return cmds = self.vcs.cmd_create_tag(self.data['version']) if not isinstance(cmds, list): cmds = [cmds] if len(cmds) == 1: print("Tag needed to proceed, you can use the following command:") for cmd in cmds: print(cmd) if utils.ask("Run this command"): print(execute_command(cmd)) else: # all commands are needed in order to proceed normally print("Please create a tag for %s yourself and rerun." % (self.data['version'], )) sys.exit(1) if not self.vcs.tag_exists(self.data['version']): print("\nFailed to create tag %s!" % (self.data['version'], )) sys.exit(1)
def translations(data): if data["name"] != "django-countries": return if not ask("Pull translations from transifex and compile", default=True): return _handlers = logger.handlers logger.handlers = [] try: cmd_pull(argv=["-a", "--minimum-perc=60"], path_to_tx=find_dot_tx()) finally: logger.handlers = _handlers _cwd = os.getcwd() os.chdir(os.path.dirname(django_countries.__file__)) try: call_command("compilemessages") execute_command(["git", "add", "locale"]) finally: os.chdir(_cwd)
def translations(data): if data['name'] != 'django-countries': return if not ask('Pull translations from transifex and compile', default=True): return _handlers = logger.handlers logger.handlers = [] try: cmd_pull(argv=['-a', '--minimum-perc=60'], path_to_tx=find_dot_tx()) finally: logger.handlers = _handlers _cwd = os.getcwd() os.chdir(os.path.dirname(django_countries.__file__)) try: call_command('compilemessages') execute_command(['git', 'add', 'locale']) finally: os.chdir(_cwd)
def _my_make_tag(self): tag_name = normalize_tag_version(self.data['version']) if self.data['tag_already_exists']: return cmds = self.vcs.cmd_create_tag(tag_name) if not isinstance(cmds, list): cmds = [cmds] if len(cmds) == 1: print_("Tag needed to proceed, you can use the following " "command:") for cmd in cmds: print_(cmd) if ask("Run this command"): print_(os.system(cmd)) else: # all commands are needed in order to proceed normally print_("Please create a tag for %s yourself and rerun." % self.data['version']) sys.exit() if not self.vcs.tag_exists(tag_name): print_("\nFailed to create tag %s!" % tag_name) sys.exit()