Skip to content

Commit 8d896d1

Browse files
benjaominghumitos
andauthored
Allow build.commands without build.tools (#10281)
* Adjust tests * Update validation to allow build.commands without build.tools * Remove comments, adds a negative test about re: "`build.jobs` can't be used without using `build.os`" * Rename BuildWithTools => BuildWithOs * Not legacy, still supported! * Make test case command not confuse, it should make sense * Adds a note to remember to create the folders before adding content * Improve a docs sentence * Use $READTHEDOCS_OUTPUT in examples * Update docs/user/build-customization.rst Co-authored-by: Manuel Kaufmann <[email protected]> --------- Co-authored-by: Manuel Kaufmann <[email protected]>
1 parent 5f153d9 commit 8d896d1

File tree

4 files changed

+72
-41
lines changed

4 files changed

+72
-41
lines changed

docs/user/build-customization.rst

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -360,14 +360,25 @@ Where to put files
360360
~~~~~~~~~~~~~~~~~~
361361

362362
It is your responsibility to generate HTML and other formats of your documentation using :ref:`config-file/v2:build.commands`.
363-
The contents of the ``_readthedocs/<format>/`` directory will be hosted as part of your documentation.
363+
The contents of the ``$READTHEDOCS_OUTPUT/<format>/`` directory will be hosted as part of your documentation.
364+
365+
We store the the base folder name ``_readthedocs/`` in the environment variable ``$READTHEDOCS_OUTPUT`` and encourage that you use this to generate paths.
364366

365367
Supported :ref:`formats <downloadable-documentation:accessing offline formats>` are published if they exist in the following directories:
366368

367-
* ``_readthedocs/html/`` (required)
368-
* ``_readthedocs/htmlzip/``
369-
* ``_readthedocs/pdf/``
370-
* ``_readthedocs/epub/``
369+
* ``$READTHEDOCS_OUTPUT/html/`` (required)
370+
* ``$READTHEDOCS_OUTPUT/htmlzip/``
371+
* ``$READTHEDOCS_OUTPUT/pdf/``
372+
* ``$READTHEDOCS_OUTPUT/epub/``
373+
374+
.. note::
375+
376+
Remember to create the folders before adding content to them.
377+
You can ensure that the output folder exists by adding the following command:
378+
379+
.. code-block:: console
380+
381+
mkdir -p $READTHEDOCS_OUTPUT/html/
371382
372383
Search support
373384
~~~~~~~~~~~~~~

readthedocs/config/config.py

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
Build,
2020
BuildJobs,
2121
BuildTool,
22-
BuildWithTools,
22+
BuildWithOs,
2323
Conda,
2424
Mkdocs,
2525
Python,
@@ -274,7 +274,7 @@ def validate(self):
274274

275275
@property
276276
def using_build_tools(self):
277-
return isinstance(self.build, BuildWithTools)
277+
return isinstance(self.build, BuildWithOs)
278278

279279
@property
280280
def is_using_conda(self):
@@ -781,11 +781,7 @@ def validate_conda(self):
781781
conda['environment'] = validate_path(environment, self.base_path)
782782
return conda
783783

784-
# NOTE: I think we should rename `BuildWithTools` to `BuildWithOs` since
785-
# `os` is the main and mandatory key that makes the diference
786-
#
787-
# NOTE: `build.jobs` can't be used without using `build.os`
788-
def validate_build_config_with_tools(self):
784+
def validate_build_config_with_os(self):
789785
"""
790786
Validates the build object (new format).
791787
@@ -799,9 +795,10 @@ def validate_build_config_with_tools(self):
799795
tools = {}
800796
with self.catch_validation_error('build.tools'):
801797
tools = self.pop_config('build.tools')
802-
validate_dict(tools)
803-
for tool in tools.keys():
804-
validate_choice(tool, self.settings['tools'].keys())
798+
if tools:
799+
validate_dict(tools)
800+
for tool in tools.keys():
801+
validate_choice(tool, self.settings["tools"].keys())
805802

806803
jobs = {}
807804
with self.catch_validation_error("build.jobs"):
@@ -824,13 +821,11 @@ def validate_build_config_with_tools(self):
824821
commands = self.pop_config("build.commands", default=[])
825822
validate_list(commands)
826823

827-
if not tools:
824+
if not (tools or commands):
828825
self.error(
829-
key='build.tools',
826+
key="build.tools",
830827
message=(
831-
'At least one tools of [{}] must be provided.'.format(
832-
' ,'.join(self.settings['tools'].keys())
833-
)
828+
"At least one item should be provided in 'tools' or 'commands'"
834829
),
835830
code=CONFIG_REQUIRED,
836831
)
@@ -856,12 +851,13 @@ def validate_build_config_with_tools(self):
856851
build["commands"].append(validate_string(command))
857852

858853
build['tools'] = {}
859-
for tool, version in tools.items():
860-
with self.catch_validation_error(f'build.tools.{tool}'):
861-
build['tools'][tool] = validate_choice(
862-
version,
863-
self.settings['tools'][tool].keys(),
864-
)
854+
if tools:
855+
for tool, version in tools.items():
856+
with self.catch_validation_error(f"build.tools.{tool}"):
857+
build["tools"][tool] = validate_choice(
858+
version,
859+
self.settings["tools"][tool].keys(),
860+
)
865861

866862
build['apt_packages'] = self.validate_apt_packages()
867863
return build
@@ -914,8 +910,8 @@ def validate_build(self):
914910
raw_build = self._raw_config.get('build', {})
915911
with self.catch_validation_error('build'):
916912
validate_dict(raw_build)
917-
if 'os' in raw_build:
918-
return self.validate_build_config_with_tools()
913+
if "os" in raw_build or "commands" in raw_build or "tools" in raw_build:
914+
return self.validate_build_config_with_os()
919915
return self.validate_old_build_config()
920916

921917
def validate_apt_package(self, index):
@@ -1335,7 +1331,7 @@ def build(self):
13351331
)
13361332
for tool, version in build['tools'].items()
13371333
}
1338-
return BuildWithTools(
1334+
return BuildWithOs(
13391335
os=build['os'],
13401336
tools=tools,
13411337
jobs=BuildJobs(**build["jobs"]),

readthedocs/config/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def __init__(self, **kwargs):
3535
super().__init__(**kwargs)
3636

3737

38-
class BuildWithTools(Base):
38+
class BuildWithOs(Base):
3939

4040
__slots__ = ("os", "tools", "jobs", "apt_packages", "commands")
4141

readthedocs/config/tests/test_config.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from readthedocs.config.models import (
3636
Build,
3737
BuildJobs,
38-
BuildWithTools,
38+
BuildWithOs,
3939
Conda,
4040
PythonInstall,
4141
PythonInstallRequirements,
@@ -1051,7 +1051,7 @@ def test_new_build_config(self):
10511051
)
10521052
build.validate()
10531053
assert build.using_build_tools
1054-
assert isinstance(build.build, BuildWithTools)
1054+
assert isinstance(build.build, BuildWithOs)
10551055
assert build.build.os == 'ubuntu-20.04'
10561056
assert build.build.tools['python'].version == '3.9'
10571057
full_version = settings.RTD_DOCKER_BUILD_SETTINGS['tools']['python']['3.9']
@@ -1086,7 +1086,10 @@ def test_new_build_config_conflict_with_build_python_version(self):
10861086
build.validate()
10871087
assert excinfo.value.key == 'python.version'
10881088

1089-
def test_commands_build_config(self):
1089+
def test_commands_build_config_tools_and_commands_valid(self):
1090+
"""
1091+
Test that build.tools and build.commands are valid together.
1092+
"""
10901093
build = self.get_build_config(
10911094
{
10921095
"build": {
@@ -1097,9 +1100,27 @@ def test_commands_build_config(self):
10971100
},
10981101
)
10991102
build.validate()
1100-
assert isinstance(build.build, BuildWithTools)
1103+
assert isinstance(build.build, BuildWithOs)
11011104
assert build.build.commands == ["pip install pelican", "pelican content"]
11021105

1106+
def test_build_jobs_without_build_os_is_invalid(self):
1107+
"""
1108+
build.jobs can't be used without build.os
1109+
"""
1110+
build = self.get_build_config(
1111+
{
1112+
"build": {
1113+
"tools": {"python": "3.8"},
1114+
"jobs": {
1115+
"pre_checkout": ["echo pre_checkout"],
1116+
},
1117+
},
1118+
},
1119+
)
1120+
with raises(InvalidConfig) as excinfo:
1121+
build.validate()
1122+
assert excinfo.value.key == "build.os"
1123+
11031124
def test_commands_build_config_invalid_command(self):
11041125
build = self.get_build_config(
11051126
{
@@ -1124,20 +1145,23 @@ def test_commands_build_config_invalid_no_os(self):
11241145
)
11251146
with raises(InvalidConfig) as excinfo:
11261147
build.validate()
1127-
assert excinfo.value.key == "build.commands"
1148+
assert excinfo.value.key == "build.os"
11281149

1129-
def test_commands_build_config_invalid_no_tools(self):
1150+
def test_commands_build_config_valid(self):
1151+
"""It's valid to build with just build.os and build.commands."""
11301152
build = self.get_build_config(
11311153
{
11321154
"build": {
11331155
"os": "ubuntu-22.04",
1134-
"commands": ["pip install pelican", "pelican content"],
1156+
"commands": ["echo 'hello world' > _readthedocs/html/index.html"],
11351157
},
11361158
},
11371159
)
1138-
with raises(InvalidConfig) as excinfo:
1139-
build.validate()
1140-
assert excinfo.value.key == "build.tools"
1160+
build.validate()
1161+
assert isinstance(build.build, BuildWithOs)
1162+
assert build.build.commands == [
1163+
"echo 'hello world' > _readthedocs/html/index.html"
1164+
]
11411165

11421166
@pytest.mark.parametrize("value", ["", None, "pre_invalid"])
11431167
def test_jobs_build_config_invalid_jobs(self, value):
@@ -1196,7 +1220,7 @@ def test_jobs_build_config(self):
11961220
},
11971221
)
11981222
build.validate()
1199-
assert isinstance(build.build, BuildWithTools)
1223+
assert isinstance(build.build, BuildWithOs)
12001224
assert isinstance(build.build.jobs, BuildJobs)
12011225
assert build.build.jobs.pre_checkout == ["echo pre_checkout"]
12021226
assert build.build.jobs.post_checkout == ["echo post_checkout"]

0 commit comments

Comments
 (0)