Skip to content

Commit 98a09db

Browse files
authored
Merge pull request #90 from QuLogic/fix-docs
Add docs CI and fix some bugs in it
2 parents 5ac0e9f + 060ec33 commit 98a09db

File tree

7 files changed

+278
-16
lines changed

7 files changed

+278
-16
lines changed

.circleci/config.yml

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Circle CI configuration file
2+
# https://circleci.com/docs/
3+
4+
---
5+
version: 2.1
6+
7+
#######################################
8+
# Define some common steps as commands.
9+
#
10+
11+
commands:
12+
check-skip:
13+
steps:
14+
- run:
15+
name: Check-skip
16+
command: |
17+
export git_log=$(git log --max-count=1 --pretty=format:"%B" |
18+
tr "\n" " ")
19+
echo "Got commit message:"
20+
echo "${git_log}"
21+
if [[ -v CIRCLE_PULL_REQUEST ]] && ( \
22+
[[ "$git_log" == *"[skip circle]"* ]] || \
23+
[[ "$git_log" == *"[circle skip]"* ]]); then
24+
echo "Skip detected, exiting job ${CIRCLE_JOB} for PR ${CIRCLE_PULL_REQUEST}."
25+
circleci-agent step halt;
26+
fi
27+
28+
merge:
29+
steps:
30+
- run:
31+
name: Merge with upstream
32+
command: |
33+
if ! git remote -v | grep upstream; then
34+
git remote add upstream https://github.com/matplotlib/cycler.git
35+
fi
36+
git fetch upstream
37+
if [[ "$CIRCLE_BRANCH" != "main" ]] && \
38+
[[ "$CIRCLE_PR_NUMBER" != "" ]]; then
39+
echo "Merging ${CIRCLE_PR_NUMBER}"
40+
git pull --ff-only upstream "refs/pull/${CIRCLE_PR_NUMBER}/merge"
41+
fi
42+
43+
pip-install:
44+
description: Upgrade pip to get as clean an install as possible
45+
steps:
46+
- run:
47+
name: Upgrade pip
48+
command: |
49+
python -m pip install --upgrade --user pip
50+
51+
cycler-install:
52+
steps:
53+
- run:
54+
name: Install Cycler
55+
command: |
56+
python -m pip install --user -ve .[docs]
57+
58+
doc-build:
59+
steps:
60+
- restore_cache:
61+
keys:
62+
- sphinx-env-v1-{{ .BuildNum }}-{{ .Environment.CIRCLE_JOB }}
63+
- sphinx-env-v1-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}-{{ .Environment.CIRCLE_JOB }}
64+
- run:
65+
name: Build documentation
66+
command: |
67+
# Set epoch to date of latest tag.
68+
export SOURCE_DATE_EPOCH="$(git log -1 --format=%at $(git describe --abbrev=0))"
69+
mkdir -p logs
70+
make html O="-T -j4 -w /tmp/sphinxerrorswarnings.log"
71+
rm -r build/html/_sources
72+
working_directory: doc
73+
- save_cache:
74+
key: sphinx-env-v1-{{ .BuildNum }}-{{ .Environment.CIRCLE_JOB }}
75+
paths:
76+
- doc/build/doctrees
77+
78+
doc-show-errors-warnings:
79+
steps:
80+
- run:
81+
name: Extract possible build errors and warnings
82+
command: |
83+
(grep "WARNING\|ERROR" /tmp/sphinxerrorswarnings.log ||
84+
echo "No errors or warnings")
85+
# Save logs as an artifact, and convert from absolute paths to
86+
# repository-relative paths.
87+
sed "s~$PWD/~~" /tmp/sphinxerrorswarnings.log > \
88+
doc/logs/sphinx-errors-warnings.log
89+
when: always
90+
- store_artifacts:
91+
path: doc/logs/sphinx-errors-warnings.log
92+
93+
##########################################
94+
# Here is where the real jobs are defined.
95+
#
96+
97+
jobs:
98+
docs-python39:
99+
docker:
100+
- image: cimg/python:3.9
101+
resource_class: large
102+
steps:
103+
- checkout
104+
- check-skip
105+
- merge
106+
107+
- pip-install
108+
109+
- cycler-install
110+
111+
- doc-build
112+
- doc-show-errors-warnings
113+
114+
- store_artifacts:
115+
path: doc/build/html
116+
117+
#########################################
118+
# Defining workflows gets us parallelism.
119+
#
120+
121+
workflows:
122+
version: 2
123+
build:
124+
jobs:
125+
# NOTE: If you rename this job, then you must update the `if` condition
126+
# and `circleci-jobs` option in `.github/workflows/circleci.yml`.
127+
- docs-python39

