1# Copyright 2022 The Pigweed Authors 2# 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 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 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 13# the License. 14"""Pip install Pigweed Python packages.""" 15 16import argparse 17from pathlib import Path 18import subprocess 19import sys 20from typing import List, Tuple 21 22try: 23 from pw_build.python_package import load_packages 24except ImportError: 25 # Load from python_package from this directory if pw_build is not available. 26 from python_package import load_packages # type: ignore 27 28 29def _parse_args() -> Tuple[argparse.Namespace, List[str]]: 30 parser = argparse.ArgumentParser(description=__doc__) 31 parser.add_argument( 32 '--python-dep-list-files', 33 type=Path, 34 required=True, 35 help=( 36 'Path to a text file containing the list of Python package ' 37 'metadata json files.' 38 ), 39 ) 40 parser.add_argument( 41 '--gn-packages', 42 required=True, 43 help=( 44 'Comma separated list of GN python package ' 'targets to install.' 45 ), 46 ) 47 parser.add_argument( 48 '--editable-pip-install', 49 action='store_true', 50 help=( 51 'If true run the pip install command with the ' 52 '\'--editable\' option.' 53 ), 54 ) 55 return parser.parse_known_args() 56 57 58class NoMatchingGnPythonDependency(Exception): 59 """An error occurred while processing a Python dependency.""" 60 61 62def main( 63 python_dep_list_files: Path, 64 editable_pip_install: bool, 65 gn_targets: List[str], 66 pip_args: List[str], 67) -> int: 68 """Find matching python packages to pip install.""" 69 pip_target_dirs: List[str] = [] 70 71 py_packages = load_packages([python_dep_list_files], ignore_missing=True) 72 for pkg in py_packages: 73 valid_target = [target in pkg.gn_target_name for target in gn_targets] 74 if not any(valid_target): 75 continue 76 top_level_source_dir = pkg.package_dir 77 pip_target_dirs.append(str(top_level_source_dir.parent.resolve())) 78 79 if not pip_target_dirs: 80 raise NoMatchingGnPythonDependency( 81 'No matching GN Python dependency found to install.\n' 82 'GN Targets to pip install:\n' + '\n'.join(gn_targets) + '\n\n' 83 'Declared Python Dependencies:\n' 84 + '\n'.join(pkg.gn_target_name for pkg in py_packages) 85 + '\n\n' 86 ) 87 88 for target in pip_target_dirs: 89 command_args = [sys.executable, "-m", "pip"] 90 command_args += pip_args 91 if editable_pip_install: 92 command_args.append('--editable') 93 command_args.append(target) 94 95 process = subprocess.run( 96 command_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT 97 ) 98 pip_output = process.stdout.decode() 99 if process.returncode != 0: 100 print(pip_output) 101 return process.returncode 102 return 0 103 104 105if __name__ == '__main__': 106 # Parse this script's args and pass any remaining args to pip. 107 argparse_args, remaining_args_for_pip = _parse_args() 108 109 # Split the comma separated string and remove leading slashes. 110 gn_target_names = [ 111 target.lstrip('/') 112 for target in argparse_args.gn_packages.split(',') 113 if target # The last target may be an empty string. 114 ] 115 116 result = main( 117 python_dep_list_files=argparse_args.python_dep_list_files, 118 editable_pip_install=argparse_args.editable_pip_install, 119 gn_targets=gn_target_names, 120 pip_args=remaining_args_for_pip, 121 ) 122 sys.exit(result) 123