diff options
-rw-r--r-- | MANIFEST.in | 2 | ||||
-rw-r--r-- | release.py | 102 | ||||
-rw-r--r-- | tasks.py | 120 |
3 files changed, 103 insertions, 121 deletions
diff --git a/MANIFEST.in b/MANIFEST.in index 14dd0bf..31cb2a0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,7 +8,7 @@ include src/build_bcrypt.py recursive-include src/_csrc * recursive-include tests *.py -exclude requirements.txt tasks.py .travis.yml azure-pipelines.yml +exclude requirements.txt release.py .travis.yml azure-pipelines.yml exclude .azure-pipelines recursive-exclude .azure-pipelines * diff --git a/release.py b/release.py new file mode 100644 index 0000000..1744402 --- /dev/null +++ b/release.py @@ -0,0 +1,102 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import getpass +import glob +import os +import subprocess +import tempfile +import time +import zipfile + +from azure.devops.connection import Connection +from azure.devops.v5_1.build.models import Build + +import click + +from msrest.authentication import BasicAuthentication + + +def run(*args, **kwargs): + print("[running] {0}".format(list(args))) + subprocess.check_call(list(args), **kwargs) + + +def wait_for_build_completed_azure(build_client, build_id): + while True: + build = build_client.get_build("bcrypt", build_id) + if build.finish_time is not None: + break + time.sleep(3) + + +def download_artifacts_azure(build_client, build_id): + artifacts = build_client.get_artifacts("bcrypt", build_id) + paths = [] + for artifact in artifacts: + contents = build_client.get_artifact_content_zip( + "bcrypt", build_id, artifact.name + ) + with tempfile.NamedTemporaryFile() as f: + for chunk in contents: + f.write(chunk) + f.flush() + with zipfile.ZipFile(f.name) as z: + for name in z.namelist(): + if not name.endswith(".whl"): + continue + p = z.open(name) + out_path = os.path.join( + os.path.dirname(__file__), + "dist", + os.path.basename(name), + ) + with open(out_path, "wb") as f: + f.write(p.read()) + paths.append(out_path) + return paths + + +def build_wheels_azure(version): + token = getpass.getpass("Azure personal access token: ") + credentials = BasicAuthentication("", token) + connection = Connection( + base_url="https://dev.azure.com/pyca", creds=credentials + ) + build_client = connection.clients.get_build_client() + [definition] = build_client.get_definitions( + "bcrypt", "bcrypt-wheel-builder" + ) + build_description = Build( + definition=definition, + ) + build = build_client.queue_build( + project="bcrypt", build=build_description + ) + wait_for_build_completed_azure(build_client, build.id) + return download_artifacts_azure(build_client, build.id) + + +@click.command() +@click.argument("version") +def release(version): + """ + ``version`` should be a string like '0.4' or '1.0'. + """ + run("git", "tag", "-s", version, "-m", "{0} release".format(version)) + run("git", "push", "--tags") + + run("python", "setup.py", "sdist") + + packages = glob.glob("dist/bcrypt-{0}*".format(version)) + run("twine", "upload", "-s", *packages) + + azure_wheel_paths = build_wheels_azure(version) + run("twine", "upload", *azure_wheel_paths) + + +if __name__ == "__main__": + release() diff --git a/tasks.py b/tasks.py deleted file mode 100644 index 916ccad..0000000 --- a/tasks.py +++ /dev/null @@ -1,120 +0,0 @@ -from __future__ import absolute_import, division, print_function - -import getpass -import io -import os -import time - -from clint.textui.progress import Bar as ProgressBar - -import invoke - -import requests - - -JENKINS_URL = "https://jenkins.cryptography.io/job/bcrypt-wheel-builder" - - -def wait_for_build_completed(session): - # Wait 20 seconds before actually checking if the build is complete, to - # ensure that it had time to really start. - time.sleep(20) - while True: - response = session.get( - "{}/lastBuild/api/json/".format(JENKINS_URL), - headers={ - "Accept": "application/json", - } - ) - response.raise_for_status() - if not response.json()["building"]: - assert response.json()["result"] == "SUCCESS" - break - time.sleep(0.1) - - -def download_artifacts(session): - response = session.get( - "{}/lastBuild/api/json/".format(JENKINS_URL), - headers={ - "Accept": "application/json" - } - ) - response.raise_for_status() - assert not response.json()["building"] - assert response.json()["result"] == "SUCCESS" - - paths = [] - - last_build_number = response.json()["number"] - for run in response.json()["runs"]: - if run["number"] != last_build_number: - print( - "Skipping {} as it is not from the latest build ({})".format( - run["url"], last_build_number - ) - ) - continue - - response = session.get( - run["url"] + "api/json/", - headers={ - "Accept": "application/json", - } - ) - response.raise_for_status() - for artifact in response.json()["artifacts"]: - response = session.get( - "{}artifact/{}".format(run["url"], artifact["relativePath"]), - stream=True - ) - assert response.headers["content-length"] - print("Downloading {}".format(artifact["fileName"])) - bar = ProgressBar( - expected_size=int(response.headers["content-length"]), - filled_char="=" - ) - content = io.BytesIO() - for data in response.iter_content(chunk_size=8192): - content.write(data) - bar.show(content.tell()) - assert bar.expected_size == content.tell() - bar.done() - out_path = os.path.join( - os.path.dirname(__file__), - "dist", - artifact["fileName"], - ) - with open(out_path, "wb") as f: - f.write(content.getvalue()) - paths.append(out_path) - return paths - - -@invoke.task -def release(version): - """ - ``version`` should be a string like '0.4' or '1.0'. - """ - invoke.run("git tag -s {0} -m '{0} release'".format(version)) - invoke.run("git push --tags") - - invoke.run("python setup.py sdist") - - invoke.run( - "twine upload -s dist/bcrypt-{}*".format(version) - ) - - session = requests.Session() - - token = getpass.getpass("Input the Jenkins token: ") - response = session.post( - "{}/build?token={}".format(JENKINS_URL, token), - params={ - "cause": "Building wheels for {}".format(version) - } - ) - response.raise_for_status() - wait_for_build_completed(session) - paths = download_artifacts(session) - invoke.run("twine upload {}".format(" ".join(paths))) |