.circleci/fetch_doc_logs.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""
2+
Download artifacts from CircleCI for a documentation build.
3+
4+
This is run by the :file:`.github/workflows/circleci.yml` workflow in order to
5+
get the warning/deprecation logs that will be posted on commits as checks. Logs
6+
are downloaded from the :file:`docs/logs` artifact path and placed in the
7+
:file:`logs` directory.
8+
9+
Additionally, the artifact count for a build is produced as a workflow output,
10+
by appending to the file specified by :env:`GITHUB_OUTPUT`.
11+
12+
If there are no logs, an "ERROR" message is printed, but this is not fatal, as
13+
the initial 'status' workflow runs when the build has first started, and there
14+
are naturally no artifacts at that point.
15+
16+
This script should be run by passing the CircleCI build URL as its first
17+
argument. In the GitHub Actions workflow, this URL comes from
18+
``github.event.target_url``.
19+
"""
20+
import json
21+
import os
22+
from pathlib import Path
23+
import sys
24+
from urllib.parse import urlparse
25+
from urllib.request import URLError, urlopen
26+
27+
28+
if len(sys.argv) != 2:
29+
print('USAGE: fetch_doc_results.py CircleCI-build-url')
30+
sys.exit(1)
31+
32+
target_url = urlparse(sys.argv[1])
33+
*_, organization, repository, build_id = target_url.path.split('/')
34+
print(f'Fetching artifacts from {organization}/{repository} for {build_id}')
35+
36+
artifact_url = (
37+
f'https://circleci.com/api/v2/project/gh/'
38+
f'{organization}/{repository}/{build_id}/artifacts'
39+
)
40+
print(artifact_url)
41+
try:
42+
with urlopen(artifact_url) as response:
43+
artifacts = json.load(response)
44+
except URLError:
45+
artifacts = {'items': []}
46+
artifact_count = len(artifacts['items'])
47+
print(f'Found {artifact_count} artifacts')
48+
49+
with open(os.environ['GITHUB_OUTPUT'], 'w+') as fd:
50+
fd.write(f'count={artifact_count}\n')
51+
52+
logs = Path('logs')
53+
logs.mkdir(exist_ok=True)
54+
55+
found = False
56+
for item in artifacts['items']:
57+
path = item['path']
58+
if path.startswith('doc/logs/'):
59+
path = Path(path).name
60+
print(f'Downloading {path} from {item["url"]}')
61+
with urlopen(item['url']) as response:
62+
(logs / path).write_bytes(response.read())
63+
found = True
64+
65+
if not found:
66+
print('ERROR: Did not find any artifact logs!')

.github/workflows/circleci.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
name: "CircleCI artifact handling"
3+
on: [status]
4+
jobs:
5+
circleci_artifacts_redirector_job:
6+
if: "${{ github.event.context == 'ci/circleci: docs-python39' }}"
7+
permissions:
8+
statuses: write
9+
runs-on: ubuntu-latest
10+
name: Run CircleCI artifacts redirector
11+
steps:
12+
- name: GitHub Action step
13+
uses: larsoner/circleci-artifacts-redirector-action@master
14+
with:
15+
repo-token: ${{ secrets.GITHUB_TOKEN }}
16+
artifact-path: 0/doc/build/html/index.html
17+
circleci-jobs: docs-python39
18+
job-title: View the built docs
19+
20+
post_warnings_as_review:
21+
if: "${{ github.event.context == 'ci/circleci: docs-python39' }}"
22+
permissions:
23+
contents: read
24+
checks: write
25+
pull-requests: write
26+
runs-on: ubuntu-latest
27+
name: Post warnings/errors as review
28+
steps:
29+
- uses: actions/checkout@v3
30+
31+
- name: Fetch result artifacts
32+
id: fetch-artifacts
33+
run: |
34+
python .circleci/fetch_doc_logs.py "${{ github.event.target_url }}"
35+
36+
- name: Set up reviewdog
37+
if: "${{ steps.fetch-artifacts.outputs.count != 0 }}"
38+
uses: reviewdog/action-setup@v1
39+
with:
40+
reviewdog_version: latest
41+
42+
- name: Post review
43+
if: "${{ steps.fetch-artifacts.outputs.count != 0 }}"
44+
env:
45+
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46+
REVIEWDOG_SKIP_DOGHOUSE: "true"
47+
CI_COMMIT: ${{ github.event.sha }}
48+
CI_REPO_OWNER: ${{ github.event.repository.owner.login }}
49+
CI_REPO_NAME: ${{ github.event.repository.name }}
50+
run: |
51+
# The 'status' event does not contain information in the way that
52+
# reviewdog expects, so we unset those so it reads from the
53+
# environment variables we set above.
54+
unset GITHUB_ACTIONS GITHUB_EVENT_PATH
55+
cat logs/sphinx-deprecations.log | \
56+
reviewdog \
57+
-efm '%f\:%l: %m' \
58+
-name=examples -tee -reporter=github-check -filter-mode=nofilter

.github/workflows/tests.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,13 @@ jobs:
2929
python-version: ${{ matrix.python-version }}
3030
allow-prereleases: true
3131

32-
- name: Install Python dependencies
32+
- name: Upgrade pip
3333
run: |
3434
python -m pip install --upgrade pip
35-
python -m pip install --upgrade pytest pytest-cov pytest-xdist
3635
3736
- name: Install cycler
3837
run: |
39-
python -m pip install --no-deps .
38+
python -m pip install .[tests]
4039
4140
- name: Run pytest
4241
run: |

doc/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@
142142
# Add any paths that contain custom static files (such as style sheets) here,
143143
# relative to this directory. They are copied after the builtin static files,
144144
# so a file named "default.css" will overwrite the builtin "default.css".
145-
html_static_path = ['_static']
145+
# html_static_path = []
146146

147147
# Add any extra paths that contain custom files (such as robots.txt or
148148
# .htaccess) here, relative to this directory. These files are copied

doc/source/index.rst

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ composition and iteration logic.
4040
Base
4141
----
4242

43-
A single entry `Cycler` object can be used to easily
44-
cycle over a single style. To create the `Cycler` use the :py:func:`cycler`
45-
function to link a key/style/kwarg to series of values. The key must be
46-
hashable (as it will eventually be used as the key in a :obj:`dict`).
43+
A single entry `Cycler` object can be used to easily cycle over a single style.
44+
To create the `Cycler` use the :py:func:`cycler` function to link a
45+
key/style/keyword argument to series of values. The key must be hashable (as it
46+
will eventually be used as the key in a :obj:`dict`).
4747

4848
.. ipython:: python
4949
@@ -53,7 +53,7 @@ hashable (as it will eventually be used as the key in a :obj:`dict`).
5353
color_cycle = cycler(color=['r', 'g', 'b'])
5454
color_cycle
5555
56-
The `Cycler` knows it's length and keys:
56+
The `Cycler` knows its length and keys:
5757

5858
.. ipython:: python
5959
@@ -97,7 +97,7 @@ create complex multi-key cycles.
9797
Addition
9898
~~~~~~~~
9999

100-
Equal length `Cycler` s with different keys can be added to get the
100+
Equal length `Cycler`\s with different keys can be added to get the
101101
'inner' product of two cycles
102102

103103
.. ipython:: python
@@ -180,7 +180,7 @@ matrices)
180180
Integer Multiplication
181181
~~~~~~~~~~~~~~~~~~~~~~
182182

183-
`Cycler` s can also be multiplied by integer values to increase the length.
183+
`Cycler`\s can also be multiplied by integer values to increase the length.
184184

185185
.. ipython:: python
186186
@@ -331,8 +331,7 @@ the same style.
331331
Exceptions
332332
----------
333333

334-
335-
A :obj:`ValueError` is raised if unequal length `Cycler` s are added together
334+
A :obj:`ValueError` is raised if unequal length `Cycler`\s are added together
336335

337336
.. ipython:: python
338337
:okexcept:
@@ -401,6 +400,6 @@ However, if you want to do something more complicated:
401400

402401
ax.legend(loc=0)
403402

404-
the plotting logic can quickly become very involved. To address this and allow easy
405-
cycling over arbitrary ``kwargs`` the `Cycler` class, a composable
406-
kwarg iterator, was developed.
403+
the plotting logic can quickly become very involved. To address this and allow
404+
easy cycling over arbitrary ``kwargs`` the `Cycler` class, a composable keyword
405+
argument iterator, was developed.

pyproject.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@ keywords = ["cycle kwargs"]
2525
homepage = "https://matplotlib.org/cycler/"
2626
repository = "https://github.com/matplotlib/cycler"
2727

28+
[project.optional-dependencies]
29+
docs = [
30+
"ipython",
31+
"matplotlib",
32+
"numpydoc",
33+
"sphinx",
34+
]
35+
tests = [
36+
"pytest",
37+
"pytest-cov",
38+
"pytest-xdist",
39+
]
40+
2841
[tool.setuptools]
2942
packages = ["cycler"]
3043

0 commit comments

Comments
 (0)