summaryrefslogtreecommitdiffstats
path: root/third_party/python/coverage/ci
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/python/coverage/ci')
-rw-r--r--third_party/python/coverage/ci/README.txt1
-rw-r--r--third_party/python/coverage/ci/download_appveyor.py95
-rw-r--r--third_party/python/coverage/ci/install.ps1203
-rwxr-xr-xthird_party/python/coverage/ci/manylinux.sh60
-rw-r--r--third_party/python/coverage/ci/run_with_env.cmd91
-rw-r--r--third_party/python/coverage/ci/upload_relnotes.py122
6 files changed, 572 insertions, 0 deletions
diff --git a/third_party/python/coverage/ci/README.txt b/third_party/python/coverage/ci/README.txt
new file mode 100644
index 0000000000..a34d036bb0
--- /dev/null
+++ b/third_party/python/coverage/ci/README.txt
@@ -0,0 +1 @@
+Files to support continuous integration systems.
diff --git a/third_party/python/coverage/ci/download_appveyor.py b/third_party/python/coverage/ci/download_appveyor.py
new file mode 100644
index 0000000000..a3d814962d
--- /dev/null
+++ b/third_party/python/coverage/ci/download_appveyor.py
@@ -0,0 +1,95 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""Use the Appveyor API to download Windows artifacts."""
+
+import os
+import os.path
+import sys
+import zipfile
+
+import requests
+
+
+def make_auth_headers():
+ """Make the authentication headers needed to use the Appveyor API."""
+ with open("ci/appveyor.token") as f:
+ token = f.read().strip()
+
+ headers = {
+ 'Authorization': 'Bearer {}'.format(token),
+ }
+ return headers
+
+
+def make_url(url, **kwargs):
+ """Build an Appveyor API url."""
+ return "https://ci.appveyor.com/api" + url.format(**kwargs)
+
+
+def get_project_build(account_project):
+ """Get the details of the latest Appveyor build."""
+ url = make_url("/projects/{account_project}", account_project=account_project)
+ response = requests.get(url, headers=make_auth_headers())
+ return response.json()
+
+
+def download_latest_artifacts(account_project):
+ """Download all the artifacts from the latest build."""
+ build = get_project_build(account_project)
+ jobs = build['build']['jobs']
+ print("Build {0[build][version]}, {1} jobs: {0[build][message]}".format(build, len(jobs)))
+ for job in jobs:
+ name = job['name'].partition(':')[2].split(',')[0].strip()
+ print(" {0}: {1[status]}, {1[artifactsCount]} artifacts".format(name, job))
+
+ url = make_url("/buildjobs/{jobid}/artifacts", jobid=job['jobId'])
+ response = requests.get(url, headers=make_auth_headers())
+ artifacts = response.json()
+
+ for artifact in artifacts:
+ is_zip = artifact['type'] == "Zip"
+ filename = artifact['fileName']
+ print(" {}, {} bytes".format(filename, artifact['size']))
+
+ url = make_url(
+ "/buildjobs/{jobid}/artifacts/{filename}",
+ jobid=job['jobId'],
+ filename=filename
+ )
+ download_url(url, filename, make_auth_headers())
+
+ if is_zip:
+ unpack_zipfile(filename)
+ os.remove(filename)
+
+
+def ensure_dirs(filename):
+ """Make sure the directories exist for `filename`."""
+ dirname, _ = os.path.split(filename)
+ if dirname and not os.path.exists(dirname):
+ os.makedirs(dirname)
+
+
+def download_url(url, filename, headers):
+ """Download a file from `url` to `filename`."""
+ ensure_dirs(filename)
+ response = requests.get(url, headers=headers, stream=True)
+ if response.status_code == 200:
+ with open(filename, 'wb') as f:
+ for chunk in response.iter_content(16*1024):
+ f.write(chunk)
+
+
+def unpack_zipfile(filename):
+ """Unpack a zipfile, using the names in the zip."""
+ with open(filename, 'rb') as fzip:
+ z = zipfile.ZipFile(fzip)
+ for name in z.namelist():
+ print(" extracting {}".format(name))
+ ensure_dirs(name)
+ z.extract(name)
+
+
+if __name__ == "__main__":
+ download_latest_artifacts(sys.argv[1])
diff --git a/third_party/python/coverage/ci/install.ps1 b/third_party/python/coverage/ci/install.ps1
new file mode 100644
index 0000000000..fd5ab22021
--- /dev/null
+++ b/third_party/python/coverage/ci/install.ps1
@@ -0,0 +1,203 @@
+# From: https://github.com/ogrisel/python-appveyor-demo/blob/master/appveyor/install.ps1
+#
+#
+# Sample script to install Python and pip under Windows
+# Authors: Olivier Grisel, Jonathan Helmus, Kyle Kastner, and Alex Willmer
+# License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
+
+$MINICONDA_URL = "http://repo.continuum.io/miniconda/"
+$BASE_URL = "https://www.python.org/ftp/python/"
+$GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"
+$GET_PIP_PATH = "C:\get-pip.py"
+
+$PYTHON_PRERELEASE_REGEX = @"
+(?x)
+(?<major>\d+)
+\.
+(?<minor>\d+)
+\.
+(?<micro>\d+)
+(?<prerelease>[a-z]{1,2}\d+)
+"@
+
+
+function Download ($filename, $url) {
+ $webclient = New-Object System.Net.WebClient
+
+ $basedir = $pwd.Path + "\"
+ $filepath = $basedir + $filename
+ if (Test-Path $filename) {
+ Write-Host "Reusing" $filepath
+ return $filepath
+ }
+
+ # Download and retry up to 3 times in case of network transient errors.
+ Write-Host "Downloading" $filename "from" $url
+ $retry_attempts = 2
+ for ($i = 0; $i -lt $retry_attempts; $i++) {
+ try {
+ $webclient.DownloadFile($url, $filepath)
+ break
+ }
+ Catch [Exception]{
+ Start-Sleep 1
+ }
+ }
+ if (Test-Path $filepath) {
+ Write-Host "File saved at" $filepath
+ } else {
+ # Retry once to get the error message if any at the last try
+ $webclient.DownloadFile($url, $filepath)
+ }
+ return $filepath
+}
+
+
+function ParsePythonVersion ($python_version) {
+ if ($python_version -match $PYTHON_PRERELEASE_REGEX) {
+ return ([int]$matches.major, [int]$matches.minor, [int]$matches.micro,
+ $matches.prerelease)
+ }
+ $version_obj = [version]$python_version
+ return ($version_obj.major, $version_obj.minor, $version_obj.build, "")
+}
+
+
+function DownloadPython ($python_version, $platform_suffix) {
+ $major, $minor, $micro, $prerelease = ParsePythonVersion $python_version
+
+ $dir = "$major.$minor.$micro"
+ $ext = "exe"
+ if ($platform_suffix) {
+ $platform_suffix = "-$platform_suffix"
+ }
+
+ $filename = "python-$python_version$platform_suffix.$ext"
+ $url = "$BASE_URL$dir/$filename"
+ $filepath = Download $filename $url
+ return $filepath
+}
+
+
+function InstallPython ($python_version, $architecture, $python_home) {
+ Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
+ if (Test-Path $python_home) {
+ Write-Host $python_home "already exists, skipping."
+ return $false
+ }
+ if ($architecture -eq "32") {
+ $platform_suffix = ""
+ } else {
+ $platform_suffix = "amd64"
+ }
+ $installer_path = DownloadPython $python_version $platform_suffix
+ $installer_ext = [System.IO.Path]::GetExtension($installer_path)
+ Write-Host "Installing $installer_path to $python_home"
+ $install_log = $python_home + ".log"
+ if ($installer_ext -eq '.msi') {
+ InstallPythonMSI $installer_path $python_home $install_log
+ } else {
+ InstallPythonEXE $installer_path $python_home $install_log
+ }
+ if (Test-Path $python_home) {
+ Write-Host "Python $python_version ($architecture) installation complete"
+ } else {
+ Write-Host "Failed to install Python in $python_home"
+ Get-Content -Path $install_log
+ Exit 1
+ }
+}
+
+
+function InstallPythonEXE ($exepath, $python_home, $install_log) {
+ $install_args = "/quiet InstallAllUsers=1 TargetDir=$python_home"
+ RunCommand $exepath $install_args
+}
+
+
+function InstallPythonMSI ($msipath, $python_home, $install_log) {
+ $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home"
+ $uninstall_args = "/qn /x $msipath"
+ RunCommand "msiexec.exe" $install_args
+ if (-not(Test-Path $python_home)) {
+ Write-Host "Python seems to be installed else-where, reinstalling."
+ RunCommand "msiexec.exe" $uninstall_args
+ RunCommand "msiexec.exe" $install_args
+ }
+}
+
+function RunCommand ($command, $command_args) {
+ Write-Host $command $command_args
+ Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru
+}
+
+
+function InstallPip ($python_home) {
+ $pip_path = $python_home + "\Scripts\pip.exe"
+ $python_path = $python_home + "\python.exe"
+ if (-not(Test-Path $pip_path)) {
+ Write-Host "Installing pip..."
+ $webclient = New-Object System.Net.WebClient
+ $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH)
+ Write-Host "Executing:" $python_path $GET_PIP_PATH
+ & $python_path $GET_PIP_PATH
+ } else {
+ Write-Host "pip already installed."
+ }
+}
+
+
+function DownloadMiniconda ($python_version, $platform_suffix) {
+ $filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe"
+ $url = $MINICONDA_URL + $filename
+ $filepath = Download $filename $url
+ return $filepath
+}
+
+
+function InstallMiniconda ($python_version, $architecture, $python_home) {
+ Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
+ if (Test-Path $python_home) {
+ Write-Host $python_home "already exists, skipping."
+ return $false
+ }
+ if ($architecture -eq "32") {
+ $platform_suffix = "x86"
+ } else {
+ $platform_suffix = "x86_64"
+ }
+ $filepath = DownloadMiniconda $python_version $platform_suffix
+ Write-Host "Installing" $filepath "to" $python_home
+ $install_log = $python_home + ".log"
+ $args = "/S /D=$python_home"
+ Write-Host $filepath $args
+ Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru
+ if (Test-Path $python_home) {
+ Write-Host "Python $python_version ($architecture) installation complete"
+ } else {
+ Write-Host "Failed to install Python in $python_home"
+ Get-Content -Path $install_log
+ Exit 1
+ }
+}
+
+
+function InstallMinicondaPip ($python_home) {
+ $pip_path = $python_home + "\Scripts\pip.exe"
+ $conda_path = $python_home + "\Scripts\conda.exe"
+ if (-not(Test-Path $pip_path)) {
+ Write-Host "Installing pip..."
+ $args = "install --yes pip"
+ Write-Host $conda_path $args
+ Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru
+ } else {
+ Write-Host "pip already installed."
+ }
+}
+
+function main () {
+ InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON
+ InstallPip $env:PYTHON
+}
+
+main
diff --git a/third_party/python/coverage/ci/manylinux.sh b/third_party/python/coverage/ci/manylinux.sh
new file mode 100755
index 0000000000..1fafec9ded
--- /dev/null
+++ b/third_party/python/coverage/ci/manylinux.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+# From: https://github.com/pypa/python-manylinux-demo/blob/master/travis/build-wheels.sh
+# which is in the public domain.
+#
+# This is run inside a CentOS 5 virtual machine to build manylinux wheels:
+#
+# $ docker run -v `pwd`:/io quay.io/pypa/manylinux1_x86_64 /io/ci/build_manylinux.sh
+#
+
+set -e -x
+
+action=$1
+shift
+
+if [[ $action == "build" ]]; then
+ # Compile wheels
+ cd /io
+ for PYBIN in /opt/python/*/bin; do
+ if [[ $PYBIN == *cp34* ]]; then
+ # manylinux docker images have Python 3.4, but we don't use it.
+ continue
+ fi
+ "$PYBIN/pip" install -r requirements/wheel.pip
+ "$PYBIN/python" setup.py clean -a
+ "$PYBIN/python" setup.py bdist_wheel -d ~/wheelhouse/
+ done
+ cd ~
+
+ # Bundle external shared libraries into the wheels
+ for whl in wheelhouse/*.whl; do
+ auditwheel repair "$whl" -w /io/dist/
+ done
+
+elif [[ $action == "test" ]]; then
+ # Create "pythonX.Y" links
+ for PYBIN in /opt/python/*/bin/; do
+ if [[ $PYBIN == *cp34* ]]; then
+ # manylinux docker images have Python 3.4, but we don't use it.
+ continue
+ fi
+ PYNAME=$("$PYBIN/python" -c "import sys; print('python{0[0]}.{0[1]}'.format(sys.version_info))")
+ ln -sf "$PYBIN/$PYNAME" /usr/local/bin/$PYNAME
+ done
+
+ # Install packages and test
+ TOXBIN=/opt/python/cp36-cp36m/bin
+ "$TOXBIN/pip" install -r /io/requirements/tox.pip
+
+ cd /io
+ export PYTHONPYCACHEPREFIX=/opt/pyc
+ if [[ $1 == "meta" ]]; then
+ shift
+ export COVERAGE_COVERAGE=yes
+ fi
+ TOXWORKDIR=.tox/linux "$TOXBIN/tox" "$@" || true
+ cd ~
+
+else
+ echo "Need an action to perform!"
+fi
diff --git a/third_party/python/coverage/ci/run_with_env.cmd b/third_party/python/coverage/ci/run_with_env.cmd
new file mode 100644
index 0000000000..66b9252efc
--- /dev/null
+++ b/third_party/python/coverage/ci/run_with_env.cmd
@@ -0,0 +1,91 @@
+:: From: https://github.com/ogrisel/python-appveyor-demo/blob/master/appveyor/run_with_env.cmd
+::
+::
+:: To build extensions for 64 bit Python 3, we need to configure environment
+:: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of:
+:: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1)
+::
+:: To build extensions for 64 bit Python 2, we need to configure environment
+:: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of:
+:: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0)
+::
+:: 32 bit builds, and 64-bit builds for 3.5 and beyond, do not require specific
+:: environment configurations.
+::
+:: Note: this script needs to be run with the /E:ON and /V:ON flags for the
+:: cmd interpreter, at least for (SDK v7.0)
+::
+:: More details at:
+:: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows
+:: http://stackoverflow.com/a/13751649/163740
+::
+:: Author: Olivier Grisel
+:: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
+::
+:: Notes about batch files for Python people:
+::
+:: Quotes in values are literally part of the values:
+:: SET FOO="bar"
+:: FOO is now five characters long: " b a r "
+:: If you don't want quotes, don't include them on the right-hand side.
+::
+:: The CALL lines at the end of this file look redundant, but if you move them
+:: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y
+:: case, I don't know why.
+@ECHO OFF
+
+SET COMMAND_TO_RUN=%*
+SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows
+SET WIN_WDK=c:\Program Files (x86)\Windows Kits\10\Include\wdf
+
+:: Extract the major and minor versions, and allow for the minor version to be
+:: more than 9. This requires the version number to have two dots in it.
+SET MAJOR_PYTHON_VERSION=%PYTHON_VERSION:~0,1%
+IF "%PYTHON_VERSION:~3,1%" == "." (
+ SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,1%
+) ELSE (
+ SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,2%
+)
+
+:: Based on the Python version, determine what SDK version to use, and whether
+:: to set the SDK for 64-bit.
+IF %MAJOR_PYTHON_VERSION% == 2 (
+ SET WINDOWS_SDK_VERSION="v7.0"
+ SET SET_SDK_64=Y
+) ELSE (
+ IF %MAJOR_PYTHON_VERSION% == 3 (
+ SET WINDOWS_SDK_VERSION="v7.1"
+ IF %MINOR_PYTHON_VERSION% LEQ 4 (
+ SET SET_SDK_64=Y
+ ) ELSE (
+ SET SET_SDK_64=N
+ IF EXIST "%WIN_WDK%" (
+ :: See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/
+ REN "%WIN_WDK%" 0wdf
+ )
+ )
+ ) ELSE (
+ ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%"
+ EXIT 1
+ )
+)
+
+IF %PYTHON_ARCH% == 64 (
+ IF %SET_SDK_64% == Y (
+ ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture
+ SET DISTUTILS_USE_SDK=1
+ SET MSSdk=1
+ "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION%
+ "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release
+ ECHO Executing: %COMMAND_TO_RUN%
+ call %COMMAND_TO_RUN% || EXIT 1
+ ) ELSE (
+ ECHO Using default MSVC build environment for 64 bit architecture
+ ECHO Executing: %COMMAND_TO_RUN%
+ call %COMMAND_TO_RUN% || EXIT 1
+ )
+) ELSE (
+ ECHO Using default MSVC build environment for 32 bit architecture
+ ECHO Executing: %COMMAND_TO_RUN%
+ call %COMMAND_TO_RUN% || EXIT 1
+)
diff --git a/third_party/python/coverage/ci/upload_relnotes.py b/third_party/python/coverage/ci/upload_relnotes.py
new file mode 100644
index 0000000000..630f4d0a3f
--- /dev/null
+++ b/third_party/python/coverage/ci/upload_relnotes.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+"""
+Upload CHANGES.md to Tidelift as Markdown chunks
+
+Put your Tidelift API token in a file called tidelift.token alongside this
+program, for example:
+
+ user/n3IwOpxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxc2ZwE4
+
+Run with two arguments: the .md file to parse, and the Tidelift package name:
+
+ python upload_relnotes.py CHANGES.md pypi/coverage
+
+Every section that has something that looks like a version number in it will
+be uploaded as the release notes for that version.
+
+"""
+
+import os.path
+import re
+import sys
+
+import requests
+
+class TextChunkBuffer:
+ """Hold onto text chunks until needed."""
+ def __init__(self):
+ self.buffer = []
+
+ def append(self, text):
+ """Add `text` to the buffer."""
+ self.buffer.append(text)
+
+ def clear(self):
+ """Clear the buffer."""
+ self.buffer = []
+
+ def flush(self):
+ """Produce a ("text", text) tuple if there's anything here."""
+ buffered = "".join(self.buffer).strip()
+ if buffered:
+ yield ("text", buffered)
+ self.clear()
+
+
+def parse_md(lines):
+ """Parse markdown lines, producing (type, text) chunks."""
+ buffer = TextChunkBuffer()
+
+ for line in lines:
+ header_match = re.search(r"^(#+) (.+)$", line)
+ is_header = bool(header_match)
+ if is_header:
+ yield from buffer.flush()
+ hashes, text = header_match.groups()
+ yield (f"h{len(hashes)}", text)
+ else:
+ buffer.append(line)
+
+ yield from buffer.flush()
+
+
+def sections(parsed_data):
+ """Convert a stream of parsed tokens into sections with text and notes.
+
+ Yields a stream of:
+ ('h-level', 'header text', 'text')
+
+ """
+ header = None
+ text = []
+ for ttype, ttext in parsed_data:
+ if ttype.startswith('h'):
+ if header:
+ yield (*header, "\n".join(text))
+ text = []
+ header = (ttype, ttext)
+ elif ttype == "text":
+ text.append(ttext)
+ else:
+ raise Exception(f"Don't know ttype {ttype!r}")
+ yield (*header, "\n".join(text))
+
+
+def relnotes(mdlines):
+ r"""Yield (version, text) pairs from markdown lines.
+
+ Each tuple is a separate version mentioned in the release notes.
+
+ A version is any section with \d\.\d in the header text.
+
+ """
+ for _, htext, text in sections(parse_md(mdlines)):
+ m_version = re.search(r"\d+\.\d[^ ]*", htext)
+ if m_version:
+ version = m_version.group()
+ yield version, text
+
+def update_release_note(package, version, text):
+ """Update the release notes for one version of a package."""
+ url = f"https://api.tidelift.com/external-api/lifting/{package}/release-notes/{version}"
+ token_file = os.path.join(os.path.dirname(__file__), "tidelift.token")
+ with open(token_file) as ftoken:
+ token = ftoken.read().strip()
+ headers = {
+ "Authorization": f"Bearer: {token}",
+ }
+ req_args = dict(url=url, data=text.encode('utf8'), headers=headers)
+ result = requests.post(**req_args)
+ if result.status_code == 409:
+ result = requests.put(**req_args)
+ print(f"{version}: {result.status_code}")
+
+def parse_and_upload(md_filename, package):
+ """Main function: parse markdown and upload to Tidelift."""
+ with open(md_filename) as f:
+ markdown = f.read()
+ for version, text in relnotes(markdown.splitlines(True)):
+ update_release_note(package, version, text)
+
+if __name__ == "__main__":
+ parse_and_upload(*sys.argv[1:]) # pylint: disable=no-value-for-parameter