1# 2# Copyright (C) 2023 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16"""repo tree fakes for use in tests.""" 17import contextlib 18import subprocess 19from pathlib import Path 20from xml.etree import ElementTree 21 22from .fakeproject import FakeProject 23 24 25class FakeRepo: 26 """A repo tree for use in tests. 27 28 This shouldn't be used directly. Use the tree_builder fixture. 29 """ 30 31 def __init__(self, temp_dir: Path) -> None: 32 self.root = temp_dir / "tree" 33 self.mirror_dir = temp_dir / "mirrors" 34 self.upstream_dir = temp_dir / "upstreams" 35 self.manifest_repo = temp_dir / "manifest" 36 self.projects: list[FakeProject] = [] 37 self._used_git_subpaths: set[str] = set() 38 self._used_tree_subpaths: set[str] = set() 39 40 def project(self, git_subpath: str, tree_subpath: str) -> FakeProject: 41 """Creates a new project in the repo.""" 42 if git_subpath in self._used_git_subpaths: 43 raise KeyError(f"A project with git path {git_subpath} already exists") 44 if tree_subpath in self._used_tree_subpaths: 45 raise KeyError(f"A project with tree path {tree_subpath} already exists") 46 project = FakeProject( 47 self.root / tree_subpath, 48 self.upstream_dir / tree_subpath, 49 self.mirror_dir / git_subpath, 50 ) 51 self.projects.append(project) 52 self._used_git_subpaths.add(git_subpath) 53 self._used_tree_subpaths.add(tree_subpath) 54 return project 55 56 def init_and_sync(self) -> None: 57 """Runs repo init and repo sync to clone the repo tree.""" 58 self.root.mkdir(parents=True) 59 with contextlib.chdir(self.root): 60 subprocess.run( 61 ["repo", "init", "-c", "-u", str(self.manifest_repo), "-b", "main"], 62 check=True, 63 ) 64 subprocess.run(["repo", "sync", "-c"], check=True) 65 66 def create_manifest_repo(self) -> None: 67 """Creates the git repo for the manifest, commits the manifest XML.""" 68 self.manifest_repo.mkdir(parents=True) 69 with contextlib.chdir(self.manifest_repo): 70 subprocess.run(["git", "init"], check=True) 71 Path("default.xml").write_bytes( 72 ElementTree.tostring(self._create_manifest_xml(), encoding="utf-8") 73 ) 74 subprocess.run(["git", "add", "default.xml"], check=True) 75 subprocess.run(["git", "commit", "-m", "Initial commit."], check=True) 76 77 def _create_manifest_xml(self) -> ElementTree.Element: 78 # Example manifest: 79 # 80 # <manifest> 81 # <remote name="aosp" fetch="$URL" /> 82 # <default revision="main" remote="aosp" /> 83 # 84 # <project path="external/project" name="platform/external/project" 85 # revision="master" remote="goog" /> 86 # ... 87 # </manifest> 88 # 89 # The revision and remote attributes of project are optional. 90 root = ElementTree.Element("manifest") 91 ElementTree.SubElement( 92 root, 93 "remote", 94 {"name": "aosp", "fetch": self.mirror_dir.resolve().as_uri()}, 95 ) 96 ElementTree.SubElement(root, "default", {"revision": "main", "remote": "aosp"}) 97 for project in self.projects: 98 ElementTree.SubElement( 99 root, 100 "project", 101 { 102 "path": str(project.local.path.relative_to(self.root)), 103 "name": str( 104 project.android_mirror.path.relative_to(self.mirror_dir) 105 ), 106 }, 107 ) 108 return root 109