| # -*- coding: UTF-8 -*- |
| """ |
| Tasks for releasing this project. |
| |
| Normal steps:: |
| |
| |
| python setup.py sdist bdist_wheel |
| |
| twine register dist/{project}-{version}.tar.gz |
| twine upload dist/* |
| |
| twine upload --skip-existing dist/* |
| |
| python setup.py upload |
| # -- DEPRECATED: No longer supported -> Use RTD instead |
| # -- DEPRECATED: python setup.py upload_docs |
| |
| pypi repositories: |
| |
| * https://pypi.python.org/pypi |
| * https://testpypi.python.org/pypi (not working anymore) |
| * https://test.pypi.org/legacy/ (not working anymore) |
| |
| Configuration file for pypi repositories: |
| |
| .. code-block:: init |
| |
| # -- FILE: $HOME/.pypirc |
| [distutils] |
| index-servers = |
| pypi |
| testpypi |
| |
| [pypi] |
| # DEPRECATED: repository = https://pypi.python.org/pypi |
| username = __USERNAME_HERE__ |
| password: |
| |
| [testpypi] |
| # DEPRECATED: repository = https://test.pypi.org/legacy |
| username = __USERNAME_HERE__ |
| password: |
| |
| .. seealso:: |
| |
| * https://packaging.python.org/ |
| * https://packaging.python.org/guides/ |
| * https://packaging.python.org/tutorials/distributing-packages/ |
| """ |
| |
| from __future__ import absolute_import, print_function |
| from invoke import Collection, task |
| from ._tasklet_cleanup import path_glob |
| from ._dry_run import DryRunContext |
| |
| |
| # ----------------------------------------------------------------------------- |
| # TASKS: |
| # ----------------------------------------------------------------------------- |
| @task |
| def checklist(ctx=None): # pylint: disable=unused-argument |
| """Checklist for releasing this project.""" |
| checklist_text = """PRE-RELEASE CHECKLIST: |
| [ ] Everything is checked in |
| [ ] All tests pass w/ tox |
| |
| RELEASE CHECKLIST: |
| [{x1}] Bump version to new-version and tag repository (via bump_version) |
| [{x2}] Build packages (sdist, bdist_wheel via prepare) |
| [{x3}] Register and upload packages to testpypi repository (first) |
| [{x4}] Verify release is OK and packages from testpypi are usable |
| [{x5}] Register and upload packages to pypi repository |
| [{x6}] Push last changes to Github repository |
| |
| POST-RELEASE CHECKLIST: |
| [ ] Bump version to new-develop-version (via bump_version) |
| [ ] Adapt CHANGES (if necessary) |
| [ ] Commit latest changes to Github repository |
| """ |
| steps = dict(x1=None, x2=None, x3=None, x4=None, x5=None, x6=None) |
| yesno_map = {True: "x", False: "_", None: " "} |
| answers = {name: yesno_map[value] |
| for name, value in steps.items()} |
| print(checklist_text.format(**answers)) |
| |
| |
| @task(name="bump_version") |
| def bump_version(ctx, new_version, version_part=None, dry_run=False): |
| """Bump version (to prepare a new release).""" |
| version_part = version_part or "minor" |
| if dry_run: |
| ctx = DryRunContext(ctx) |
| ctx.run("bumpversion --new-version={} {}".format(new_version, |
| version_part)) |
| |
| |
| @task(name="build", aliases=["build_packages"]) |
| def build_packages(ctx, hide=False): |
| """Build packages for this release.""" |
| print("build_packages:") |
| ctx.run("python setup.py sdist bdist_wheel", echo=True, hide=hide) |
| |
| |
| @task |
| def prepare(ctx, new_version=None, version_part=None, hide=True, |
| dry_run=False): |
| """Prepare the release: bump version, build packages, ...""" |
| if new_version is not None: |
| bump_version(ctx, new_version, version_part=version_part, |
| dry_run=dry_run) |
| build_packages(ctx, hide=hide) |
| packages = ensure_packages_exist(ctx, check_only=True) |
| print_packages(packages) |
| |
| # -- NOT-NEEDED: |
| # @task(name="register") |
| # def register_packages(ctx, repo=None, dry_run=False): |
| # """Register release (packages) in artifact-store/repository.""" |
| # original_ctx = ctx |
| # if repo is None: |
| # repo = ctx.project.repo or "pypi" |
| # if dry_run: |
| # ctx = DryRunContext(ctx) |
| |
| # packages = ensure_packages_exist(original_ctx) |
| # print_packages(packages) |
| # for artifact in packages: |
| # ctx.run("twine register --repository={repo} {artifact}".format( |
| # artifact=artifact, repo=repo)) |
| |
| |
| @task |
| def upload(ctx, repo=None, repo_url=None, dry_run=False, |
| skip_existing=False, verbose=False): |
| """Upload release packages to repository (artifact-store).""" |
| if repo is None: |
| repo = ctx.project.repo or "pypi" |
| if repo_url is None: |
| repo_url = ctx.project.repo_url or None |
| original_ctx = ctx |
| if dry_run: |
| ctx = DryRunContext(ctx) |
| |
| # -- OPTIONS: |
| opts = [] |
| if repo_url: |
| opts.append("--repository-url={0}".format(repo_url)) |
| elif repo: |
| opts.append("--repository={0}".format(repo)) |
| if skip_existing: |
| opts.append("--skip-existing") |
| if verbose: |
| opts.append("--verbose") |
| |
| packages = ensure_packages_exist(original_ctx) |
| print_packages(packages) |
| ctx.run("twine upload {opts} dist/*".format(opts=" ".join(opts))) |
| |
| # ctx.run("twine upload --repository={repo} dist/*".format(repo=repo)) |
| # 2018-05-05 WORK-AROUND for new https://pypi.org/: |
| # twine upload --repository-url=https://upload.pypi.org/legacy /dist/* |
| # NOT-WORKING: repo_url = "https://upload.pypi.org/simple/" |
| # |
| # ctx.run("twine upload --repository-url={repo_url} {opts} dist/*".format( |
| # repo_url=repo_url, opts=" ".join(opts))) |
| # ctx.run("twine upload --repository={repo} {opts} dist/*".format( |
| # repo=repo, opts=" ".join(opts))) |
| |
| |
| # -- DEPRECATED: Use RTD instead |
| # @task(name="upload_docs") |
| # def upload_docs(ctx, repo=None, dry_run=False): |
| # """Upload and publish docs. |
| # |
| # NOTE: Docs are built first. |
| # """ |
| # if repo is None: |
| # repo = ctx.project.repo or "pypi" |
| # if dry_run: |
| # ctx = DryRunContext(ctx) |
| # |
| # ctx.run("python setup.py upload_docs") |
| # |
| # ----------------------------------------------------------------------------- |
| # TASK HELPERS: |
| # ----------------------------------------------------------------------------- |
| def print_packages(packages): |
| print("PACKAGES[%d]:" % len(packages)) |
| for package in packages: |
| package_size = package.stat().st_size |
| package_time = package.stat().st_mtime |
| print(" - %s (size=%s)" % (package, package_size)) |
| |
| |
| def ensure_packages_exist(ctx, pattern=None, check_only=False): |
| if pattern is None: |
| project_name = ctx.project.name |
| project_prefix = project_name.replace("_", "-").split("-")[0] |
| pattern = "dist/%s*" % project_prefix |
| |
| packages = list(path_glob(pattern, current_dir=".")) |
| if not packages: |
| if check_only: |
| message = "No artifacts found: pattern=%s" % pattern |
| raise RuntimeError(message) |
| else: |
| # -- RECURSIVE-SELF-CALL: Once |
| print("NO-PACKAGES-FOUND: Build packages first ...") |
| build_packages(ctx, hide=True) |
| packages = ensure_packages_exist(ctx, pattern, |
| check_only=True) |
| return packages |
| |
| |
| # ----------------------------------------------------------------------------- |
| # TASK CONFIGURATION: |
| # ----------------------------------------------------------------------------- |
| # DISABLED: register_packages |
| namespace = Collection(bump_version, checklist, prepare, build_packages, upload) |
| namespace.configure({ |
| "project": { |
| "repo": "pypi", |
| "repo_url": None, |
| } |
| }) |