From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- third_party/python/coverage/ci/README.txt | 1 + .../python/coverage/ci/download_appveyor.py | 95 ++++++++++ third_party/python/coverage/ci/install.ps1 | 203 +++++++++++++++++++++ third_party/python/coverage/ci/manylinux.sh | 60 ++++++ third_party/python/coverage/ci/run_with_env.cmd | 91 +++++++++ third_party/python/coverage/ci/upload_relnotes.py | 122 +++++++++++++ 6 files changed, 572 insertions(+) create mode 100644 third_party/python/coverage/ci/README.txt create mode 100644 third_party/python/coverage/ci/download_appveyor.py create mode 100644 third_party/python/coverage/ci/install.ps1 create mode 100755 third_party/python/coverage/ci/manylinux.sh create mode 100644 third_party/python/coverage/ci/run_with_env.cmd create mode 100644 third_party/python/coverage/ci/upload_relnotes.py (limited to 'third_party/python/coverage/ci') 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) +(?\d+) +\. +(?\d+) +\. +(?\d+) +(?[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 -- cgit v1.2.3