Skip to content

Commit f78aebb

Browse files
trivialfishcho3
andauthored
Use REST API to trigger RTD build. (dmlc#11216)
--------- Co-authored-by: Hyunsu Cho <[email protected]> Co-authored-by: Hyunsu Cho <[email protected]>
1 parent 9715661 commit f78aebb

File tree

4 files changed

+199
-32
lines changed

4 files changed

+199
-32
lines changed

.github/workflows/doc.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: XGBoost-docs
2+
3+
on: [push, pull_request]
4+
5+
env:
6+
BRANCH_NAME: >-
7+
${{ github.event.pull_request.number && 'PR-' }}${{ github.event.pull_request.number || github.ref_name }}
8+
9+
jobs:
10+
build-jvm-docs:
11+
name: Build docs for JVM packages
12+
runs-on:
13+
- runs-on=${{ github.run_id }}
14+
- runner=linux-amd64-cpu
15+
- tag=doc-build-jvm-docs
16+
steps:
17+
# Restart Docker daemon so that it recognizes the ephemeral disks
18+
- run: sudo systemctl restart docker
19+
- uses: actions/checkout@v4
20+
with:
21+
submodules: "true"
22+
- name: Log into Docker registry (AWS ECR)
23+
run: bash ops/pipeline/login-docker-registry.sh
24+
- run: bash ops/pipeline/build-jvm-gpu.sh
25+
- run: bash ops/pipeline/build-jvm-doc.sh
26+
- name: Upload JVM doc
27+
run: |
28+
# xgboost-docs/{branch}/{commit}/{branch}.tar.bz2
29+
# branch can be the name of the dmlc/xgboost branch, or `PR-{number}`.
30+
python3 ops/pipeline/manage-artifacts.py upload \
31+
--s3-bucket xgboost-docs \
32+
--prefix ${BRANCH_NAME}/${GITHUB_SHA} --make-public \
33+
jvm-packages/${{ env.BRANCH_NAME }}.tar.bz2
34+
35+
trigger-rtd-build:
36+
needs: [build-jvm-docs]
37+
name: Trigger Read The Docs build.
38+
runs-on:
39+
- runs-on=${{ github.run_id }}
40+
- runner=linux-amd64-cpu
41+
- tag=doc-trigger-rtd-build
42+
steps:
43+
# Restart Docker daemon so that it recognizes the ephemeral disks
44+
- run: sudo systemctl restart docker
45+
- uses: actions/checkout@v4
46+
with:
47+
submodules: "true"
48+
- name: Trigger RTD
49+
run: bash ops/pipeline/trigger-rtd.sh

doc/conf.py

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
1-
# -*- coding: utf-8 -*-
2-
#
3-
# documentation build configuration file, created by
4-
# sphinx-quickstart on Thu Jul 23 19:40:08 2015.
5-
#
6-
# This file is execfile()d with the current directory set to its
7-
# containing dir.
8-
#
9-
# Note that not all possible configuration values are present in this
10-
# autogenerated file.
11-
#
12-
# All configuration values have a default; values that are commented out
13-
# serve to show the default.
1+
"""Sphinx configuration.
2+
3+
See `doc/contrib/docs.rst <https://xgboost.readthedocs.io/en/stable/contrib/docs.html>`__
4+
for more info.
5+
"""
6+
147
import os
15-
import re
168
import shutil
179
import subprocess
1810
import sys
@@ -26,6 +18,9 @@
2618
TMP_DIR = os.path.join(CURR_PATH, "tmp")
2719
DOX_DIR = "doxygen"
2820

21+
# Directly load the source module.
22+
sys.path.append(os.path.join(PROJECT_ROOT, "python-package"))
23+
# Tell xgboost to not load the libxgboost.so
2924
os.environ["XGBOOST_BUILD_DOC"] = "1"
3025

3126
# Version information.
@@ -35,7 +30,11 @@
3530
release = xgboost.__version__
3631

3732

38-
def run_doxygen():
33+
# Document is uploaded to here by the CI builder.
34+
S3_BUCKET = "https://xgboost-docs.s3.us-west-2.amazonaws.com"
35+
36+
37+
def run_doxygen() -> None:
3938
"""Run the doxygen make command in the designated folder."""
4039
curdir = os.path.normpath(os.path.abspath(os.path.curdir))
4140
if os.path.exists(TMP_DIR):
@@ -67,33 +66,74 @@ def run_doxygen():
6766
os.chdir(curdir)
6867

6968

70-
def build_jvm_docs():
71-
"""Build docs for the JVM packages"""
72-
git_branch = os.getenv("READTHEDOCS_VERSION_NAME", default=None)
73-
print(f"READTHEDOCS_VERSION_NAME = {git_branch}")
69+
def get_branch() -> str:
70+
"""Guess the git branch."""
71+
branch = os.getenv("READTHEDOCS_VERSION_NAME", default=None)
72+
print(f"READTHEDOCS_VERSION_NAME = {branch}")
73+
74+
def is_id():
75+
try:
76+
return str(int(branch)) == branch
77+
except ValueError:
78+
return False
79+
80+
if not branch: # Not in RTD
81+
branch = "master" # use the master branch as the default.
82+
elif branch == "latest":
83+
branch = "master"
84+
elif branch.startswith("release_"):
85+
pass # release branch, like: release_2.1.0
86+
elif branch == "stable":
87+
branch = f"release_{xgboost.__version__}"
88+
elif is_id():
89+
# Likely PR branch
90+
branch = f"PR-{branch}"
91+
else: # other dmlc branches.
92+
pass
93+
print(f"branch = {branch}")
94+
return branch
95+
96+
97+
def get_sha(branch: str) -> str | None:
98+
sha = os.getenv("READTHEDOCS_GIT_COMMIT_HASH", default=None)
99+
if sha is not None:
100+
return sha
101+
102+
if branch == "master":
103+
res = subprocess.run(["git", "rev-parse", "master"], stdout=subprocess.PIPE)
104+
else:
105+
res = subprocess.run(["git", "rev-parse", "HEAD"], stdout=subprocess.PIPE)
106+
if res.returncode != 0:
107+
return None
108+
return res.stdout.decode("utf-8").strip()
109+
74110

75-
if not git_branch:
76-
git_branch = "master"
77-
elif git_branch == "latest":
78-
git_branch = "master"
79-
elif git_branch == "stable":
80-
git_branch = f"release_{xgboost.__version__}"
81-
print(f"git_branch = {git_branch}")
111+
def build_jvm_docs() -> None:
112+
"""Fetch docs for the JVM packages"""
113+
branch = get_branch()
114+
commit = get_sha(branch)
115+
if commit is None:
116+
print("Couldn't find commit to build jvm docs.")
117+
return
82118

83-
def try_fetch_jvm_doc(branch):
119+
def try_fetch_jvm_doc(branch: str) -> bool:
84120
"""
85121
Attempt to fetch JVM docs for a given branch.
86122
Returns True if successful
87123
"""
88124
try:
89-
url = f"https://s3-us-west-2.amazonaws.com/xgboost-docs/{branch}.tar.bz2"
90-
filename, _ = urllib.request.urlretrieve(url)
125+
local_jvm_docs = os.environ.get("XGBOOST_JVM_DOCS", None)
126+
if local_jvm_docs is not None:
127+
filename = os.path.expanduser(local_jvm_docs)
128+
else:
129+
url = f"{S3_BUCKET}/{branch}/{commit}/{branch}.tar.bz2"
130+
filename, _ = urllib.request.urlretrieve(url)
131+
print(f"Finished: {url} -> {filename}")
91132
if not os.path.exists(TMP_DIR):
92133
print(f"Create directory {TMP_DIR}")
93134
os.mkdir(TMP_DIR)
94135
jvm_doc_dir = os.path.join(TMP_DIR, "jvm_docs")
95136
if os.path.exists(jvm_doc_dir):
96-
print(f"Delete directory {jvm_doc_dir}")
97137
shutil.rmtree(jvm_doc_dir)
98138
print(f"Create directory {jvm_doc_dir}")
99139
os.mkdir(jvm_doc_dir)
@@ -105,8 +145,8 @@ def try_fetch_jvm_doc(branch):
105145
print(f"JVM doc not found at {url}. Skipping...")
106146
return False
107147

108-
if not try_fetch_jvm_doc(git_branch):
109-
print(f"Falling back to the master branch...")
148+
if not try_fetch_jvm_doc(branch):
149+
print("Falling back to the master branch.")
110150
try_fetch_jvm_doc("master")
111151

112152

ops/pipeline/trigger-rtd-impl.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""Helper script for triggering Read the docs build.
2+
3+
See `doc/contrib/docs.rst <https://xgboost.readthedocs.io/en/stable/contrib/docs.html>`__
4+
for more info.
5+
6+
"""
7+
8+
import json
9+
import os
10+
import pprint
11+
from http.client import responses as http_responses
12+
13+
import requests # type: ignore
14+
15+
16+
def trigger_build(token: str) -> None:
17+
"""Trigger RTD build."""
18+
19+
event_path = os.environ["GITHUB_EVENT_PATH"]
20+
with open(event_path, "r") as fd:
21+
event: dict = json.load(fd)
22+
23+
if event.get("pull_request", None) is None:
24+
# refs/heads/branch-name
25+
branch = event["ref"].split("/")[-1]
26+
else:
27+
branch = event["pull_request"]["number"]
28+
29+
URL = f"https://readthedocs.org/api/v3/projects/xgboost/versions/{branch}/builds/"
30+
HEADERS = {"Authorization": f"token {token}"}
31+
response = requests.post(URL, headers=HEADERS)
32+
# 202 means the build is successfully triggered.
33+
if response.status_code != 202:
34+
status_text = http_responses[response.status_code]
35+
raise RuntimeError(
36+
"ReadTheDocs returned an unexpected response: "
37+
f"{response.status_code} {status_text}, reason: {response.reason}"
38+
)
39+
pprint.pprint(response.json(), indent=4)
40+
41+
42+
def main() -> None:
43+
token = os.getenv("RTD_AUTH_TOKEN")
44+
# GA redacts the secret by default, but we should still be really careful to not log
45+
# (expose) the token in the CI.
46+
if token is None:
47+
raise RuntimeError(
48+
"The RTD_AUTH_TOKEN environment variable must be set to a valid auth token for the"
49+
"ReadTheDocs service."
50+
)
51+
if len(token) == 0:
52+
print("Document build is not triggered.")
53+
return
54+
55+
if not isinstance(token, str) or len(token) != 40:
56+
raise ValueError(f"Invalid token.")
57+
58+
trigger_build(token)
59+
60+
61+
if __name__ == "__main__":
62+
main()

ops/pipeline/trigger-rtd.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
## Trigger a new build on ReadTheDocs service.
3+
4+
set -euo pipefail
5+
6+
if [[ -z ${BRANCH_NAME:-} ]]
7+
then
8+
echo "Make sure to define environment variable BRANCH_NAME."
9+
exit 1
10+
fi
11+
12+
echo "Branch name: ${BRANCH_NAME}"
13+
export RTD_AUTH_TOKEN=$(aws secretsmanager get-secret-value \
14+
--secret-id runs-on/readthedocs-auth-token --output text \
15+
--region us-west-2 --query SecretString || echo -n '')
16+
python3 ops/pipeline/trigger-rtd-impl.py

0 commit comments

Comments
 (0)