Skip to content

Build: expose READTHEDOCS_VIRTUALENV_PATH variable #9971

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Feb 7, 2023
Merged
7 changes: 7 additions & 0 deletions docs/user/environment-variables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ Read the Docs builders set the following environment variables automatically for

:Examples: ``en``, ``it``, ``de_AT``, ``es``, ``pt_BR``

.. envvar:: READTHEDOCS_VIRTUALENV_PATH

Path for the :ref:`virtualenv that was created for this build <builds:Understanding what's going on>`.
Only exists for builds using Virtualenv and not Conda.

:Example: ``/home/docs/checkouts/readthedocs.org/user_builds/project/envs/version``

User-defined environment variables
----------------------------------

Expand Down
9 changes: 8 additions & 1 deletion readthedocs/api/v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,15 @@ def get_command(self, obj):
docroot = re.sub("/[0-9a-z]+/?$", "", settings.DOCROOT, count=1)
container_hash = "/([0-9a-z]+/)?"

command = obj.command
regex = f"{docroot}{container_hash}{project_slug}/envs/{version_slug}(/bin/)?"
command = re.sub(regex, "", obj.command, count=1)
command = re.sub(regex, "", command, count=1)

regex = r"^\$READTHEDOCS_VIRTUALENV_PATH/bin/"
command = re.sub(regex, "", command, count=1)

regex = r"^\$CONDA_ENVS_PATH/\\$CONDA_DEFAULT_ENV/bin/"
command = re.sub(regex, "", command, count=1)
return command


Expand Down
4 changes: 4 additions & 0 deletions readthedocs/doc_builder/director.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ def get_build_env_vars(self):
if self.data.config.conda is not None:
env.update(
{
# NOTE: should these be prefixed with "READTHEDOCS_"?
"CONDA_ENVS_PATH": os.path.join(
self.data.project.doc_path, "conda"
),
Expand All @@ -577,6 +578,9 @@ def get_build_env_vars(self):
self.data.version.slug,
"bin",
),
"READTHEDOCS_VIRTUALENV_PATH": os.path.join(
self.data.project.doc_path, "envs", self.data.version.slug
),
}
)

Expand Down
13 changes: 12 additions & 1 deletion readthedocs/doc_builder/environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,18 @@ def get_wrapped_command(self):

def _escape_command(self, cmd):
r"""Escape the command by prefixing suspicious chars with `\`."""
return self.bash_escape_re.sub(r'\\\1', cmd)
command = self.bash_escape_re.sub(r"\\\1", cmd)

# HACK: avoid escaping variables that we need to use in the commands
not_escape_variables = (
"READTHEDOCS_OUTPUT",
"READTHEDOCS_VIRTUALENV_PATH",
"CONDA_ENVS_PATH",
"CONDA_DEFAULT_ENV",
)
for variable in not_escape_variables:
command = command.replace(f"\\${variable}", f"${variable}")
return command


class BaseEnvironment:
Expand Down
31 changes: 16 additions & 15 deletions readthedocs/doc_builder/python_environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,17 @@ def install_package(self, install):
bin_path=self.venv_bin(),
)

def venv_bin(self, filename=None):
def venv_bin(self, prefixes, filename=None):
"""
Return path to the virtualenv bin path, or a specific binary.

:param filename: If specified, add this filename to the path return
:param prefix: List of path prefixes to include in the resulting path
:returns: Path to virtualenv bin or filename in virtualenv bin
"""
parts = [self.venv_path(), 'bin']
if filename is not None:
parts.append(filename)
return os.path.join(*parts)
prefixes.append(filename)
return os.path.join(*prefixes)


class Virtualenv(PythonEnvironment):
Expand All @@ -110,8 +110,10 @@ class Virtualenv(PythonEnvironment):
.. _virtualenv: https://virtualenv.pypa.io/
"""

def venv_path(self):
return os.path.join(self.project.doc_path, 'envs', self.version.slug)
# pylint: disable=arguments-differ
def venv_bin(self, filename=None):
prefixes = ["$READTHEDOCS_VIRTUALENV_PATH", "bin"]
return super().venv_bin(prefixes, filename=filename)

def setup_base(self):
"""
Expand All @@ -133,9 +135,7 @@ def setup_base(self):
cli_args.append('--system-site-packages')

# Append the positional destination argument
cli_args.append(
self.venv_path(),
)
cli_args.append("$READTHEDOCS_VIRTUALENV_PATH")

self.build_env.run(
self.config.python_interpreter,
Expand Down Expand Up @@ -167,7 +167,9 @@ def install_core_requirements(self):
)
cmd = pip_install_cmd + [pip_version, 'setuptools<58.3.0']
self.build_env.run(
*cmd, bin_path=self.venv_bin(), cwd=self.checkout_path
*cmd,
bin_path=self.venv_bin(),
cwd=self.checkout_path,
)

requirements = []
Expand Down Expand Up @@ -318,8 +320,10 @@ class Conda(PythonEnvironment):
.. _Conda: https://conda.io/docs/
"""

def venv_path(self):
return os.path.join(self.project.doc_path, 'conda', self.version.slug)
# pylint: disable=arguments-differ
def venv_bin(self, filename=None):
prefixes = ["$CONDA_ENVS_PATH", "$CONDA_DEFAULT_ENV", "bin"]
return super().venv_bin(prefixes, filename=filename)

def conda_bin_name(self):
"""
Expand Down Expand Up @@ -354,9 +358,6 @@ def _update_conda_startup(self):
)

def setup_base(self):
conda_env_path = os.path.join(self.project.doc_path, 'conda')
os.path.join(conda_env_path, self.version.slug)

if self.project.has_feature(Feature.UPDATE_CONDA_STARTUP):
self._update_conda_startup()

Expand Down
3 changes: 3 additions & 0 deletions readthedocs/projects/tests/test_build_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ def test_get_env_vars(self, load_yaml_config, build_environment, config, externa
"bin",
),
PUBLIC_TOKEN="a1b2c3",
# Local and Circle are different values.
# We only check it's present, but not its value.
READTHEDOCS_VIRTUALENV_PATH=mock.ANY,
)
if not external:
expected_build_env_vars["PRIVATE_TOKEN"] = "a1b2c3"
Expand Down
2 changes: 1 addition & 1 deletion readthedocs/rtd_tests/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ def test_response_finished_and_success(self):
buildcommandresult = get(
BuildCommandResult,
build=build,
command="/home/docs/checkouts/readthedocs.org/user_builds/myproject/envs/myversion/bin/python -m pip install --upgrade --no-cache-dir pip setuptools<58.3.0",
command="python -m pip install --upgrade --no-cache-dir pip setuptools<58.3.0",
exit_code=0,
)
resp = client.get('/api/v2/build/{build}/'.format(build=build.pk))
Expand Down