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    module_names = []
56    for target in targets:
57        mod_info = cc_module_info.get_module_info(target)
58        clion_gen = clion_project_file_gen.CLionProjectFileGenerator(mod_info)
59        clion_gen.generate_cmakelists_file()
60        module_names.append(mod_info[constant.KEY_MODULE_NAME])
61    rel_path = os.path.relpath(parent_dir, common_util.get_android_root_dir())
62    # If the relative path is Android root, we won't show '.' in the path.
63    if rel_path == '.':
64        rel_path = ''
65    return clion_project_file_gen.generate_base_cmakelists_file(
66        cc_module_info, rel_path, module_names)
67
68
69def _find_parent(abs_path, current_parent):
70    """Finds parent directory of two directories.
71
72    Args:
73        abs_path: A string of an absolute path of a directory.
74        current_parent: A string of the absolute path of current parent
75                        directory. It could be None int the beginning.
76
77    Returns:
78        A string of new parent directory.
79    """
80    if not current_parent:
81        return abs_path
82    if common_util.is_source_under_relative_path(abs_path, current_parent):
83        return current_parent
84    if common_util.is_source_under_relative_path(current_parent, abs_path):
85        return abs_path
86    return _find_parent(
87        os.path.dirname(abs_path), os.path.dirname(current_parent))
88
89
90def _filter_out_modules(targets, filter_func):
91    """Filters out target from targets if it passes the filter function.
92
93    Args:
94        targets: A list of targets to be analyzed.
95        filter_func: A filter function reference.
96
97    Returns:
98        A tuple of a list of filtered module target and a list of lefted
99        targets.
100    """
101    jtargets = []
102    lefts = []
103    for target in targets:
104        if filter_func(target):
105            jtargets.append(target)
106            continue
107        lefts.append(target)
108    return jtargets, lefts
109
110
111def _get_merged_native_target(cc_module_info, targets):
112    """Gets merged native parent target from original native targets.
113
114    If a target is a module, we put it directly into the new list. If a traget
115    is a path we put all the native modules under the path into the new list.
116
117    Args:
118        cc_module_info: A ModuleInfo instance contains the data of
119                        module_bp_cc_deps.json.
120        targets: A list of targets to be merged.
121
122    Returns:
123        A tuple of a string of merged native project's relative path and a list
124        of new targets we have dealt with.
125    """
126    parent_folder = None
127    new_targets = []
128    for target in targets:
129        _, abs_path = common_util.get_related_paths(cc_module_info, target)
130        parent_folder = _find_parent(abs_path, parent_folder)
131        if cc_module_info.is_module(target):
132            new_targets.append(target)
133            continue
134        mod_names = cc_module_info.get_module_names_in_targets_paths([target])
135        new_targets.extend(mod_names)
136    return parent_folder, new_targets
137
138
139def get_native_and_java_projects(atest_module_info, cc_module_info, targets):
140    """Gets native and java projects from targets.
141
142    Separates native and java projects from targets.
143    1. If it's a native module, add it to native projects.
144    2. If it's a java module, add it to java projects.
145    3. Calls _analyze_native_and_java_projects to analyze the remaining targets.
146
147    Args:
148        atest_module_info: A ModuleInfo instance contains the merged data of
149                           module-info.json and module_bp_java_deps.json.
150        cc_module_info: A ModuleInfo instance contains the data of
151                        module_bp_cc_deps.json.
152        targets: A list of targets to be analyzed.
153
154    Returns:
155        A tuple of a list of java build targets and a list of native build
156        targets.
157    """
158    ctargets, lefts = _filter_out_modules(targets, cc_module_info.is_module)
159    jtargets, lefts = _filter_out_modules(lefts, atest_module_info.is_module)
160    path_info = cc_module_info.path_to_module_info
161    jtars, ctars = _analyze_native_and_java_projects(
162        atest_module_info, path_info, lefts)
163    ctargets.extend(ctars)
164    jtargets.extend(jtars)
165    return jtargets, ctargets
166
167
168def _analyze_native_and_java_projects(atest_module_info, path_info, targets):
169    """Analyzes native and java projects from targets.
170
171    Args:
172        atest_module_info: A ModuleInfo instance contains the merged data of
173                           module-info.json and module_bp_java_deps.json.
174        path_info: A dictionary contains native projects' path as key
175                   and module's info dictionary as value.
176        targets: A list of targets to be analyzed.
177
178    Returns:
179        A tuple of a list of java build targets and a list of native build
180        targets.
181    """
182    jtargets = []
183    ctargets = []
184    for target in targets:
185        rel_path, abs_path = common_util.get_related_paths(
186            atest_module_info, target)
187        if _check_java_file_exists(abs_path):
188            jtargets.append(target)
189        if _check_native_project_exists(path_info, rel_path):
190            ctargets.append(target)
191    return jtargets, ctargets
192
193
194def _check_java_file_exists(abs_path):
195    """Checks if any Java files exist in an abs_path directory.
196
197    Args:
198        abs_path: A string of absolute path of a directory to be check.
199
200    Returns:
201        True if any Java files exist otherwise False.
202    """
203    for _, _, filenames in os.walk(abs_path):
204        if fnmatch.filter(filenames, constant.JAVA_FILES):
205            return True
206    return False
207
208
209def _check_native_project_exists(path_to_module_info, rel_path):
210    """Checks if any native project exists in a rel_path directory.
211
212    Args:
213        path_to_module_info: A dictionary contains data of relative path as key
214                             and module info dictionary as value.
215        rel_path: A string of relative path of a directory to be check.
216
217    Returns:
218        True if any native project exists otherwise False.
219    """
220    for path in path_to_module_info:
221        if common_util.is_source_under_relative_path(path, rel_path):
222            return True
223    return False
224