1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright (c) 2024 Huawei Device Co., Ltd.
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
16import os
17import json
18import re
19from typing import List, Tuple
20
21if __name__ == '__main__':
22    from basic_tool import BasicTool
23else:
24    from pkgs.basic_tool import BasicTool
25
26
27class GnCommonTool:
28    """
29    处理BUILD.gn文件的通用方法
30    """
31
32    @classmethod
33    def is_gn_variable(cls, target: str, has_quote: bool = True):
34        """
35        判断target是否是gn中的变量:
36        规则:如果是有引号的模式,则没有引号均认为是变量,有引号的情况下,如有是"$xxx"的模式,则认为xxx是变量;如果是无引号模式,则只要$开头就认为是变量
37        b = "xxx"
38        c = b
39        c = "${b}"
40        "$p"
41        """
42        target = target.strip()
43        if not has_quote:
44            return target.startswith("$")
45        if target.startswith('"') and target.endswith('"'):
46            target = target.strip('"')
47            if target.startswith("${") and target.endswith("}"):
48                return True
49            elif target.startswith("$"):
50                return True
51            return False
52        else:
53            return True
54
55    # 给__find_variables_in_gn用的,减少io
56    __var_val_mem_dict = dict()
57
58    @classmethod
59    def find_variables_in_gn(cls, var_name_tuple: tuple, path: str, stop_tail: str = "home") -> tuple:
60        """
61        同时查找多个gn变量的值
62        var_name_tuple:变量名的tuple,变量名应是未经过处理后的,如:
63        xxx
64        "${xxx}"
65        "$xxx"
66        """
67
68        if os.path.isfile(path):
69            path = os.path.split(path)[0]
70        var_val_dict = dict()
71        not_found_count = len(var_name_tuple)
72        for var in var_name_tuple:
73            val = GnCommonTool.__var_val_mem_dict.get(var)
74            if val is not None:
75                not_found_count -= 1
76            var_val_dict[var] = val
77        while not path.endswith(stop_tail) and not_found_count != 0:
78            for v in var_name_tuple:
79                cmd = r"grep -Ern '^( *){} *= *\".*?\"' --include=*.gn* {}| grep -Ev '\$' | head -n 1 | grep -E '\".*\"' -wo".format(
80                    v.strip('"').lstrip("${").rstrip('}'), path)
81                output = os.popen(cmd).read().strip().strip('"')
82                if len(output) != 0:
83                    not_found_count -= 1
84                    var_val_dict[v] = output
85                    GnCommonTool.__var_val_mem_dict[v] = output
86            path = os.path.split(path)[0]
87        return tuple(var_val_dict.values())
88
89    @classmethod
90    def find_part_subsystem(cls, gn_file: str, project_path: str) -> tuple:
91        """
92        查找gn_file对应的part_name和subsystem
93        如果在gn中找不到,就到bundle.json中去找
94        """
95        part_var_flag = False  # 标识这个变量从gn中取出的原始值是不是变量
96        subsystem_var_flag = False
97        var_list = list()
98        part_name_pattern = r"part_name *=\s*\S*"
99        subsystem_pattern = r"subsystem_name *=\s*\S*"
100        meta_grep_pattern = "grep -E '{}' {} | head -n 1"
101        part_cmd = meta_grep_pattern.format(part_name_pattern, gn_file)
102        subsystem_cmd = meta_grep_pattern.format(subsystem_pattern, gn_file)
103
104        part_name, subsystem_name = cls._parse_part_subsystem(part_var_flag, subsystem_var_flag,
105                                                              var_list, part_cmd, subsystem_cmd, gn_file, project_path)
106        if part_name and subsystem_name:
107            return part_name, subsystem_name
108        # 如果有一个没有找到,就要一层层去找bundle.json文件
109        t_part_name, t_subsystem_name = cls.__find_part_subsystem_from_bundle(
110            gn_file, stop_tail=project_path)
111        if t_part_name:
112            part_name = t_part_name
113        if t_subsystem_name:
114            subsystem_name = t_subsystem_name
115        return part_name, subsystem_name
116
117    @classmethod
118    def __find_part_subsystem_from_bundle(cls, gnpath: str, stop_tail: str = "home") -> tuple:
119        """
120        根据BUILD.gn的全路径,一层层往上面查找bundle.json文件,
121        并从bundle.json中查找part_name和subsystem
122        """
123        filename = "bundle.json"
124        part_name = None
125        subsystem_name = None
126        if stop_tail not in gnpath:
127            return part_name, subsystem_name
128        if os.path.isfile(gnpath):
129            gnpath = os.path.split(gnpath)[0]
130        while not gnpath.endswith(stop_tail):
131            bundle_path = os.path.join(gnpath, filename)
132            if not os.path.isfile(bundle_path):  # 如果该文件不在该目录下
133                gnpath = os.path.split(gnpath)[0]
134                continue
135            with open(bundle_path, 'r', encoding='utf-8') as f:
136                content = json.load(f)
137                try:
138                    part_name = content["component"]["name"]
139                    subsystem_name = content["component"]["subsystem"]
140                except KeyError:
141                    ...
142                finally:
143                    break
144        part_name = None if (part_name is not None and len(
145            part_name) == 0) else part_name
146        subsystem_name = None if (subsystem_name is not None and len(
147            subsystem_name) == 0) else subsystem_name
148        return part_name, subsystem_name
149
150    @classmethod
151    def _parse_part_subsystem(cls, part_var_flag: bool, subsystem_var_flag: bool, var_list: List[str], part_cmd: str,
152                              subsystem_cmd: str, gn_file: str, project_path: str) -> Tuple[str, str]:
153        part_name = subsystem_name = None
154        part = os.popen(part_cmd).read().strip()
155        if len(part) != 0:
156            part = part.split('=')[-1].strip()
157            if GnCommonTool.is_gn_variable(part):
158                part_var_flag = True
159                var_list.append(part)
160            else:
161                part_name = part.strip('"')
162                if len(part_name) == 0:
163                    part_name = None
164        subsystem = os.popen(subsystem_cmd).read().strip()
165        if len(subsystem) != 0:  # 这里是只是看有没有grep到关键字
166            subsystem = subsystem.split('=')[-1].strip()
167            if GnCommonTool.is_gn_variable(subsystem):
168                subsystem_var_flag = True
169                var_list.append(subsystem)
170            else:
171                subsystem_name = subsystem.strip('"')
172                if len(subsystem_name) == 0:
173                    subsystem_name = None
174        if part_var_flag and subsystem_var_flag:
175            part_name, subsystem_name = GnCommonTool.find_variables_in_gn(
176                tuple(var_list), gn_file, project_path)
177        elif part_var_flag:
178            t = GnCommonTool.find_variables_in_gn(
179                tuple(var_list), gn_file, project_path)[0]
180            part_name = t if t is not None and len(t) != 0 else part_name
181        elif subsystem_var_flag:
182            t = GnCommonTool.find_variables_in_gn(
183                tuple(var_list), gn_file, project_path)[0]
184            subsystem_name = t if t is not None and len(
185                t) != 0 else subsystem_name
186        return part_name, subsystem_name
187
188
189class GnVariableParser:
190    @classmethod
191    def string_parser(cls, var: str, content: str) -> str:
192        """
193        解析值为字符串的变量,没有对引号进行去除,如果是a = b这种b为变量的,则无法匹配
194        :param content: 要进行解析的内容
195        :param var: 变量名
196        :return: 变量值[str]
197        """
198        result = BasicTool.re_group_1(
199            content, r"{} *= *[\n]?(\".*?\")".format(var), flags=re.S | re.M)
200        return result
201
202    @classmethod
203    def list_parser(cls, var: str, content: str) -> List[str]:
204        """
205        解析值为列表的变量,list的元素必须全为数字或字符串,且没有对引号进行去除,如果是a = b这种b为变量的,则无法匹配
206        :param var: 变量名
207        :param content: 要进行
208        :return: 变量值[List]
209        """
210        result = BasicTool.re_group_1(
211            content, r"{} *= *(\[.*?\])".format(var), flags=re.S | re.M)
212        result_list = list()
213        for item in result.lstrip('[').rstrip(']').split('\n'):
214            item = item.strip().strip(',"')
215            if not item:
216                continue
217            result_list.append(item)
218        return result_list
219