1#!/usr/bin/env python3
2#
3# Copyright 2019 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""native_util
18
19This module has a collection of functions that provide helper functions for
20launching native projects in IDE.
21"""
22
23import fnmatch
24import os
25
26from aidegen import constant
27from aidegen.lib import clion_project_file_gen
28from aidegen.lib import common_util
29from aidegen.lib import native_module_info
30
31
32def generate_clion_projects(targets):
33    """Generates CLion projects by targets.
34
35    Generates base CLion project file in the common parent directory of the
36    targets.
37
38    Usages:
39        >>>aidegen frameworks/native/libs/ui frameworks/native/lib/gui
40        the base will be generated in,
41            out/development/ide/clion/frameworks/native/libs/
42        >>>aidegen frameworks/native/libs/ui art/libnativeloader
43        the base will be generated in,
44            out/development/ide/clion/
45        but we expect normally native devs rarely use it in this way.
46
47    Args:
48        targets: A list of targets to check and generate their native projects.
49
50    Returns:
51        A symbolic link CLion project file path.
52    """
53    cc_module_info = native_module_info.NativeModuleInfo()
54    parent_dir, targets = _get_merged_native_target(cc_module_info, targets)
55    rel_path = os.path.relpath(parent_dir, common_util.get_android_root_dir())
56    # If the relative path is Android root, we won't show '.' in the path.
57    if rel_path == '.':
58        rel_path = ''
59    module_names = []
60    for target in targets:
61        mod_info = cc_module_info.get_module_info(target)
62        clion_gen = clion_project_file_gen.CLionProjectFileGenerator(
63            mod_info, rel_path)
64        clion_gen.generate_cmakelists_file()
65        module_names.append(mod_info[constant.KEY_MODULE_NAME])
66    return clion_project_file_gen.generate_base_cmakelists_file(
67        cc_module_info, rel_path, module_names)
68
69
70def _find_parent(abs_path, current_parent):
71    """Finds parent directory of two directories.
72
73    Args:
74        abs_path: A string of an absolute path of a directory.
75        current_parent: A string of the absolute path of current parent
76                        directory. It could be None int the beginning.
77
78    Returns:
79        A string of new parent directory.
80    """
81    if not current_parent:
82        return abs_path
83    if common_util.is_source_under_relative_path(abs_path, current_parent):
84        return current_parent
85    if common_util.is_source_under_relative_path(current_parent, abs_path):
86        return abs_path
87    return _find_parent(
88        os.path.dirname(abs_path), os.path.dirname(current_parent))
89
90
91def _filter_out_modules(targets, filter_func):
92    """Filters out target from targets if it passes the filter function.
93
94    Args:
95        targets: A list of targets to be analyzed.
96        filter_func: A filter function reference.
97
98    Returns:
99        A tuple of a list of filtered module target and a list of lefted
100        targets.
101    """
102    jtargets = []
103    lefts = []
104    for target in targets:
105        if filter_func(target):
106            jtargets.append(target)
107            continue
108        lefts.append(target)
109    return jtargets, lefts
110
111
112def _get_merged_native_target(cc_module_info, targets):
113    """Gets merged native parent target from original native targets.
114
115    If a target is a module, we put it directly into the new list. If a traget
116    is a path we put all the native modules under the path into the new list.
117
118    Args:
119        cc_module_info: A ModuleInfo instance contains the data of
120                        module_bp_cc_deps.json.
121        targets: A list of targets to be merged.
122
123    Returns:
124        A tuple of a string of merged native project's relative path and a list
125        of new targets we have dealt with.
126    """
127    parent_folder = None
128    new_targets = []
129    for target in targets:
130        _, abs_path = common_util.get_related_paths(cc_module_info, target)
131        parent_folder = _find_parent(abs_path, parent_folder)
132        if cc_module_info.is_module(target):
133            new_targets.append(target)
134            continue
135        mod_names = cc_module_info.get_module_names_in_targets_paths([target])
136        new_targets.extend(mod_names)
137    return parent_folder, new_targets
138
139
140def get_native_and_java_projects(atest_module_info, cc_module_info, targets):
141    """Gets native and java projects from targets.
142
143    Separates native and java projects from targets.
144    1. If it's a native module, add it to native projects.
145    2. If it's a java module, add it to java projects.
146    3. Calls _analyze_native_and_java_projects to analyze the remaining targets.
147
148    Args:
149        atest_module_info: A ModuleInfo instance contains the merged data of
150                           module-info.json and module_bp_java_deps.json.
151        cc_module_info: A ModuleInfo instance contains the data of
152                        module_bp_cc_deps.json.
153        targets: A list of targets to be analyzed.
154
155    Returns:
156        A tuple of a list of java build targets and a list of native build
157        targets.
158    """
159    ctargets, lefts = _filter_out_modules(targets, cc_module_info.is_module)
160    jtargets, lefts = _filter_out_modules(lefts, atest_module_info.is_module)
161    path_info = cc_module_info.path_to_module_info
162    jtars, ctars = _analyze_native_and_java_projects(
163        atest_module_info, path_info, lefts)
164    ctargets.extend(ctars)
165    jtargets.extend(jtars)
166    return jtargets, ctargets
167
168
169def _analyze_native_and_java_projects(atest_module_info, path_info, targets):
170    """Analyzes native and java projects from targets.
171
172    Args:
173        atest_module_info: A ModuleInfo instance contains the merged data of
174                           module-info.json and module_bp_java_deps.json.
175        path_info: A dictionary contains native projects' path as key
176                   and module's info dictionary as value.
177        targets: A list of targets to be analyzed.
178
179    Returns:
180        A tuple of a list of java build targets and a list of native build
181        targets.
182    """
183    jtargets = []
184    ctargets = []
185    for target in targets:
186        rel_path, abs_path = common_util.get_related_paths(
187            atest_module_info, target)
188        if _check_java_file_exists(abs_path):
189            jtargets.append(target)
190        if _check_native_project_exists(path_info, rel_path):
191            ctargets.append(target)
192    return jtargets, ctargets
193
194
195def _check_java_file_exists(abs_path):
196    """Checks if any Java files exist in an abs_path directory.
197
198    Args:
199        abs_path: A string of absolute path of a directory to be check.
200
201    Returns:
202        True if any Java files exist otherwise False.
203    """
204    for _, _, filenames in os.walk(abs_path):
205        if fnmatch.filter(filenames, constant.JAVA_FILES):
206            return True
207    return False
208
209
210def _check_native_project_exists(path_to_module_info, rel_path):
211    """Checks if any native project exists in a rel_path directory.
212
213    Args:
214        path_to_module_info: A dictionary contains data of relative path as key
215                             and module info dictionary as value.
216        rel_path: A string of relative path of a directory to be check.
217
218    Returns:
219        True if any native project exists otherwise False.
220    """
221    for path in path_to_module_info:
222        if common_util.is_source_under_relative_path(path, rel_path):
223            return True
224    return False
225