-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Build: allow to install packages with apt #8065
Changes from all commits
2435a4a
d355827
f6116fc
59768af
45ab78f
b443949
e5923a1
2773710
5f8874b
ecd0183
1d330ea
f735a2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,7 +69,7 @@ This is to avoid typos and provide feedback on invalid configurations. | |
|
||
.. contents:: | ||
:local: | ||
:depth: 1 | ||
:depth: 3 | ||
|
||
version | ||
~~~~~~~ | ||
|
@@ -303,6 +303,9 @@ Configuration for the documentation build process. | |
|
||
build: | ||
image: latest | ||
apt_packages: | ||
- libclang | ||
- cmake | ||
|
||
python: | ||
version: 3.7 | ||
|
@@ -323,6 +326,32 @@ as defined here: | |
* `stable <https://github.com/readthedocs/readthedocs-docker-images/tree/releases/5.x>`_: :buildpyversions:`stable` | ||
* `latest <https://github.com/readthedocs/readthedocs-docker-images/tree/releases/6.x>`_: :buildpyversions:`latest` | ||
|
||
build.apt_packages | ||
`````````````````` | ||
|
||
List of `APT packages`_ to install. | ||
stsewd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Our build servers run Ubuntu 18.04, with the default set of package repositories installed. | ||
stsewd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
We don't currently support PPA's or other custom repositories. | ||
|
||
.. _APT packages: https://packages.ubuntu.com/ | ||
|
||
:Type: ``list`` | ||
:Default: ``[]`` | ||
|
||
.. code-block:: yaml | ||
|
||
version: 2 | ||
|
||
build: | ||
apt_packages: | ||
- libclang | ||
- cmake | ||
|
||
.. note:: | ||
|
||
When possible avoid installing Python packages using apt (``python3-numpy`` for example), | ||
:ref:`use pip or Conda instead <guides/reproducible-builds:pinning dependencies>`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💯 |
||
|
||
sphinx | ||
~~~~~~ | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -64,6 +64,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SUBMODULES_INVALID = 'submodules-invalid' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
INVALID_KEYS_COMBINATION = 'invalid-keys-combination' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
INVALID_KEY = 'invalid-key' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
INVALID_NAME = 'invalid-name' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
LATEST_CONFIGURATION_VERSION = 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -121,10 +122,18 @@ def __init__(self, key, code, error_message, source_file=None): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
super().__init__(message, code=code) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def _get_display_key(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Checks for patterns similar to `python.install.0.requirements` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# if matched change to `python.install[0].requirements` using backreference. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Display keys in a more friendly format. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Indexes are displayed like ``n``, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
but users may be more familiar with the ``[n]`` syntax. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
For example ``python.install.0.requirements`` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
is changed to `python.install[0].requirements`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return re.sub( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
r'^(python\.install)(\.)(\d+)(\.\w+)$', r'\1[\3]\4', self.key | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
r'^([a-zA-Z_.-]+)\.(\d+)([a-zA-Z_.-]*)$', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
r'\1[\2]\3', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.key | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
stsewd marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -745,12 +754,69 @@ def validate_build(self): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Allow to override specific project | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
config_image = self.defaults.get('build_image') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if config_image: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
build['image'] = config_image | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Allow to override specific project | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
config_image = self.defaults.get('build_image') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if config_image: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
build['image'] = config_image | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
with self.catch_validation_error('build.apt_packages'): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
raw_packages = self._raw_config.get('build', {}).get('apt_packages', []) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
validate_list(raw_packages) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Transform to a dict, so is easy to validate individual entries. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self._raw_config.setdefault('build', {})['apt_packages'] = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
list_to_dict(raw_packages) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
build['apt_packages'] = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.validate_apt_package(index) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for index in range(len(raw_packages)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if not raw_packages: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.pop_config('build.apt_packages') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return build | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def validate_apt_package(self, index): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like we probably can be smarter about this. Can we not call apt after a
We should still raise a validation error here, but I'd like to see additional restrictions for this built in. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point Eric. Also, we need to take care of executing commands in the same line as well. Like,
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe python's subprocess command will handle this, but we should definitely be sure and have tests for it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we use subprocess, but the Docker client API ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't use subprocess, we escape special chars in readthedocs.org/readthedocs/doc_builder/environments.py Lines 335 to 367 in 2435a4a
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you tested defining packages in a malicious way? (e.g. trying to executed forbidden things). From this docstring it only happen under certain circumstances. Are we sure we are always calling this in that way? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Validate the package name to avoid injections of extra options. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
We validate that they aren't interpreted as an option or file. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
See https://manpages.ubuntu.com/manpages/xenial/man8/apt-get.8.html | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
and https://www.debian.org/doc/manuals/debian-reference/ch02.en.html#_debian_package_file_names # noqa | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for allowed chars in packages names. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
key = f'build.apt_packages.{index}' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
package = self.pop_config(key) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
with self.catch_validation_error(key): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
validate_string(package) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
package = package.strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
invalid_starts = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Don't allow extra options. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'-', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Don't allow to install from a path. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
stsewd marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'/', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'.', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for start in invalid_starts: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I left this bc feels like it gives a nice error for usual mistakes, but isn't needed as the regex below already validates this. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if package.startswith(start): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
key=key, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
message=( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'Invalid package name. ' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
f'Package can\'t start with {start}.', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
code=INVALID_NAME, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# List of valid chars in packages names. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pattern = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9.+-]*$') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if not pattern.match(package): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
key=key, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
message='Invalid package name.', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
code=INVALID_NAME, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return package | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def validate_python(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Validates the python key. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this will spam the TOC too much -- seems fine tho?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was between 2 or 3, but wanted to have
build.apt_images
andsphing.builder
visible instead of just havingsphinx
andbuild
, but no strong opinion.