This is an automated email from the ASF dual-hosted git repository.
shahar1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 602e150aace Allow building docs with a local sphinx_airflow_theme
checkout (#75001)
602e150aace is described below
commit 602e150aace0ed5d4e22cd4df0cf49949bcd669d
Author: Shahar Epstein <[email protected]>
AuthorDate: Sat Oct 24 08:12:46 2026 +0300
Allow building docs with a local sphinx_airflow_theme checkout (#75001)
---
contributing-docs/11_documentation_building.rst | 11 +++
dev/breeze/doc/images/output_build-docs.svg | 52 ++++++++----
dev/breeze/doc/images/output_build-docs.txt | 2 +-
.../airflow_breeze/commands/developer_commands.py | 27 ++++++
.../commands/developer_commands_config.py | 1 +
.../src/airflow_breeze/params/shell_params.py | 5 ++
dev/breeze/src/airflow_breeze/utils/path_utils.py | 3 +
dev/breeze/tests/test_developer_commands.py | 96 +++++++++++++++++-----
dev/breeze/tests/test_shell_params.py | 14 ++++
docs/README.md | 4 +-
.../docker-compose/mount-sphinx-airflow-theme.yml} | 24 +++---
scripts/in_container/run_docs_build.sh | 6 ++
12 files changed, 189 insertions(+), 56 deletions(-)
diff --git a/contributing-docs/11_documentation_building.rst
b/contributing-docs/11_documentation_building.rst
index 1297b1048b8..b8e24ccbc4f 100644
--- a/contributing-docs/11_documentation_building.rst
+++ b/contributing-docs/11_documentation_building.rst
@@ -241,6 +241,17 @@ For example:
Will build ``fab`` provider documentation and clean build artifacts before.
+To try a change to the HTML theme (``sphinx_airflow_theme`` from the
+`airflow-site <https://github.com/apache/airflow-site>`_ repository) on real
documentation, build with the
+theme folder of an ``airflow-site`` checkout instead of the published wheel:
+
+.. code-block:: bash
+
+ breeze build-docs apache-airflow --sphinx-airflow-theme-path
../airflow-site/sphinx_airflow_theme
+
+Run ``./site.sh build-landing-pages && ./site.sh prepare-theme`` in
``airflow-site`` first to generate the
+theme's CSS/JS. Breeze mounts the folder read-only and installs the theme in
the container before the build.
+
Inventory cache handling
........................
diff --git a/dev/breeze/doc/images/output_build-docs.svg
b/dev/breeze/doc/images/output_build-docs.svg
index 45d78b15e35..10d0e93d55f 100644
--- a/dev/breeze/doc/images/output_build-docs.svg
+++ b/dev/breeze/doc/images/output_build-docs.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 1514.0"
xmlns="http://www.w3.org/2000/svg">
+<svg class="rich-terminal" viewBox="0 0 1482 1611.6"
xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@@ -43,7 +43,7 @@
<defs>
<clipPath id="breeze-build-docs-clip-terminal">
- <rect x="0" y="0" width="1463.0" height="1463.0" />
+ <rect x="0" y="0" width="1463.0" height="1560.6" />
</clipPath>
<clipPath id="breeze-build-docs-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -222,9 +222,21 @@
<clipPath id="breeze-build-docs-line-58">
<rect x="0" y="1416.7" width="1464" height="24.65"/>
</clipPath>
+<clipPath id="breeze-build-docs-line-59">
+ <rect x="0" y="1441.1" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-build-docs-line-60">
+ <rect x="0" y="1465.5" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-build-docs-line-61">
+ <rect x="0" y="1489.9" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-build-docs-line-62">
+ <rect x="0" y="1514.3" width="1464" height="24.65"/>
+ </clipPath>
</defs>
- <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1"
x="1" y="1" width="1480" height="1512" rx="8"/><text
class="breeze-build-docs-title" fill="#c5c8c6" text-anchor="middle" x="740"
y="27">Command: build-docs</text>
+ <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1"
x="1" y="1" width="1480" height="1609.6" rx="8"/><text
class="breeze-build-docs-title" fill="#c5c8c6" text-anchor="middle" x="740"
y="27">Command: build-docs</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -279,21 +291,25 @@
</text><text class="breeze-build-docs-r5" x="0" y="1044.8" textLength="12.2"
clip-path="url(#breeze-build-docs-line-42)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1044.8" textLength="353.8"
clip-path="url(#breeze-build-docs-line-42)">--include-removed-providers  </text><text
class="breeze-build-docs-r1" x="402.6" y="1044.8" textLength="561.2"
clip-path="url(#breeze-build-docs-line-42)">Whether to include providers that are removed.</te
[...]
</text><text class="breeze-build-docs-r5" x="0" y="1069.2" textLength="1464"
clip-path="url(#breeze-build-docs-line-43)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-build-docs-r1" x="1464" y="1069.2" textLength="12.2"
clip-path="url(#breeze-build-docs-line-43)">
</text><text class="breeze-build-docs-r5" x="0" y="1093.6" textLength="24.4"
clip-path="url(#breeze-build-docs-line-44)">╭─</text><text
class="breeze-build-docs-r5" x="24.4" y="1093.6" textLength="170.8"
clip-path="url(#breeze-build-docs-line-44)"> Misc options </text><text
class="breeze-build-docs-r5" x="195.2" y="1093.6" textLength="1244.4"
clip-path="url(#breeze-build-docs-line-44)">────────────────────────────────────────────────────────────────────────────────────────
[...]
-</text><text class="breeze-build-docs-r5" x="0" y="1118" textLength="12.2"
clip-path="url(#breeze-build-docs-line-45)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1118" textLength="244"
clip-path="url(#breeze-build-docs-line-45)">--include-commits   </text><text
class="breeze-build-docs-r1" x="341.6" y="1118" textLength="451.4"
clip-path="url(#breeze-build-docs-line-45)">Include commits in the documentation.</text><text
class="breeze-build-do [...]
-</text><text class="breeze-build-docs-r5" x="0" y="1142.4" textLength="12.2"
clip-path="url(#breeze-build-docs-line-46)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1142.4" textLength="244"
clip-path="url(#breeze-build-docs-line-46)">--github-repository </text><text
class="breeze-build-docs-r7" x="292.8" y="1142.4" textLength="24.4"
clip-path="url(#breeze-build-docs-line-46)">-g</text><text
class="breeze-build-docs-r1" x="341.6" y="1142.4" textLength="597.8"
clip-path="ur [...]
-</text><text class="breeze-build-docs-r5" x="0" y="1166.8" textLength="12.2"
clip-path="url(#breeze-build-docs-line-47)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1166.8" textLength="244"
clip-path="url(#breeze-build-docs-line-47)">--builder           </text><text
class="breeze-build-docs-r1" x="341.6" y="1166.8" textLength="768.6"
clip-path="url(#breeze-build-docs-line-47)">Buildx builder used to per
[...]
-</text><text class="breeze-build-docs-r5" x="0" y="1191.2" textLength="12.2"
clip-path="url(#breeze-build-docs-line-48)">│</text><text
class="breeze-build-docs-r6" x="341.6" y="1191.2" textLength="73.2"
clip-path="url(#breeze-build-docs-line-48)">(TEXT)</text><text
class="breeze-build-docs-r5" x="1451.8" y="1191.2" textLength="12.2"
clip-path="url(#breeze-build-docs-line-48)">│</text><text
class="breeze-build-docs-r1" x="1464" y="1191.2" textLength="12.2"
clip-path="url(#breeze-build-doc [...]
-</text><text class="breeze-build-docs-r5" x="0" y="1215.6" textLength="12.2"
clip-path="url(#breeze-build-docs-line-49)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1215.6" textLength="244"
clip-path="url(#breeze-build-docs-line-49)">--distributions-list</text><text
class="breeze-build-docs-r1" x="341.6" y="1215.6" textLength="1098"
clip-path="url(#breeze-build-docs-line-49)">Optional, contains space separated list of package ids that
[...]
-</text><text class="breeze-build-docs-r5" x="0" y="1240" textLength="12.2"
clip-path="url(#breeze-build-docs-line-50)">│</text><text
class="breeze-build-docs-r1" x="341.6" y="1240" textLength="1098"
clip-path="url(#breeze-build-docs-line-50)">documentation building, and document publishing. It is an easier alternative to adding    </text><text
class="breeze-build-docs-r5" x="1451.8" y="1240" textLength="12.2" clip [...]
-</text><text class="breeze-build-docs-r5" x="0" y="1264.4" textLength="12.2"
clip-path="url(#breeze-build-docs-line-51)">│</text><text
class="breeze-build-docs-r1" x="341.6" y="1264.4" textLength="1098"
clip-path="url(#breeze-build-docs-line-51)">individual packages as arguments to every command. This overrides the packages passed as  </text><text
class="breeze-build-docs-r5" x="1451.8" y="1264.4" textLength="12.2" cli [...]
-</text><text class="breeze-build-docs-r5" x="0" y="1288.8" textLength="12.2"
clip-path="url(#breeze-build-docs-line-52)">│</text><text
class="breeze-build-docs-r1" x="341.6" y="1288.8" textLength="134.2"
clip-path="url(#breeze-build-docs-line-52)">arguments. </text><text
class="breeze-build-docs-r6" x="475.8" y="1288.8" textLength="73.2"
clip-path="url(#breeze-build-docs-line-52)">(TEXT)</text><text
class="breeze-build-docs-r5" x="1451.8" y="1288.8" textLength="12.2"
clip-path="url( [...]
-</text><text class="breeze-build-docs-r5" x="0" y="1313.2" textLength="1464"
clip-path="url(#breeze-build-docs-line-53)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-build-docs-r1" x="1464" y="1313.2" textLength="12.2"
clip-path="url(#breeze-build-docs-line-53)">
-</text><text class="breeze-build-docs-r5" x="0" y="1337.6" textLength="24.4"
clip-path="url(#breeze-build-docs-line-54)">╭─</text><text
class="breeze-build-docs-r5" x="24.4" y="1337.6" textLength="195.2"
clip-path="url(#breeze-build-docs-line-54)"> Common options </text><text
class="breeze-build-docs-r5" x="219.6" y="1337.6" textLength="1220"
clip-path="url(#breeze-build-docs-line-54)">────────────────────────────────────────────────────────────────────────────────────────
[...]
-</text><text class="breeze-build-docs-r5" x="0" y="1362" textLength="12.2"
clip-path="url(#breeze-build-docs-line-55)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1362" textLength="109.8"
clip-path="url(#breeze-build-docs-line-55)">--dry-run</text><text
class="breeze-build-docs-r7" x="158.6" y="1362" textLength="24.4"
clip-path="url(#breeze-build-docs-line-55)">-D</text><text
class="breeze-build-docs-r1" x="207.4" y="1362" textLength="719.8"
clip-path="url(#breeze-build-docs-l [...]
-</text><text class="breeze-build-docs-r5" x="0" y="1386.4" textLength="12.2"
clip-path="url(#breeze-build-docs-line-56)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1386.4" textLength="109.8"
clip-path="url(#breeze-build-docs-line-56)">--verbose</text><text
class="breeze-build-docs-r7" x="158.6" y="1386.4" textLength="24.4"
clip-path="url(#breeze-build-docs-line-56)">-v</text><text
class="breeze-build-docs-r1" x="207.4" y="1386.4" textLength="585.6"
clip-path="url(#breeze-buil [...]
-</text><text class="breeze-build-docs-r5" x="0" y="1410.8" textLength="12.2"
clip-path="url(#breeze-build-docs-line-57)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1410.8" textLength="109.8"
clip-path="url(#breeze-build-docs-line-57)">--answer </text><text
class="breeze-build-docs-r7" x="158.6" y="1410.8" textLength="24.4"
clip-path="url(#breeze-build-docs-line-57)">-a</text><text
class="breeze-build-docs-r1" x="207.4" y="1410.8" textLength="329.4"
clip-path="url(#breeze [...]
-</text><text class="breeze-build-docs-r5" x="0" y="1435.2" textLength="12.2"
clip-path="url(#breeze-build-docs-line-58)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1435.2" textLength="109.8"
clip-path="url(#breeze-build-docs-line-58)">--help   </text><text
class="breeze-build-docs-r7" x="158.6" y="1435.2" textLength="24.4"
clip-path="url(#breeze-build-docs-line-58)">-h</text><text
class="breeze-build-docs-r1" x="207.4" y="1435.2" textLength="329.4"
clip-path="u [...]
-</text><text class="breeze-build-docs-r5" x="0" y="1459.6" textLength="1464"
clip-path="url(#breeze-build-docs-line-59)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-build-docs-r1" x="1464" y="1459.6" textLength="12.2"
clip-path="url(#breeze-build-docs-line-59)">
+</text><text class="breeze-build-docs-r5" x="0" y="1118" textLength="12.2"
clip-path="url(#breeze-build-docs-line-45)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1118" textLength="329.4"
clip-path="url(#breeze-build-docs-line-45)">--include-commits          </text><text
class="breeze-build-docs-r1" x="427" y="1118" textLength="451.4"
clip-path="url(#breeze-build-docs-line-45)">Include commits in the documen
[...]
+</text><text class="breeze-build-docs-r5" x="0" y="1142.4" textLength="12.2"
clip-path="url(#breeze-build-docs-line-46)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1142.4" textLength="329.4"
clip-path="url(#breeze-build-docs-line-46)">--github-repository        </text><text
class="breeze-build-docs-r7" x="378.2" y="1142.4" textLength="24.4"
clip-path="url(#breeze-build-docs-line-46)">-g</text><text
class="breeze-build-docs-r1" x="427" y [...]
+</text><text class="breeze-build-docs-r5" x="0" y="1166.8" textLength="12.2"
clip-path="url(#breeze-build-docs-line-47)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1166.8" textLength="329.4"
clip-path="url(#breeze-build-docs-line-47)">--builder                  </text><text
class="breeze-build-docs-r1" x="427" y="1166.8" textLength="768.6"
clip-path="url(#breeze-build-docs-line-47)">Buil [...]
+</text><text class="breeze-build-docs-r5" x="0" y="1191.2" textLength="12.2"
clip-path="url(#breeze-build-docs-line-48)">│</text><text
class="breeze-build-docs-r5" x="427" y="1191.2" textLength="134.2"
clip-path="url(#breeze-build-docs-line-48)">autodetect]</text><text
class="breeze-build-docs-r6" x="573.4" y="1191.2" textLength="73.2"
clip-path="url(#breeze-build-docs-line-48)">(TEXT)</text><text
class="breeze-build-docs-r5" x="1451.8" y="1191.2" textLength="12.2"
clip-path="url(#breeze [...]
+</text><text class="breeze-build-docs-r5" x="0" y="1215.6" textLength="12.2"
clip-path="url(#breeze-build-docs-line-49)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1215.6" textLength="329.4"
clip-path="url(#breeze-build-docs-line-49)">--distributions-list       </text><text
class="breeze-build-docs-r1" x="427" y="1215.6" textLength="1012.6"
clip-path="url(#breeze-build-docs-line-49)">Optional, contains space separated lis
[...]
+</text><text class="breeze-build-docs-r5" x="0" y="1240" textLength="12.2"
clip-path="url(#breeze-build-docs-line-50)">│</text><text
class="breeze-build-docs-r1" x="427" y="1240" textLength="1012.6"
clip-path="url(#breeze-build-docs-line-50)">documentation building, and document publishing. It is an easier alternative to    </text><text
class="breeze-build-docs-r5" x="1451.8" y="1240" textLength="12.2"
clip-path="url(# [...]
+</text><text class="breeze-build-docs-r5" x="0" y="1264.4" textLength="12.2"
clip-path="url(#breeze-build-docs-line-51)">│</text><text
class="breeze-build-docs-r1" x="427" y="1264.4" textLength="1012.6"
clip-path="url(#breeze-build-docs-line-51)">adding individual packages as arguments to every command. This overrides the       </text><text
class="breeze-build-docs-r5" x="1451.8" y="1264.4" textLength="1 [...]
+</text><text class="breeze-build-docs-r5" x="0" y="1288.8" textLength="12.2"
clip-path="url(#breeze-build-docs-line-52)">│</text><text
class="breeze-build-docs-r1" x="427" y="1288.8" textLength="366"
clip-path="url(#breeze-build-docs-line-52)">packages passed as arguments. </text><text
class="breeze-build-docs-r6" x="793" y="1288.8" textLength="73.2"
clip-path="url(#breeze-build-docs-line-52)">(TEXT)</text><text
class="breeze-build-docs-r5" x="1451.8" y="1288.8" textL [...]
+</text><text class="breeze-build-docs-r5" x="0" y="1313.2" textLength="12.2"
clip-path="url(#breeze-build-docs-line-53)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1313.2" textLength="329.4"
clip-path="url(#breeze-build-docs-line-53)">--sphinx-airflow-theme-path</text><text
class="breeze-build-docs-r1" x="427" y="1313.2" textLength="1012.6"
clip-path="url(#breeze-build-docs-line-53)">Build the docs with the `sphinx_airflow_theme` sub-folder 
[...]
+</text><text class="breeze-build-docs-r5" x="0" y="1337.6" textLength="12.2"
clip-path="url(#breeze-build-docs-line-54)">│</text><text
class="breeze-build-docs-r1" x="427" y="1337.6" textLength="1012.6"
clip-path="url(#breeze-build-docs-line-54)">checkout (e.g. `../airflow-site/sphinx_airflow_theme`, not the repository root),   </text><text
class="breeze-build-docs-r5" x="1451.8" y="1337.6" textLength="12.2"
clip-path="url(#breeze-build-docs-l [...]
+</text><text class="breeze-build-docs-r5" x="0" y="1362" textLength="12.2"
clip-path="url(#breeze-build-docs-line-55)">│</text><text
class="breeze-build-docs-r1" x="427" y="1362" textLength="1012.6"
clip-path="url(#breeze-build-docs-line-55)">after `./site.sh build-landing-pages && ./site.sh prepare-theme`, instead of the   </text><text
class="breeze-build-docs-r5" x="1451.8" y="1362" textLength="12.2"
clip-path="url(#breeze- [...]
+</text><text class="breeze-build-docs-r5" x="0" y="1386.4" textLength="12.2"
clip-path="url(#breeze-build-docs-line-56)">│</text><text
class="breeze-build-docs-r1" x="427" y="1386.4" textLength="207.4"
clip-path="url(#breeze-build-docs-line-56)">published theme. </text><text
class="breeze-build-docs-r6" x="634.4" y="1386.4" textLength="134.2"
clip-path="url(#breeze-build-docs-line-56)">(DIRECTORY)</text><text
class="breeze-build-docs-r5" x="1451.8" y="1386.4" textLength="12.2" [...]
+</text><text class="breeze-build-docs-r5" x="0" y="1410.8" textLength="1464"
clip-path="url(#breeze-build-docs-line-57)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-build-docs-r1" x="1464" y="1410.8" textLength="12.2"
clip-path="url(#breeze-build-docs-line-57)">
+</text><text class="breeze-build-docs-r5" x="0" y="1435.2" textLength="24.4"
clip-path="url(#breeze-build-docs-line-58)">╭─</text><text
class="breeze-build-docs-r5" x="24.4" y="1435.2" textLength="195.2"
clip-path="url(#breeze-build-docs-line-58)"> Common options </text><text
class="breeze-build-docs-r5" x="219.6" y="1435.2" textLength="1220"
clip-path="url(#breeze-build-docs-line-58)">────────────────────────────────────────────────────────────────────────────────────────
[...]
+</text><text class="breeze-build-docs-r5" x="0" y="1459.6" textLength="12.2"
clip-path="url(#breeze-build-docs-line-59)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1459.6" textLength="109.8"
clip-path="url(#breeze-build-docs-line-59)">--dry-run</text><text
class="breeze-build-docs-r7" x="158.6" y="1459.6" textLength="24.4"
clip-path="url(#breeze-build-docs-line-59)">-D</text><text
class="breeze-build-docs-r1" x="207.4" y="1459.6" textLength="719.8"
clip-path="url(#breeze-buil [...]
+</text><text class="breeze-build-docs-r5" x="0" y="1484" textLength="12.2"
clip-path="url(#breeze-build-docs-line-60)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1484" textLength="109.8"
clip-path="url(#breeze-build-docs-line-60)">--verbose</text><text
class="breeze-build-docs-r7" x="158.6" y="1484" textLength="24.4"
clip-path="url(#breeze-build-docs-line-60)">-v</text><text
class="breeze-build-docs-r1" x="207.4" y="1484" textLength="585.6"
clip-path="url(#breeze-build-docs-l [...]
+</text><text class="breeze-build-docs-r5" x="0" y="1508.4" textLength="12.2"
clip-path="url(#breeze-build-docs-line-61)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1508.4" textLength="109.8"
clip-path="url(#breeze-build-docs-line-61)">--answer </text><text
class="breeze-build-docs-r7" x="158.6" y="1508.4" textLength="24.4"
clip-path="url(#breeze-build-docs-line-61)">-a</text><text
class="breeze-build-docs-r1" x="207.4" y="1508.4" textLength="329.4"
clip-path="url(#breeze [...]
+</text><text class="breeze-build-docs-r5" x="0" y="1532.8" textLength="12.2"
clip-path="url(#breeze-build-docs-line-62)">│</text><text
class="breeze-build-docs-r4" x="24.4" y="1532.8" textLength="109.8"
clip-path="url(#breeze-build-docs-line-62)">--help   </text><text
class="breeze-build-docs-r7" x="158.6" y="1532.8" textLength="24.4"
clip-path="url(#breeze-build-docs-line-62)">-h</text><text
class="breeze-build-docs-r1" x="207.4" y="1532.8" textLength="329.4"
clip-path="u [...]
+</text><text class="breeze-build-docs-r5" x="0" y="1557.2" textLength="1464"
clip-path="url(#breeze-build-docs-line-63)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-build-docs-r1" x="1464" y="1557.2" textLength="12.2"
clip-path="url(#breeze-build-docs-line-63)">
</text>
</g>
</g>
diff --git a/dev/breeze/doc/images/output_build-docs.txt
b/dev/breeze/doc/images/output_build-docs.txt
index 5105c9d092b..fca340f3bd2 100644
--- a/dev/breeze/doc/images/output_build-docs.txt
+++ b/dev/breeze/doc/images/output_build-docs.txt
@@ -1 +1 @@
-585c88f391fe92933f3d30f4bff0be5b
+64d40b641cf8161bf9acf27704df91da
diff --git a/dev/breeze/src/airflow_breeze/commands/developer_commands.py
b/dev/breeze/src/airflow_breeze/commands/developer_commands.py
index e7ba6eefbd5..a320cb633ab 100644
--- a/dev/breeze/src/airflow_breeze/commands/developer_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/developer_commands.py
@@ -901,6 +901,7 @@ def _build_python_docs(
package_filter: tuple[str, ...],
distributions_list: str,
doc_packages: tuple[str, ...],
+ sphinx_airflow_theme_path: str,
):
# Docs are always built on the default Python. The Sphinx configuration
mocks third-party
# modules, and what that mocking does depends on the interpreter - on 3.12
functools copies
@@ -964,6 +965,7 @@ def _build_python_docs(
github_repository=github_repository,
python=DEFAULT_PYTHON_MAJOR_MINOR_VERSION,
mount_sources=MOUNT_ALL,
+ sphinx_airflow_theme_path=sphinx_airflow_theme_path,
)
result = execute_command_in_shell(shell_params,
project_name="breeze-docs", command=cmd)
fix_ownership_using_docker()
@@ -976,6 +978,16 @@ def _build_python_docs(
return sphinx_returncode
+def _validate_sphinx_airflow_theme_path(ctx: click.Context, param:
click.Parameter, value: str | None):
+ if value and not Path(value, "sphinx_airflow_theme/static/_gen").is_dir():
+ raise click.BadParameter(
+ f"{value} has no sphinx_airflow_theme/static/_gen. It must be the
`sphinx_airflow_theme` "
+ "sub-folder of an airflow-site checkout (not the repository root),
"
+ "after `./site.sh build-landing-pages && ./site.sh prepare-theme`."
+ )
+ return value
+
+
@main.command(name="build-docs")
@option_builder
@click.option(
@@ -1035,6 +1047,14 @@ def _build_python_docs(
is_flag=True,
help="Only build SDK docs. Requires at least one --sdk value to be
useful.",
)
[email protected](
+ "--sphinx-airflow-theme-path",
+ type=click.Path(exists=True, file_okay=False, resolve_path=True),
+ callback=_validate_sphinx_airflow_theme_path,
+ help="Build the docs with the `sphinx_airflow_theme` sub-folder of an
airflow-site checkout "
+ "(e.g. `../airflow-site/sphinx_airflow_theme`, not the repository root),
after "
+ "`./site.sh build-landing-pages && ./site.sh prepare-theme`, instead of
the published theme.",
+)
@option_verbose
@option_answer
@argument_doc_packages
@@ -1053,11 +1073,17 @@ def build_docs(
distributions_list: str,
sdk: tuple[str, ...],
sdk_docs_only: bool,
+ sphinx_airflow_theme_path: str | None,
doc_packages: tuple[str, ...],
):
"""
Build documents.
"""
+ if sdk_docs_only and sphinx_airflow_theme_path:
+ raise click.UsageError(
+ "--sphinx-airflow-theme-path cannot be used with --sdk-docs-only: "
+ "the SDK docs do not use the Sphinx theme."
+ )
perform_environment_checks()
fix_ownership_using_docker()
cleanup_python_generated_files()
@@ -1080,6 +1106,7 @@ def build_docs(
package_filter=package_filter,
distributions_list=distributions_list,
doc_packages=doc_packages,
+ sphinx_airflow_theme_path=sphinx_airflow_theme_path or "",
)
# Every requested SDK is built even if an earlier one failed, so a single
run reports
# all the broken toolchains rather than only the first.
diff --git
a/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py
b/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py
index 97e85a7d312..16ddb36c357 100644
--- a/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py
@@ -380,6 +380,7 @@ DEVELOPER_PARAMETERS: dict[str, list[dict[str, str |
list[str]]]] = {
"--github-repository",
"--builder",
"--distributions-list",
+ "--sphinx-airflow-theme-path",
],
},
],
diff --git a/dev/breeze/src/airflow_breeze/params/shell_params.py
b/dev/breeze/src/airflow_breeze/params/shell_params.py
index a692289494a..6d117084825 100644
--- a/dev/breeze/src/airflow_breeze/params/shell_params.py
+++ b/dev/breeze/src/airflow_breeze/params/shell_params.py
@@ -99,6 +99,7 @@ from airflow_breeze.utils.path_utils import (
SCRIPTS_CI_DOCKER_COMPOSE_INTEGRATION_KERBEROS_PATH,
SCRIPTS_CI_DOCKER_COMPOSE_LOCAL_ALL_SOURCES_PATH,
SCRIPTS_CI_DOCKER_COMPOSE_LOCAL_YAML_PATH,
+ SCRIPTS_CI_DOCKER_COMPOSE_MOUNT_SPHINX_AIRFLOW_THEME_PATH,
SCRIPTS_CI_DOCKER_COMPOSE_MOUNT_UI_DIST_PATH,
SCRIPTS_CI_DOCKER_COMPOSE_MOUNT_UV_LOCK_PATH,
SCRIPTS_CI_DOCKER_COMPOSE_MYPY_PATH,
@@ -241,6 +242,7 @@ class ShellParams:
skip_image_upgrade_check: bool = False
skip_provider_dependencies_check: bool = False
skip_ssh_setup: bool = os.environ.get("SKIP_SSH_SETUP", "false") == "true"
+ sphinx_airflow_theme_path: str = ""
standalone_dag_processor: bool = False
start_airflow: bool = False
test_type: str | None = None
@@ -442,6 +444,8 @@ class ShellParams:
compose_file_list.append(SCRIPTS_CI_DOCKER_COMPOSE_REMOVE_SOURCES_PATH)
if self.mount_ui_dist:
compose_file_list.append(SCRIPTS_CI_DOCKER_COMPOSE_MOUNT_UI_DIST_PATH)
+ if self.sphinx_airflow_theme_path:
+
compose_file_list.append(SCRIPTS_CI_DOCKER_COMPOSE_MOUNT_SPHINX_AIRFLOW_THEME_PATH)
if self.forward_credentials:
compose_file_list.append(SCRIPTS_CI_DOCKER_COMPOSE_FORWARD_CREDENTIALS_PATH)
if self.include_mypy_volume:
@@ -755,6 +759,7 @@ services:
_set_var(_env, "SQLITE_URL", self.sqlite_url)
_set_var(_env, "SSH_PORT", None, SSH_PORT)
_set_var(_env, "SKIP_ASSETS_COMPILATION", self.skip_assets_compilation)
+ _set_var(_env, "SPHINX_AIRFLOW_THEME_PATH",
self.sphinx_airflow_theme_path)
_set_var(_env, "STANDALONE_DAG_PROCESSOR",
self.standalone_dag_processor)
_set_var(_env, "START_AIRFLOW", self.start_airflow)
_set_var(_env, "TERMINAL_MULTIPLEXER", self.terminal_multiplexer)
diff --git a/dev/breeze/src/airflow_breeze/utils/path_utils.py
b/dev/breeze/src/airflow_breeze/utils/path_utils.py
index 3ae47757459..34b2b571a6a 100644
--- a/dev/breeze/src/airflow_breeze/utils/path_utils.py
+++ b/dev/breeze/src/airflow_breeze/utils/path_utils.py
@@ -527,6 +527,9 @@ SCRIPTS_CI_DOCKER_COMPOSE_INTEGRATION_KERBEROS_PATH = (
)
SCRIPTS_CI_DOCKER_COMPOSE_LOCAL_ALL_SOURCES_PATH =
SCRIPTS_CI_DOCKER_COMPOSE_PATH / "local-all-sources.yml"
SCRIPTS_CI_DOCKER_COMPOSE_LOCAL_YAML_PATH = SCRIPTS_CI_DOCKER_COMPOSE_PATH /
"local.yml"
+SCRIPTS_CI_DOCKER_COMPOSE_MOUNT_SPHINX_AIRFLOW_THEME_PATH = (
+ SCRIPTS_CI_DOCKER_COMPOSE_PATH / "mount-sphinx-airflow-theme.yml"
+)
SCRIPTS_CI_DOCKER_COMPOSE_MOUNT_UI_DIST_PATH = SCRIPTS_CI_DOCKER_COMPOSE_PATH
/ "mount-ui-dist.yml"
SCRIPTS_CI_DOCKER_COMPOSE_MOUNT_UV_LOCK_PATH = SCRIPTS_CI_DOCKER_COMPOSE_PATH
/ "mount-uv-lock.yml"
SCRIPTS_CI_DOCKER_COMPOSE_MYPY_PATH = SCRIPTS_CI_DOCKER_COMPOSE_PATH /
"mypy.yml"
diff --git a/dev/breeze/tests/test_developer_commands.py
b/dev/breeze/tests/test_developer_commands.py
index 4064f16697a..b9c603f6da7 100644
--- a/dev/breeze/tests/test_developer_commands.py
+++ b/dev/breeze/tests/test_developer_commands.py
@@ -17,13 +17,14 @@
from __future__ import annotations
-from unittest.mock import patch
+from unittest.mock import create_autospec, patch
import click
import pytest
from click import UsageError
from click.testing import CliRunner
+from airflow_breeze.commands import developer_commands
from airflow_breeze.commands.developer_commands import build_docs, down, run
from airflow_breeze.global_constants import DEFAULT_PYTHON_MAJOR_MINOR_VERSION
@@ -115,6 +116,28 @@ def
test_down_lists_projects_left_in_other_worktrees(runner, tmp_path, args, lis
assert "/work/other" not in output
[email protected]
+def docker_mocks(monkeypatch, tmp_path):
+ monkeypatch.setenv("SKIP_SAVING_CHOICES", "true")
+ for name in ("BREEZE_SDK", "DISTRIBUTIONS_LIST"):
+ monkeypatch.delenv(name, raising=False)
+ # The real _build_python_docs deletes generated/**/apis under
AIRFLOW_ROOT_PATH.
+ monkeypatch.setattr(developer_commands, "AIRFLOW_ROOT_PATH", tmp_path)
+ mocks = {}
+ for name in (
+ "perform_environment_checks",
+ "fix_ownership_using_docker",
+ "cleanup_python_generated_files",
+ "build_ci_image_if_needed",
+ "execute_command_in_shell",
+ ):
+ mocks[name] = create_autospec(getattr(developer_commands, name))
+ monkeypatch.setattr(developer_commands, name, mocks[name])
+ mocks["execute_command_in_shell"].return_value.returncode = 0
+ return mocks
+
+
[email protected]("docker_mocks")
class TestBuildDocsPythonVersion:
"""`breeze build-docs` always documents on the default Python.
@@ -123,30 +146,16 @@ class TestBuildDocsPythonVersion:
selected - see the comment in ``_build_python_docs``.
"""
- @pytest.fixture(autouse=True)
- def _no_docker(self, monkeypatch):
- monkeypatch.setenv("SKIP_SAVING_CHOICES", "true")
- for name in (
- "perform_environment_checks",
- "fix_ownership_using_docker",
- "cleanup_python_generated_files",
- ):
-
monkeypatch.setattr(f"airflow_breeze.commands.developer_commands.{name}",
lambda *a, **kw: None)
-
- def _invoke(self, runner: CliRunner, args: list[str], env: dict[str, str]
| None = None):
- with (
-
patch("airflow_breeze.commands.developer_commands.build_ci_image_if_needed") as
mock_build,
-
patch("airflow_breeze.commands.developer_commands.execute_command_in_shell") as
mock_shell,
- ):
- mock_shell.return_value.returncode = 0
- runner.invoke(build_docs, args, env=env, catch_exceptions=False)
- return mock_build, mock_shell
-
- def test_environment_python_does_not_change_the_docs_build(self, runner):
+ def test_environment_python_does_not_change_the_docs_build(self, runner,
docker_mocks):
# PYTHON_MAJOR_MINOR_VERSION is set on every job of the docs
publishing workflow, so an
# option reading it silently decided what the docs were built with.
- mock_build, mock_shell = self._invoke(runner, [],
env={"PYTHON_MAJOR_MINOR_VERSION": "3.12"})
+ result = runner.invoke(
+ build_docs, [], env={"PYTHON_MAJOR_MINOR_VERSION": "3.12"},
catch_exceptions=False
+ )
+ assert result.exit_code == 0
+ mock_build = docker_mocks["build_ci_image_if_needed"]
+ mock_shell = docker_mocks["execute_command_in_shell"]
assert mock_build.call_args.kwargs["command_params"].python ==
DEFAULT_PYTHON_MAJOR_MINOR_VERSION
assert mock_shell.call_args.args[0].python ==
DEFAULT_PYTHON_MAJOR_MINOR_VERSION
@@ -157,6 +166,49 @@ class TestBuildDocsPythonVersion:
assert "no such option" in result.output.lower()
+class TestBuildDocsSphinxAirflowTheme:
+ @pytest.mark.parametrize("pass_option", [True, False])
+ def test_theme_path_is_passed_to_shell_params(
+ self, runner, docker_mocks, tmp_path, monkeypatch, pass_option
+ ):
+ (tmp_path / "theme" / "sphinx_airflow_theme" / "static" /
"_gen").mkdir(parents=True)
+ monkeypatch.chdir(tmp_path)
+
+ result = runner.invoke(
+ build_docs,
+ ["--sphinx-airflow-theme-path", "theme"] if pass_option else [],
+ catch_exceptions=False,
+ )
+
+ assert result.exit_code == 0
+ shell_params =
docker_mocks["execute_command_in_shell"].call_args.args[0]
+ assert shell_params.sphinx_airflow_theme_path == (str(tmp_path /
"theme") if pass_option else "")
+
+ def test_theme_without_generated_assets_is_rejected(self, runner,
docker_mocks, tmp_path):
+ theme_checkout = tmp_path / "airflow-site"
+ (theme_checkout / "sphinx_airflow_theme").mkdir(parents=True)
+
+ result = runner.invoke(build_docs, ["--sphinx-airflow-theme-path",
str(theme_checkout)])
+
+ assert result.exit_code == 2
+ assert "sphinx_airflow_theme/static/_gen" in "
".join(click.unstyle(result.output).split())
+ for mock in docker_mocks.values():
+ mock.assert_not_called()
+
+ def test_theme_with_sdk_docs_only_is_rejected(self, runner, docker_mocks,
tmp_path):
+ theme_checkout = tmp_path / "airflow-site"
+ (theme_checkout / "sphinx_airflow_theme" / "static" /
"_gen").mkdir(parents=True)
+
+ result = runner.invoke(
+ build_docs, ["--sdk-docs-only", "--sphinx-airflow-theme-path",
str(theme_checkout)]
+ )
+
+ assert result.exit_code == 2
+ assert "--sdk-docs-only:" in click.unstyle(result.output)
+ for mock in docker_mocks.values():
+ mock.assert_not_called()
+
+
class TestRunIncludeMypyVolume:
@pytest.fixture(autouse=True)
def _no_docker(self, monkeypatch):
diff --git a/dev/breeze/tests/test_shell_params.py
b/dev/breeze/tests/test_shell_params.py
index b6d0b6d77a9..78e3d56d691 100644
--- a/dev/breeze/tests/test_shell_params.py
+++ b/dev/breeze/tests/test_shell_params.py
@@ -34,6 +34,7 @@ from airflow_breeze.params.shell_params import ShellParams
from airflow_breeze.utils.path_utils import (
SCRIPTS_CI_DOCKER_COMPOSE_BASE_PATH,
SCRIPTS_CI_DOCKER_COMPOSE_LOCAL_YAML_PATH,
+ SCRIPTS_CI_DOCKER_COMPOSE_MOUNT_SPHINX_AIRFLOW_THEME_PATH,
SCRIPTS_CI_DOCKER_COMPOSE_MOUNT_UV_LOCK_PATH,
SCRIPTS_CI_DOCKER_COMPOSE_PATH,
SCRIPTS_CI_DOCKER_COMPOSE_PYCACHE_PATH,
@@ -411,3 +412,16 @@ def test_uv_lock_is_mounted_only_by_its_own_compose_file():
assert uv_lock_compose_file["services"]["airflow"]["volumes"] == [
{"type": "bind", "source": "../../../uv.lock", "target":
"/opt/airflow/uv.lock"}
]
+
+
[email protected](
+ ("theme_path", "expected_count"),
[("/tmp/airflow-site/sphinx_airflow_theme", 1), ("", 0)]
+)
+def test_sphinx_airflow_theme_is_mounted_only_when_a_path_is_given(theme_path:
str, expected_count: int):
+ shell_params = ShellParams(sphinx_airflow_theme_path=theme_path)
+ compose_files = shell_params.compose_file.split(os.pathsep)
+ assert (
+
compose_files.count(str(SCRIPTS_CI_DOCKER_COMPOSE_MOUNT_SPHINX_AIRFLOW_THEME_PATH))
== expected_count
+ )
+ # Compose interpolates the bind source from this variable, so it must be
exported together with the file.
+ assert
shell_params.env_variables_for_docker_commands["SPHINX_AIRFLOW_THEME_PATH"] ==
theme_path
diff --git a/docs/README.md b/docs/README.md
index 21533eec881..e76925d3fd6 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -198,7 +198,9 @@ Such a main build also publishes the latest
"sphinx-airflow-theme" package to Gi
of documentation can automatically pick it up from there. This means that if
you want to make changes to
`javascript` or `css` that are part of the theme, you need to do it in
`airflow-site` repository and
merge it to `main` branch in order to be able to run the documentation build
in `apache-airflow` repository
-and pick up the latest version of the theme.
+and pick up the latest version of the theme. To preview an unreleased theme
change, see the
+`--sphinx-airflow-theme-path` option in
+[Building documentation in
Breeze](../contributing-docs/11_documentation_building.rst#building-documentation-in-breeze).
The version of sphinx theme is fixed in both repositories:
diff --git a/scripts/in_container/run_docs_build.sh
b/scripts/ci/docker-compose/mount-sphinx-airflow-theme.yml
old mode 100755
new mode 100644
similarity index 58%
copy from scripts/in_container/run_docs_build.sh
copy to scripts/ci/docker-compose/mount-sphinx-airflow-theme.yml
index bacfb75efe9..626a07d801f
--- a/scripts/in_container/run_docs_build.sh
+++ b/scripts/ci/docker-compose/mount-sphinx-airflow-theme.yml
@@ -1,4 +1,3 @@
-#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
@@ -15,16 +14,13 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-# shellcheck source=scripts/in_container/_in_container_script_init.sh
-. "$( dirname "${BASH_SOURCE[0]}" )/_in_container_script_init.sh"
-
-git config --global --add safe.directory /opt/airflow
-
-cd "${AIRFLOW_SOURCES}" || exit 1
-python -m docs.build_docs "${@}"
-
-# Copy the generated documentation to the mounted volume
-if [[ ( ${CI:="false"} == "true" || ${CI} == "True" ) && -d
"${AIRFLOW_SOURCES}/generated/_build/docs/" ]]; then
- rm -rf "/files/documentation"
- cp -r "${AIRFLOW_SOURCES}/generated/_build" "/files/documentation"
-fi
+---
+services:
+ airflow:
+ # Added by `breeze build-docs --sphinx-airflow-theme-path`. The mount is
read-only so the
+ # in-container install cannot leave root-owned build output in the
airflow-site checkout.
+ volumes:
+ - type: bind
+ source: ${SPHINX_AIRFLOW_THEME_PATH}
+ target: /opt/sphinx_airflow_theme
+ read_only: true
diff --git a/scripts/in_container/run_docs_build.sh
b/scripts/in_container/run_docs_build.sh
index bacfb75efe9..1cdfbca8b67 100755
--- a/scripts/in_container/run_docs_build.sh
+++ b/scripts/in_container/run_docs_build.sh
@@ -21,6 +21,12 @@
git config --global --add safe.directory /opt/airflow
cd "${AIRFLOW_SOURCES}" || exit 1
+
+# Mounted by `breeze build-docs --sphinx-airflow-theme-path`.
+if [[ -n "${SPHINX_AIRFLOW_THEME_PATH:-}" ]]; then
+ uv pip install --reinstall-package sphinx-airflow-theme
/opt/sphinx_airflow_theme
+fi
+
python -m docs.build_docs "${@}"
# Copy the generated documentation to the mounted volume