1 # Copyright 2020 The Pigweed Authors
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 # use this file except in compliance with the License. You may obtain a copy of
7 # https://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations under
14 """Install and remove optional packages."""
22 from typing import Dict, List, Sequence, Tuple
24 _LOG: logging.Logger = logging.getLogger(__name__)
28 """Package to be installed.
30 Subclass this to implement installation of a specific package.
32 def __init__(self, name):
39 def install(self, path: pathlib.Path) -> None: # pylint: disable=no-self-use
40 """Install the package at path.
42 Install the package in path. Cannot assume this directory is empty—it
43 may need to be deleted or updated.
46 def remove(self, path: pathlib.Path) -> None: # pylint: disable=no-self-use
47 """Remove the package from path.
49 Removes the directory containing the package. For most packages this
50 should be sufficient to remove the package, and subclasses should not
51 need to override this package.
53 if os.path.exists(path):
56 def status(self, path: pathlib.Path) -> bool: # pylint: disable=no-self-use
57 """Returns if package is installed at path and current.
59 This method will be skipped if the directory does not exist.
62 def info(self, path: pathlib.Path) -> Sequence[str]: # pylint: disable=no-self-use
63 """Returns a short string explaining how to enable the package."""
66 _PACKAGES: Dict[str, Package] = {}
69 def register(package_class: type, name: str = None) -> None:
71 obj = package_class(name)
74 _PACKAGES[obj.name] = obj
77 @dataclasses.dataclass
80 installed: Tuple[str, ...]
81 available: Tuple[str, ...]
85 """Install and remove optional packages."""
86 def __init__(self, root: pathlib.Path):
88 os.makedirs(root, exist_ok=True)
90 def install(self, package: str, force: bool = False) -> None:
91 pkg = _PACKAGES[package]
94 pkg.install(self._pkg_root / pkg.name)
96 def remove(self, package: str) -> None:
97 pkg = _PACKAGES[package]
98 pkg.remove(self._pkg_root / pkg.name)
100 def status(self, package: str) -> bool:
101 pkg = _PACKAGES[package]
102 path = self._pkg_root / pkg.name
103 return os.path.isdir(path) and pkg.status(path)
105 def list(self) -> Packages:
108 for package in sorted(_PACKAGES.keys()):
109 pkg = _PACKAGES[package]
110 if pkg.status(self._pkg_root / pkg.name):
111 installed.append(pkg.name)
113 available.append(pkg.name)
116 all=tuple(_PACKAGES.keys()),
117 installed=tuple(installed),
118 available=tuple(available),
121 def info(self, package: str) -> Sequence[str]:
122 pkg = _PACKAGES[package]
123 return pkg.info(self._pkg_root / pkg.name)
126 class PackageManagerCLI:
127 """Command-line interface to PackageManager."""
129 self._mgr: PackageManager = None
131 def install(self, package: str, force: bool = False) -> int:
132 _LOG.info('Installing %s...', package)
133 self._mgr.install(package, force)
134 _LOG.info('Installing %s...done.', package)
135 for line in self._mgr.info(package):
136 _LOG.info('%s', line)
139 def remove(self, package: str) -> int:
140 _LOG.info('Removing %s...', package)
141 self._mgr.remove(package)
142 _LOG.info('Removing %s...done.', package)
145 def status(self, package: str) -> int:
146 if self._mgr.status(package):
147 _LOG.info('%s is installed.', package)
148 for line in self._mgr.info(package):
149 _LOG.info('%s', line)
152 _LOG.info('%s is not installed.', package)
155 def list(self) -> int:
156 packages = self._mgr.list()
158 _LOG.info('Installed packages:')
159 for package in packages.installed:
160 _LOG.info(' %s', package)
161 for line in self._mgr.info(package):
162 _LOG.info(' %s', line)
165 _LOG.info('Available packages:')
166 for package in packages.available:
167 _LOG.info(' %s', package)
172 def run(self, command: str, pkg_root: pathlib.Path, **kwargs) -> int:
173 self._mgr = PackageManager(pkg_root.resolve())
174 return getattr(self, command)(**kwargs)
177 def parse_args(argv: List[str] = None) -> argparse.Namespace:
178 parser = argparse.ArgumentParser("Manage packages.")
184 default=(pathlib.Path(os.environ['_PW_ACTUAL_ENVIRONMENT_ROOT']) /
187 subparsers = parser.add_subparsers(dest='command', required=True)
188 install = subparsers.add_parser('install')
189 install.add_argument('--force', '-f', action='store_true')
190 remove = subparsers.add_parser('remove')
191 status = subparsers.add_parser('status')
192 for cmd in (install, remove, status):
193 cmd.add_argument('package', choices=_PACKAGES.keys())
194 _ = subparsers.add_parser('list')
195 return parser.parse_args(argv)
199 return PackageManagerCLI().run(**kwargs)