|
1 | 1 | import os
|
2 | 2 | import shutil
|
| 3 | +import tarfile |
3 | 4 | from collections import defaultdict
|
4 | 5 |
|
5 | 6 | import structlog
|
|
13 | 14 | from readthedocs.projects.exceptions import RepositoryError
|
14 | 15 | from readthedocs.projects.models import Feature
|
15 | 16 | from readthedocs.projects.signals import after_build, before_build, before_vcs
|
| 17 | +from readthedocs.storage import build_tools_storage |
16 | 18 |
|
17 | 19 | log = structlog.get_logger(__name__)
|
18 | 20 |
|
@@ -157,7 +159,7 @@ def setup_environment(self):
|
157 | 159 |
|
158 | 160 | # Install all ``build.tools`` specified by the user
|
159 | 161 | if self.data.config.using_build_tools:
|
160 |
| - self.language_environment.install_build_tools() |
| 162 | + self.install_build_tools() |
161 | 163 |
|
162 | 164 | self.run_build_job("pre_create_environment")
|
163 | 165 | self.create_environment()
|
@@ -354,6 +356,140 @@ def run_build_commands(self):
|
354 | 356 | # ignore=shutil.ignore_patterns(*self.ignore_patterns),
|
355 | 357 | )
|
356 | 358 |
|
| 359 | + def install_build_tools(self): |
| 360 | + """ |
| 361 | + Install all ``build.tools`` defined by the user in the config file. |
| 362 | +
|
| 363 | + It uses ``asdf`` behind the scenes to manage all the tools and versions |
| 364 | + of them. These tools/versions are stored in the Cloud cache and are |
| 365 | + downloaded on each build (~50 - ~100Mb). |
| 366 | +
|
| 367 | + If the requested tool/version is not present in the cache, it's |
| 368 | + installed via ``asdf`` on the fly. |
| 369 | + """ |
| 370 | + if settings.RTD_DOCKER_COMPOSE: |
| 371 | + # Create a symlink for ``root`` user to use the same ``.asdf`` |
| 372 | + # installation as the ``docs`` user. Required for local building |
| 373 | + # since everything is run as ``root`` when using Local Development |
| 374 | + # instance |
| 375 | + cmd = [ |
| 376 | + "ln", |
| 377 | + "-s", |
| 378 | + os.path.join(settings.RTD_DOCKER_WORKDIR, ".asdf"), |
| 379 | + "/root/.asdf", |
| 380 | + ] |
| 381 | + self.build_environment.run( |
| 382 | + *cmd, |
| 383 | + record=False, |
| 384 | + ) |
| 385 | + |
| 386 | + for tool, version in self.data.config.build.tools.items(): |
| 387 | + full_version = version.full_version # e.g. 3.9 -> 3.9.7 |
| 388 | + |
| 389 | + # TODO: generate the correct path for the Python version |
| 390 | + # see https://github.com/readthedocs/readthedocs.org/pull/8447#issuecomment-911562267 |
| 391 | + # tool_path = f'{self.config.build.os}/{tool}/2021-08-30/{full_version}.tar.gz' |
| 392 | + tool_path = f"{self.data.config.build.os}-{tool}-{full_version}.tar.gz" |
| 393 | + tool_version_cached = build_tools_storage.exists(tool_path) |
| 394 | + if tool_version_cached: |
| 395 | + remote_fd = build_tools_storage.open(tool_path, mode="rb") |
| 396 | + with tarfile.open(fileobj=remote_fd) as tar: |
| 397 | + # Extract it on the shared path between host and Docker container |
| 398 | + extract_path = os.path.join(self.data.project.doc_path, "tools") |
| 399 | + tar.extractall(extract_path) |
| 400 | + |
| 401 | + # Move the extracted content to the ``asdf`` installation |
| 402 | + cmd = [ |
| 403 | + "mv", |
| 404 | + f"{extract_path}/{full_version}", |
| 405 | + os.path.join( |
| 406 | + settings.RTD_DOCKER_WORKDIR, |
| 407 | + f".asdf/installs/{tool}/{full_version}", |
| 408 | + ), |
| 409 | + ] |
| 410 | + self.build_environment.run( |
| 411 | + *cmd, |
| 412 | + record=False, |
| 413 | + ) |
| 414 | + else: |
| 415 | + log.debug( |
| 416 | + "Cached version for tool not found.", |
| 417 | + os=self.data.config.build.os, |
| 418 | + tool=tool, |
| 419 | + full_version=full_version, |
| 420 | + tool_path=tool_path, |
| 421 | + ) |
| 422 | + # If the tool version selected is not available from the |
| 423 | + # cache we compile it at build time |
| 424 | + cmd = [ |
| 425 | + # TODO: make ``PYTHON_CONFIGURE_OPTS="--enable-shared"`` |
| 426 | + # environment variable to work here. Note that |
| 427 | + # ``self.build_environment.run`` does not support passing |
| 428 | + # environment for a particular command: |
| 429 | + # https://github.com/readthedocs/readthedocs.org/blob/9d2d1a2/readthedocs/doc_builder/environments.py#L430-L431 |
| 430 | + "asdf", |
| 431 | + "install", |
| 432 | + tool, |
| 433 | + full_version, |
| 434 | + ] |
| 435 | + self.build_environment.run( |
| 436 | + *cmd, |
| 437 | + ) |
| 438 | + |
| 439 | + # Make the tool version chosen by the user the default one |
| 440 | + cmd = [ |
| 441 | + "asdf", |
| 442 | + "global", |
| 443 | + tool, |
| 444 | + full_version, |
| 445 | + ] |
| 446 | + self.build_environment.run( |
| 447 | + *cmd, |
| 448 | + ) |
| 449 | + |
| 450 | + # Recreate shims for this tool to make the new version |
| 451 | + # installed available |
| 452 | + # https://asdf-vm.com/learn-more/faq.html#newly-installed-exectable-not-running |
| 453 | + cmd = [ |
| 454 | + "asdf", |
| 455 | + "reshim", |
| 456 | + tool, |
| 457 | + ] |
| 458 | + self.build_environment.run( |
| 459 | + *cmd, |
| 460 | + record=False, |
| 461 | + ) |
| 462 | + |
| 463 | + if all( |
| 464 | + [ |
| 465 | + tool == "python", |
| 466 | + # Do not install them if the tool version was cached |
| 467 | + # because these dependencies are already installed when |
| 468 | + # created with our script and uploaded to the cache's |
| 469 | + # bucket |
| 470 | + not tool_version_cached, |
| 471 | + # Do not install them on conda/mamba since they are not |
| 472 | + # needed because the environment is managed by conda/mamba |
| 473 | + # itself |
| 474 | + self.data.config.python_interpreter not in ("conda", "mamba"), |
| 475 | + ] |
| 476 | + ): |
| 477 | + # Install our own requirements if the version is compiled |
| 478 | + cmd = [ |
| 479 | + "python", |
| 480 | + "-mpip", |
| 481 | + "install", |
| 482 | + "-U", |
| 483 | + "virtualenv", |
| 484 | + # We cap setuptools to avoid breakage of projects |
| 485 | + # relying on setup.py invokations, |
| 486 | + # see https://github.com/readthedocs/readthedocs.org/issues/8659 |
| 487 | + "setuptools<58.3.0", |
| 488 | + ] |
| 489 | + self.build_environment.run( |
| 490 | + *cmd, |
| 491 | + ) |
| 492 | + |
357 | 493 | # Helpers
|
358 | 494 | #
|
359 | 495 | # TODO: move somewhere or change names to make them private or something to
|
|
0 commit comments