Skip to content

Commit

Permalink
build: automate updating readthedocs
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed Nov 16, 2024
1 parent 2a89551 commit 0db86f2
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 19 deletions.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,11 @@ sample_html_beta: _sample_cog_html ## Generate sample HTML report for a beta rel
##@ Kitting: making releases

.PHONY: release_version edit_for_release cheats relbranch relcommit1 relcommit2
.PHONY: kit pypi_upload test_upload kit_local build_kits
.PHONY: kit pypi_upload test_upload kit_local build_kits update_rtd
.PHONY: tag bump_version

REPO_OWNER = nedbat/coveragepy
RTD_PROJECT = coverage

release_version: #: Update the version for a release.
python igor.py release_version
Expand Down Expand Up @@ -228,6 +229,9 @@ tag: #: Make a git tag with the version number (see howto.txt).
git tag -s -m "Version $$(python setup.py --version)" $$(python setup.py --version)
git push --follow-tags

update_rtd: #: Update ReadTheDocs with the versions to show
python ci/update-rtfd.py $(RTD_PROJECT)

bump_version: #: Edit sources to bump the version after a release (see howto.txt).
git switch -c nedbat/bump-version
python igor.py bump_version
Expand Down
26 changes: 14 additions & 12 deletions ci/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,25 @@

import requests

_SESSION = None
_SESSIONS = {}

def get_session():
"""Get a properly authenticated requests Session."""
def get_session(env="GITHUB_TOKEN"):
"""Get a properly authenticated requests Session.
global _SESSION
Get the token from the `env` environment variable.
"""

if _SESSION is None:
# If GITHUB_TOKEN is in the environment, use it.
token = os.environ.get("GITHUB_TOKEN")
session = _SESSIONS.get(env)
if session is None:
token = os.environ.get(env)
if token is None:
sys.exit("!! Must have a GITHUB_TOKEN")
sys.exit(f"!! Must have {env}")

_SESSION = requests.session()
_SESSION.headers["Authorization"] = f"token {token}"
session = requests.session()
session.headers["Authorization"] = f"token {token}"
# requests.get() will always prefer the .netrc file even if a header
# is already set. This tells it to ignore the .netrc file.
_SESSION.trust_env = False
session.trust_env = False
_SESSIONS[env] = session

return _SESSION
return session
107 changes: 107 additions & 0 deletions ci/update-rtfd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""
Update ReadTheDocs to show and hide releases.
"""

import re
import sys

from session import get_session

# How many from each level to show.
NUM_MAJORS = 3
NUM_MINORS = 4
OLD_MINORS = 1
NUM_MICROS = 1
OLD_MICROS = 1


def get_all_versions(project):
"""Pull all the versions for a project from ReadTheDocs."""
versions = []
session = get_session("RTFD_TOKEN")

url = f"https://readthedocs.org/api/v3/projects/{project}/versions/"
while url:
resp = session.get(url)
resp.raise_for_status()
data = resp.json()
versions.extend(data["results"])
url = data["next"]
return versions


def version_tuple(vstr):
"""Convert a tag name into a version_info tuple."""
m = re.fullmatch(r"[^\d]*(\d+)\.(\d+)(?:\.(\d+))?(?:([abc])(\d+))?", vstr)
if not m:
return None
return (
int(m[1]),
int(m[2]),
int(m[3] or 0),
(m[4] or "final"),
int(m[5] or 0),
)


def main(project):
"""Update ReadTheDocs for the versions we want to show."""

# Get all the tags. Where there are dupes, keep the shorter tag for a version.
versions = get_all_versions(project)
versions.sort(key=(lambda v: len(v["verbose_name"])), reverse=True)
vdict = {}
for v in versions:
if v["type"] == "tag":
vinfo = version_tuple(v["verbose_name"])
if vinfo and vinfo[3] == "final":
vdict[vinfo] = v

# Decide which to show and update them.

majors = set()
minors = set()
micros = set()
minors_to_show = NUM_MINORS
micros_to_show = NUM_MICROS

session = get_session("RTFD_TOKEN")
version_list = sorted(vdict.items(), reverse=True)
for vi, ver in version_list:
if vi[:1] not in majors:
majors.add(vi[:1])
minors = set()
if len(majors) > 1:
minors_to_show = OLD_MINORS
micros_to_show = OLD_MICROS
if vi[:2] not in minors:
minors.add(vi[:2])
micros = set()
if vi[:3] not in micros:
micros.add(vi[:3])

show_it = (
len(majors) <= NUM_MAJORS
and len(minors) <= minors_to_show
and len(micros) <= micros_to_show
)
active = ver["active"] or (len(majors) <= NUM_MAJORS)
hidden = not show_it

update = ver["active"] != active or ver["hidden"] != hidden
if update:
print(f"Updating {ver['verbose_name']} to {active=}, {hidden=}")
url = ver["_links"]["_self"]
resp = session.patch(url, data={"active": active, "hidden": hidden})
resp.raise_for_status()

# Set the default version.
latest = version_list[0][1]
print(f"Setting default version to {latest['slug']}")
url = latest["_links"]["project"]
resp = session.patch(url, data={"default_version": latest["slug"]})
resp.raise_for_status()


if __name__ == "__main__":
main(sys.argv[1])
8 changes: 2 additions & 6 deletions howto.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,15 @@ $ deopvars
- Bump version:
$ make bump_version
- Update readthedocs
- @ https://readthedocs.org/projects/coverage/versions/
- IF PRE-RELEASE
- find the latest tag in the inactive list, edit it, make it active.
- keep just the latest version of each x.y release, make the rest active but hidden.
- pre-releases should be hidden
- IF NOT PRE-RELEASE:
- @ https://readthedocs.org/dashboard/coverage/advanced/
- change the "default version" to the new version
$ make update_rtd
- Once CI passes, merge the bump-version branch to master and push it
$ gshipit

- things to automate:
- readthedocs api to do the readthedocs changes


* Testing

Expand Down

0 comments on commit 0db86f2

Please sign in to comment.