diff options
Diffstat (limited to 'pw_build/py/pw_build/pip_install_python_deps.py')
-rw-r--r-- | pw_build/py/pw_build/pip_install_python_deps.py | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/pw_build/py/pw_build/pip_install_python_deps.py b/pw_build/py/pw_build/pip_install_python_deps.py new file mode 100644 index 000000000..4768bed9c --- /dev/null +++ b/pw_build/py/pw_build/pip_install_python_deps.py @@ -0,0 +1,122 @@ +# Copyright 2022 The Pigweed Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +"""Pip install Pigweed Python packages.""" + +import argparse +from pathlib import Path +import subprocess +import sys +from typing import List, Tuple + +try: + from pw_build.python_package import load_packages +except ImportError: + # Load from python_package from this directory if pw_build is not available. + from python_package import load_packages # type: ignore + + +def _parse_args() -> Tuple[argparse.Namespace, List[str]]: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--python-dep-list-files', + type=Path, + required=True, + help=( + 'Path to a text file containing the list of Python package ' + 'metadata json files.' + ), + ) + parser.add_argument( + '--gn-packages', + required=True, + help=( + 'Comma separated list of GN python package ' 'targets to install.' + ), + ) + parser.add_argument( + '--editable-pip-install', + action='store_true', + help=( + 'If true run the pip install command with the ' + '\'--editable\' option.' + ), + ) + return parser.parse_known_args() + + +class NoMatchingGnPythonDependency(Exception): + """An error occurred while processing a Python dependency.""" + + +def main( + python_dep_list_files: Path, + editable_pip_install: bool, + gn_targets: List[str], + pip_args: List[str], +) -> int: + """Find matching python packages to pip install.""" + pip_target_dirs: List[str] = [] + + py_packages = load_packages([python_dep_list_files], ignore_missing=True) + for pkg in py_packages: + valid_target = [target in pkg.gn_target_name for target in gn_targets] + if not any(valid_target): + continue + top_level_source_dir = pkg.package_dir + pip_target_dirs.append(str(top_level_source_dir.parent.resolve())) + + if not pip_target_dirs: + raise NoMatchingGnPythonDependency( + 'No matching GN Python dependency found to install.\n' + 'GN Targets to pip install:\n' + '\n'.join(gn_targets) + '\n\n' + 'Declared Python Dependencies:\n' + + '\n'.join(pkg.gn_target_name for pkg in py_packages) + + '\n\n' + ) + + for target in pip_target_dirs: + command_args = [sys.executable, "-m", "pip"] + command_args += pip_args + if editable_pip_install: + command_args.append('--editable') + command_args.append(target) + + process = subprocess.run( + command_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) + pip_output = process.stdout.decode() + if process.returncode != 0: + print(pip_output) + return process.returncode + return 0 + + +if __name__ == '__main__': + # Parse this script's args and pass any remaining args to pip. + argparse_args, remaining_args_for_pip = _parse_args() + + # Split the comma separated string and remove leading slashes. + gn_target_names = [ + target.lstrip('/') + for target in argparse_args.gn_packages.split(',') + if target # The last target may be an empty string. + ] + + result = main( + python_dep_list_files=argparse_args.python_dep_list_files, + editable_pip_install=argparse_args.editable_pip_install, + gn_targets=gn_target_names, + pip_args=remaining_args_for_pip, + ) + sys.exit(result) |