Skip to content

Commit

Permalink
Build: POC of build.commands
Browse files Browse the repository at this point in the history
Minimal implementation of `build.commands` as a POC to show how it could work.

It's using a `.readthedocs.yaml` similar to this one:

```yaml
version: 2

build:
  os: ubuntu-20.04
  commands:
    - mkdir output/
    - echo "Hello world" > output/index.html

  tools:
    python: "3"
```

This, of course, is not a good implementation and it's done pretty quick as a
way to show what parts of the code are required to be touched. I think this will
help with the discussion about how the UX around `build.commands` could work.

It defines an "implicit contract" where the `output/` folder under the
repository checkout will be uploaded to S3 and no HTML integrations will be done.
  • Loading branch information
humitos committed Apr 27, 2022
1 parent 7db6582 commit 41451f8
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 2 deletions.
18 changes: 18 additions & 0 deletions readthedocs/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,11 @@ def validate_build_config_with_tools(self):
BuildJobs.__slots__,
)

commands = []
with self.catch_validation_error("build.commands"):
commands = self.pop_config("build.commands")
validate_list(commands)

if not tools:
self.error(
key='build.tools',
Expand All @@ -801,13 +806,25 @@ def validate_build_config_with_tools(self):
code=CONFIG_REQUIRED,
)

if commands and jobs:
self.error(
key="build.commands",
message="The keys build.jobs and build.commands can't be used together.",
code=INVALID_KEYS_COMBINATION,
)

build["jobs"] = {}
for job, commands in jobs.items():
with self.catch_validation_error(f"build.jobs.{job}"):
build["jobs"][job] = [
validate_string(command) for command in validate_list(commands)
]

build["commands"] = []
for command in commands:
with self.catch_validation_error("build.commands"):
build["commands"].append(validate_string(command))

build['tools'] = {}
for tool, version in tools.items():
with self.catch_validation_error(f'build.tools.{tool}'):
Expand Down Expand Up @@ -1292,6 +1309,7 @@ def build(self):
os=build['os'],
tools=tools,
jobs=BuildJobs(**build["jobs"]),
commands=build["commands"],
apt_packages=build["apt_packages"],
)
return Build(**build)
Expand Down
5 changes: 3 additions & 2 deletions readthedocs/config/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ def __init__(self, **kwargs):

class BuildWithTools(Base):

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

def __init__(self, **kwargs):
kwargs.setdefault('apt_packages', [])
kwargs.setdefault("apt_packages", [])
kwargs.setdefault("commands", [])
super().__init__(**kwargs)


Expand Down
19 changes: 19 additions & 0 deletions readthedocs/doc_builder/director.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import shutil
from collections import defaultdict

import structlog
Expand Down Expand Up @@ -335,6 +336,24 @@ def run_build_job(self, job):
for command in commands:
environment.run(*command.split(), escape_command=False, cwd=cwd)

def run_build_commands(self):
cwd = self.data.project.checkout_path(self.data.version.slug)
environment = self.vcs_environment
for command in self.data.config.build.commands:
environment.run(*command.split(), escape_command=False, cwd=cwd)

# Copy files to artifacts path so they are uploaded to S3
target = self.data.project.artifact_path(
version=self.data.version.slug,
type_="sphinx",
)
artifacts_path = os.path.join(cwd, "output")
shutil.copytree(
artifacts_path,
target,
# ignore=shutil.ignore_patterns(*self.ignore_patterns),
)

# Helpers
#
# TODO: move somewhere or change names to make them private or something to
Expand Down
11 changes: 11 additions & 0 deletions readthedocs/projects/tasks/builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""
import signal
import socket
from collections import defaultdict

import structlog
from celery import Task
Expand Down Expand Up @@ -595,6 +596,16 @@ def execute(self):
# ``__exit__``
self.data.build_director.create_build_environment()
with self.data.build_director.build_environment:

# NOTE: check if the build uses `build.commands` and only run those
if self.data.config.build.commands:
self.update_build(state=BUILD_STATE_BUILDING)
self.data.build_director.run_build_commands()

self.data.outcomes = defaultdict(lambda: False)
self.data.outcomes["html"] = True
return

# Installing
self.update_build(state=BUILD_STATE_INSTALLING)
self.data.build_director.setup_environment()
Expand Down

0 comments on commit 41451f8

Please sign in to comment.