1#!/usr/bin/env python 2# Copyright 2024 The Pigweed Authors 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may not 5# use this file except in compliance with the License. You may obtain a copy of 6# the License at 7# 8# https://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, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations under 14# the License. 15""" 16Zephyr CLI 17 18The 'zephyr' subcommand for the Pigweed CLI provides functionality for 19integrating a Pigweed and Zephyr application. 20""" 21 22import argparse 23import os 24import re 25import subprocess 26from pathlib import Path 27import logging 28import yaml 29 30from pw_env_setup_zephyr.argparser import add_parser_arguments 31 32 33_LOG = logging.getLogger('pw_zephyr.zephyr') 34 35 36def get_parser() -> argparse.ArgumentParser: 37 """Get a parser associated with this command.""" 38 parser = argparse.ArgumentParser( 39 description=__doc__, 40 formatter_class=argparse.RawDescriptionHelpFormatter, 41 ) 42 parser = add_parser_arguments(parser) 43 return parser 44 45 46def maybe_append_remote(remotes: list, name: str, path: Path) -> bool: 47 """Try appending a remote entry if it exists and is valid.""" 48 if not path.exists(): 49 _LOG.debug("'%s' does not exist, can't add '%s'", str(path), name) 50 return False 51 if not path.is_dir(): 52 _LOG.debug("'%s' is not a directory, can't add '%s'", str(path), name) 53 return False 54 55 result = subprocess.run( 56 ['git', 'remote', '-v'], 57 stdout=subprocess.PIPE, 58 cwd=path, 59 ) 60 if result.returncode != 0: 61 _LOG.error("Failed to find remotes for '%s' in '%s'", name, str(path)) 62 return False 63 output = result.stdout.decode('utf-8') 64 _LOG.debug("git remotes=\n%s", output) 65 url_match = re.match( 66 r'^\w+\s+([\w@\:\/\-\.]+)\s+\(fetch\).*$', output, re.MULTILINE 67 ) 68 if not url_match: 69 _LOG.error( 70 "Failed to find (fetch) remote for '%s' in '%s'", name, str(path) 71 ) 72 return False 73 remotes.append( 74 { 75 'name': name, 76 'url-base': url_match.group(1), 77 } 78 ) 79 return True 80 81 82def append_project(projects: list, name: str, path: Path) -> None: 83 """Append a project entry to the list""" 84 result = subprocess.run( 85 ['git', 'log', '--pretty=format:"%H"', '-n', '1'], 86 stdout=subprocess.PIPE, 87 cwd=path, 88 ) 89 if result.returncode != 0: 90 _LOG.error( 91 "Failed to find hash branch for '%s' in '%s'", name, str(path) 92 ) 93 return 94 output: str = result.stdout.decode('utf-8').strip().strip('"') 95 _LOG.debug("git current hash is '%s'", output) 96 if output == '': 97 _LOG.error("'%s' has no commit hash", str(path)) 98 return 99 100 projects.append( 101 { 102 'name': name, 103 'remote': name, 104 'revision': output, 105 'path': str( 106 path.relative_to(os.path.commonpath([path, os.getcwd()])) 107 ), 108 'import': name == 'zephyr', 109 } 110 ) 111 112 113def generate_manifest() -> dict: 114 """Generate a dictionary that matches a West manifest yaml format.""" 115 pw_root_dir = Path(os.environ['PW_ROOT']) 116 zephyr_root_dir = pw_root_dir / 'environment' / 'packages' / 'zephyr' 117 remotes: list = [] 118 projects: list = [] 119 if maybe_append_remote(remotes, 'pigweed', pw_root_dir): 120 append_project(projects, 'pigweed', pw_root_dir) 121 if maybe_append_remote(remotes, 'zephyr', zephyr_root_dir): 122 append_project(projects, 'zephyr', zephyr_root_dir) 123 124 return { 125 'manifest': { 126 'remotes': remotes, 127 'projects': projects, 128 }, 129 } 130 131 132def print_manifest() -> None: 133 """Print the content of a West manifest for the current Pigweed checkout""" 134 _LOG.info(yaml.safe_dump(generate_manifest())) 135 136 137def main() -> None: 138 """Main entry point for the 'pw zephyr' command.""" 139 parser = get_parser() 140 args = parser.parse_args() 141 _LOG.addHandler(logging.StreamHandler()) 142 _LOG.propagate = False 143 if args.verbose: 144 _LOG.setLevel(logging.DEBUG) 145 else: 146 _LOG.setLevel(logging.INFO) 147 148 if args.zephyr_subcommand == 'manifest': 149 print_manifest() 150 else: 151 parser.print_usage() 152 153 154if __name__ == '__main__': 155 main() 156