| # Copyright 2021 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import datetime |
| |
| from recipe_engine import recipe_api |
| |
| from RECIPE_MODULES.recipe_engine.file.api import SymlinkTree |
| |
| |
| class ArchiveTree: |
| """A representation of a tree to archive in RBE-CAS. |
| |
| This tree uses the same interface as api.file.SymlinkTree. |
| """ |
| |
| def __init__(self, root, api): |
| """See ArchiveApi.tree for the public constructor.""" |
| self._tree = api.cas_util.hardlink_tree(root) |
| self._api = api |
| |
| @property |
| def root(self): |
| """The root (Path) of the tree.""" |
| return self._tree.root |
| |
| def register_link(self, target, linkname): |
| """Registers a pair of paths to link.""" |
| self._tree.register_link( |
| self._api.path.abs_to_path(self._api.path.realpath(target)), |
| linkname, |
| ) |
| |
| def create_links(self, name, preserve_symlinks=False): |
| """Creates all registered links on disk.""" |
| # Use an env var instead of a flag to pass `preserve_symlinks` so we can |
| # call SymlinkTree.create_links as usual. |
| # TODO(crbug.com/1216363): this should no longer be necessary once the |
| # `cas` CLI respects symlinks. |
| env = {} |
| if preserve_symlinks: |
| env["PRESERVE_SYMLINKS"] = "1" |
| with self._api.context(env=env): |
| self._tree.create_links(name) |
| |
| |
| class HardlinkTree(SymlinkTree): |
| """A representation of a tree of hardlinks. |
| |
| This tree differs from the SymlinkTree in the resource passed to it. |
| See ArchiveApi.hardlink_tree for the public constructor. |
| """ |
| |
| pass |
| |
| |
| class CasUtilApi(recipe_api.RecipeApi): |
| """API for archiving and accessing archived files in RBE-CAS.""" |
| |
| def upload( |
| self, |
| staging_dir, |
| upload_paths=(), |
| step_name=None, |
| output_property=None, |
| ): |
| """Returns the digest of the archived tree created from the provided |
| staging_dir. |
| |
| Args: |
| staging_dir (Path): The root of the directory to upload. |
| upload_paths (list(Path)): A list of files under the staging_dir |
| to upload if not uploading the entire directory. |
| output_property (str): The name of the output property to set with |
| the digest. |
| """ |
| |
| def do_upload(step_name): |
| # TODO(crbug.com/1279920): Remove retries after "digest mismatch" |
| # upload failures have been root-caused. The CAS CLI should be able |
| # to recover from transient errors internally. |
| return self.m.utils.retry( |
| lambda: self.m.cas.archive( |
| step_name, |
| staging_dir, |
| *upload_paths, |
| # TODO(olivernewman): Stop setting log_level after slow CAS |
| # uploads have been diagnosed. |
| log_level="debug", |
| # TODO(fxbug.dev/122153): Stop setting a timeout once CAS |
| # slowness is resolved and/or the CAS CLI sets shorter |
| # internal timeouts. |
| timeout=datetime.timedelta(minutes=15), |
| ), |
| max_attempts=3, |
| ) |
| |
| # TODO(fxbug.dev/62939): The two codepaths below mirror what was in |
| # upload_isolated before the migration happened so that step names stay |
| # the same. Combine the two paths once we confirm there are no |
| # dependencies on the step names. |
| if output_property: |
| with self.m.step.nest(step_name or "cas") as step: |
| cas_digest = do_upload("archive") |
| step.presentation.properties[output_property] = cas_digest |
| else: |
| cas_digest = do_upload(step_name or "archive") |
| return cas_digest |
| |
| def download(self, digest, output_dir, step_name=None): |
| """Downloads the archive with the CAS digest into output_dir.""" |
| self.m.cas.download( |
| step_name or "download", digest=digest, output_dir=output_dir |
| ) |
| |
| def tree(self, root): |
| """Creates an ArchiveTree, given a root directory. |
| |
| Args: |
| root (Path): root of a tree to archive. |
| """ |
| return ArchiveTree(root, self.m) |
| |
| def hardlink_tree(self, root): |
| """Creates a HardlinkTree, given a root directory. |
| |
| Args: |
| root (Path): root of a tree of hardlinks. |
| """ |
| # Pass in the file module to include all necessary deps for SymlinkTree |
| # which HardlinkTree is a child of. |
| return HardlinkTree(root, self.m.file.m, self.resource("hardlink.py")) |