Skip to content

Commit 70c6205

Browse files
author
Massimiliano Pippi
authored
Add versioning to docs (#711)
* introduce mike * use default 'site' dir for output * install mike and pin material theme * add task to build&publish docs using Mike * publish dev and versioned docs * narrow down env definition * add build script to handle versioning * invoke task, not mike directly
1 parent 2ad7fb3 commit 70c6205

File tree

8 files changed

+219
-16
lines changed

8 files changed

+219
-16
lines changed

.github/workflows/docs.yaml

+11-6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ on:
1414
push:
1515
branches:
1616
- master
17+
# release branches have names like 0.8.x, 0.9.x, ...
18+
- '[0-9]+.[0-9]+.x'
1719
# At this day, GitHub doesn't support YAML anchors, d'oh!
1820
paths:
1921
- 'docs/**'
@@ -69,12 +71,15 @@ jobs:
6971
python3 -m pip install -r ./requirements_docs.txt
7072
7173
- name: Build docs website
74+
# this runs on every PR to ensure the docs build is sane, these docs
75+
# won't be published
76+
if: github.event_name == 'pull_request'
7277
run: task docs:build
7378

74-
- name: Deploy
75-
# publish docs only when PR is merged on master
79+
- name: Publish docs
80+
# determine docs version for the commit pushed and publish accordingly using Mike
7681
if: github.event_name == 'push'
77-
uses: peaceiris/actions-gh-pages@v3
78-
with:
79-
github_token: ${{ secrets.GITHUB_TOKEN }}
80-
publish_dir: ./public
82+
env:
83+
REMOTE: https://x-access-token:${{secrets.GITHUB_TOKEN}}@github.com/${{github.repository}}.git
84+
85+
run: python docs/build.py

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ venv
2222
.DS_Store
2323

2424
# Mkdocs
25+
/site/
2526
/public/
2627
/docsgen/arduino-cli
2728
/docs/rpc/*.md
28-
/docs/commands/*.md
29+
/docs/commands/*.md

Taskfile.yml

+12
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ tasks:
3434
cmds:
3535
- mkdocs build -s
3636

37+
docs:publish:
38+
desc: Use Mike to build and push versioned docs
39+
deps:
40+
- docs:gen:commands
41+
- docs:gen:protobuf
42+
cmds:
43+
- mike deploy -r {{.DOCS_REMOTE}} {{.DOCS_VERSION}} {{.DOCS_ALIAS}}
44+
3745
docs:serve:
3846
desc: Run documentation website locally
3947
deps:
@@ -126,3 +134,7 @@ vars:
126134
GOLINTBIN:
127135
sh: go list -f {{"{{"}}".Target{{"}}"}}" golang.org/x/lint/golint
128136
GOLINTFLAGS: "-min_confidence 0.8 -set_exit_status"
137+
# docs versioning
138+
DOCS_VERSION: dev
139+
DOCS_ALIAS: ""
140+
DOCS_REMOTE: "origin"

docs/build.py

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# This file is part of arduino-cli.
2+
3+
# Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
4+
5+
# This software is released under the GNU General Public License version 3,
6+
# which covers the main part of arduino-cli.
7+
# The terms of this license can be found at:
8+
# https://www.gnu.org/licenses/gpl-3.0.en.html
9+
10+
# You can be released from the requirements of the above licenses by purchasing
11+
# a commercial license. Buying such a license is mandatory if you want to
12+
# modify or otherwise use the software for commercial activities involving the
13+
# Arduino software without disclosing the source code of your own applications.
14+
# To purchase a commercial license, send an email to license@arduino.cc.
15+
import os
16+
import sys
17+
import re
18+
import unittest
19+
import subprocess
20+
21+
import semver
22+
from git import Repo
23+
24+
25+
class TestScript(unittest.TestCase):
26+
def test_get_docs_version(self):
27+
ver, alias = get_docs_version("master", [])
28+
self.assertEqual(ver, "dev")
29+
self.assertEqual(alias, "")
30+
31+
release_names = ["1.4.x", "0.13.x"]
32+
ver, alias = get_docs_version("0.13.x", release_names)
33+
self.assertEqual(ver, "0.13")
34+
self.assertEqual(alias, "")
35+
ver, alias = get_docs_version("1.4.x", release_names)
36+
self.assertEqual(ver, "1.4")
37+
self.assertEqual(alias, "latest")
38+
39+
ver, alias = get_docs_version("0.1.x", [])
40+
self.assertIsNone(ver)
41+
self.assertIsNone(alias)
42+
43+
44+
def get_docs_version(ref_name, release_branches):
45+
if ref_name == "master":
46+
return "dev", ""
47+
48+
if ref_name in release_branches:
49+
# if version is latest, add an alias
50+
alias = "latest" if ref_name == release_branches[0] else ""
51+
# strip `.x` suffix from the branch name to get the version: 0.3.x -> 0.3
52+
return ref_name[:-2], alias
53+
54+
return None, None
55+
56+
57+
def get_rel_branch_names(blist):
58+
"""Get the names of the release branches, sorted from newest to older.
59+
60+
Only process remote refs so we're sure to get all of them and clean up the
61+
name so that we have a list of strings like 0.6.x, 0.7.x, ...
62+
"""
63+
pattern = re.compile(r"origin/(\d+\.\d+\.x)")
64+
names = []
65+
for b in blist:
66+
res = pattern.search(b.name)
67+
if res is not None:
68+
names.append(res.group(1))
69+
70+
# Since sorting is stable, first sort by major...
71+
names = sorted(names, key=lambda x: int(x.split(".")[0]), reverse=True)
72+
# ...then by minor
73+
return sorted(names, key=lambda x: int(x.split(".")[1]), reverse=True)
74+
75+
76+
def main(repo_dir):
77+
# Git remote must be set to publish docs
78+
remote = os.environ.get("REMOTE")
79+
if not remote:
80+
print("REMOTE env var must be set to publish, running dry mode")
81+
82+
# Get current repo
83+
repo = Repo(repo_dir)
84+
85+
# Get the list of release branch names
86+
rel_br_names = get_rel_branch_names(repo.refs)
87+
88+
# Deduce docs version from current branch. Use the 'latest' alias if
89+
# version is the most recent
90+
docs_version, alias = get_docs_version(repo.active_branch.name, rel_br_names)
91+
if docs_version is None:
92+
print(
93+
f"Can't get version from current branch '{repo.active_branch}', skip docs generation"
94+
)
95+
return 0
96+
97+
args = [
98+
"task",
99+
"docs:publish",
100+
f"DOCS_REMOTE={remote}",
101+
f"DOCS_VERSION={docs_version}",
102+
f"DOCS_ALIAS={alias}",
103+
]
104+
if remote:
105+
subprocess.run(args, shell=True, check=True, cwd=repo_dir)
106+
else:
107+
print(" ".join(args))
108+
109+
return 0
110+
111+
112+
# Usage:
113+
#
114+
# To run the tests:
115+
# $python build.py test
116+
#
117+
# To run the script (must be run from within the repo tree):
118+
# $python build.py
119+
#
120+
if __name__ == "__main__":
121+
if len(sys.argv) > 1 and sys.argv[1] == "test":
122+
unittest.main(argv=[""], exit=False)
123+
sys.exit(0)
124+
125+
here = os.path.dirname(os.path.realpath(__file__))
126+
sys.exit(main(os.path.join(here, "..")))

docs/css/version-select.css

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@media only screen and (max-width:76.1875em) {
2+
#version-selector {
3+
padding: .6rem .8rem;
4+
}
5+
}

docs/js/version-select.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
window.addEventListener("DOMContentLoaded", function() {
2+
// This is a bit hacky. Figure out the base URL from a known CSS file the
3+
// template refers to...
4+
var ex = new RegExp("/?assets/fonts/material-icons.css$");
5+
var sheet = document.querySelector('link[href$="material-icons.css"]');
6+
7+
var REL_BASE_URL = sheet.getAttribute("href").replace(ex, "");
8+
var ABS_BASE_URL = sheet.href.replace(ex, "");
9+
var CURRENT_VERSION = ABS_BASE_URL.split("/").pop();
10+
11+
function makeSelect(options, selected) {
12+
var select = document.createElement("select");
13+
select.classList.add("form-control");
14+
15+
options.forEach(function(i) {
16+
var option = new Option(i.text, i.value, undefined,
17+
i.value === selected);
18+
select.add(option);
19+
});
20+
21+
return select;
22+
}
23+
24+
var xhr = new XMLHttpRequest();
25+
xhr.open("GET", REL_BASE_URL + "/../versions.json");
26+
xhr.onload = function() {
27+
var versions = JSON.parse(this.responseText);
28+
29+
var realVersion = versions.find(function(i) {
30+
return i.version === CURRENT_VERSION ||
31+
i.aliases.includes(CURRENT_VERSION);
32+
}).version;
33+
34+
var select = makeSelect(versions.map(function(i) {
35+
return {text: i.title, value: i.version};
36+
}), realVersion);
37+
select.addEventListener("change", function(event) {
38+
window.location.href = REL_BASE_URL + "/../" + this.value;
39+
});
40+
41+
var container = document.createElement("div");
42+
container.id = "version-selector";
43+
container.className = "md-nav__item";
44+
container.appendChild(select);
45+
46+
var sidebar = document.querySelector(".md-nav--primary > .md-nav__list");
47+
sidebar.parentNode.insertBefore(container, sidebar);
48+
};
49+
xhr.send();
50+
});

mkdocs.yml

+9-7
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,10 @@ site_url: https://arduino.github.io/arduino-cli/
66
# Repository
77
repo_name: arduino/arduino-cli
88
repo_url: https://github.com/arduino/arduino-cli
9-
edit_uri: ""
9+
edit_uri: ''
1010

1111
# Copyright
12-
copyright: 'Copyright 2020 ARDUINO SA (http://www.arduino.cc/)'
13-
14-
# Configuration
15-
site_dir: public
16-
edit_uri: ""
12+
copyright: Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
1713

1814
# Theme
1915
theme:
@@ -57,7 +53,7 @@ markdown_extensions:
5753

5854
# Navigation
5955
nav:
60-
- Documentation Home: 'index.md'
56+
- Documentation Home: index.md
6157
- installation.md
6258
- getting-started.md
6359
- CONTRIBUTING.md
@@ -108,3 +104,9 @@ nav:
108104
- library-specification.md
109105
- platform-specification.md
110106
- package_index.json specification: package_index_json-specification.md
107+
108+
extra_css:
109+
- css/version-select.css
110+
111+
extra_javascript:
112+
- js/version-select.js

requirements_docs.txt

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1-
mkdocs
2-
mkdocs-material
1+
mkdocs<1.2
2+
mkdocs-material<5
3+
mike
4+
gitpython

0 commit comments

Comments
 (0)