• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright 2018 - 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"""module_info_util
18
19This module receives a module path which is relative to its root directory and
20makes a command to generate two json files, one for mk files and one for bp
21files. Then it will load these two json files into two json dictionaries,
22merge them into one dictionary and return the merged dictionary to its caller.
23
24Example usage:
25merged_dict = generate_merged_module_info()
26"""
27
28import glob
29import logging
30import os
31import sys
32
33from aidegen import constant
34from aidegen.lib import common_util
35from aidegen.lib import errors
36from aidegen.lib import project_config
37
38from atest import atest_utils
39
40_MERGE_NEEDED_ITEMS = [
41    constant.KEY_CLASS,
42    constant.KEY_PATH,
43    constant.KEY_INSTALLED,
44    constant.KEY_DEPENDENCIES,
45    constant.KEY_SRCS,
46    constant.KEY_SRCJARS,
47    constant.KEY_CLASSES_JAR,
48    constant.KEY_TAG,
49    constant.KEY_COMPATIBILITY,
50    constant.KEY_AUTO_TEST_CONFIG,
51    constant.KEY_MODULE_NAME,
52    constant.KEY_TEST_CONFIG
53]
54_INTELLIJ_PROJECT_FILE_EXT = '*.iml'
55_LAUNCH_PROJECT_QUERY = (
56    'There exists an IntelliJ project file: %s. Do you want '
57    'to launch it (yes/No)?')
58_BUILD_BP_JSON_ENV_ON = {
59    constant.GEN_JAVA_DEPS: 'true',
60    constant.GEN_CC_DEPS: 'true',
61    constant.GEN_COMPDB: 'true',
62    constant.GEN_RUST: 'true'
63}
64_GEN_JSON_FAILED = (
65    'Generate new {0} failed, AIDEGen will proceed and reuse the old {1}.')
66_TARGET = 'nothing'
67_LINKFILE_WARNING = (
68    'File {} does not exist and we can not make a symbolic link for it.')
69_RUST_PROJECT_JSON = 'out/soong/rust-project.json'
70
71
72# pylint: disable=dangerous-default-value
73@common_util.back_to_cwd
74@common_util.time_logged
75def generate_merged_module_info(env_on=_BUILD_BP_JSON_ENV_ON):
76    """Generate a merged dictionary.
77
78    Linked functions:
79        _build_bp_info(module_info, project, verbose, skip_build)
80        _get_soong_build_json_dict()
81        _merge_dict(mk_dict, bp_dict)
82
83    Args:
84        env_on: A dictionary of environment settings to be turned on, the
85                default value is _BUILD_BP_JSON_ENV_ON.
86
87    Returns:
88        A merged dictionary from module-info.json and module_bp_java_deps.json.
89    """
90    config = project_config.ProjectConfig.get_instance()
91    module_info = config.atest_module_info
92    projects = config.targets
93    verbose = True
94    skip_build = config.is_skip_build
95    main_project = projects[0] if projects else None
96    _build_bp_info(
97        module_info, main_project, verbose, skip_build, env_on)
98    json_path = common_util.get_blueprint_json_path(
99        constant.BLUEPRINT_JAVA_JSONFILE_NAME)
100    bp_dict = common_util.get_json_dict(json_path)
101    return _merge_dict(module_info.name_to_module_info, bp_dict)
102
103
104def _build_bp_info(module_info, main_project=None, verbose=False,
105                   skip_build=False, env_on=_BUILD_BP_JSON_ENV_ON):
106    """Make nothing to create module_bp_java_deps.json, module_bp_cc_deps.json.
107
108    Use atest build method to build the target 'nothing' by setting env config
109    SOONG_COLLECT_JAVA_DEPS to true to trigger the process of collecting
110    dependencies and generate module_bp_java_deps.json etc.
111
112    Args:
113        module_info: A ModuleInfo instance contains data of module-info.json.
114        main_project: A string of the main project name.
115        verbose: A boolean, if true displays full build output.
116        skip_build: A boolean, if true, skip building if
117                    get_blueprint_json_path(file_name) file exists, otherwise
118                    build it.
119        env_on: A dictionary of environment settings to be turned on, the
120                default value is _BUILD_BP_JSON_ENV_ON.
121
122    Build results:
123        1. Build successfully return.
124        2. Build failed:
125           1) There's no project file, raise BuildFailureError.
126           2) There exists a project file, ask users if they want to
127              launch IDE with the old project file.
128              a) If the answer is yes, return.
129              b) If the answer is not yes, sys.exit(1)
130    """
131    file_paths = _get_generated_json_files(env_on)
132    files_exist = all([os.path.isfile(fpath) for fpath in file_paths])
133    files = '\n'.join(file_paths)
134    if skip_build and files_exist:
135        logging.info('Files:\n%s exist, skipping build.', files)
136        return
137    original_file_mtimes = {f: None for f in file_paths}
138    if files_exist:
139        original_file_mtimes = {f: os.path.getmtime(f) for f in file_paths}
140
141    logging.warning(
142        '\nGenerate files:\n %s by atest build method.', files)
143    build_with_on_cmd = atest_utils.build([_TARGET], verbose, env_on)
144
145    # For Android Rust projects, we need to create a symbolic link to the file
146    # out/soong/rust-project.json to launch the rust projects in IDEs.
147    _generate_rust_project_link()
148
149    if build_with_on_cmd:
150        logging.info('\nGenerate blueprint json successfully.')
151    else:
152        if not all([_is_new_json_file_generated(
153                f, original_file_mtimes[f]) for f in file_paths]):
154            if files_exist:
155                _show_files_reuse_message(file_paths)
156            else:
157                _show_build_failed_message(module_info, main_project)
158
159
160def _get_generated_json_files(env_on=_BUILD_BP_JSON_ENV_ON):
161    """Gets the absolute paths of the files which is going to be generated.
162
163    Determine the files which will be generated by the environment on dictionary
164    and the default blueprint json files' dictionary.
165    The generation of json files depends on env_on. If the env_on looks like,
166    _BUILD_BP_JSON_ENV_ON = {
167        'SOONG_COLLECT_JAVA_DEPS': 'true',
168        'SOONG_COLLECT_CC_DEPS': 'true',
169        'SOONG_GEN_COMPDB': 'true',
170        'SOONG_GEN_RUST_PROJECT': 'true'
171    }
172    We want to generate 4 files: module_bp_java_deps.json,
173    module_bp_cc_deps.json, compile_commands.json and rust-project.json. And in
174    get_blueprint_json_files_relative_dict function, there are 4 json files
175    by default and return a result list of the absolute paths of the existent
176    files.
177
178    Args:
179        env_on: A dictionary of environment settings to be turned on, the
180                default value is _BUILD_BP_JSON_ENV_ON.
181
182    Returns:
183        A list of the absolute paths of the files which is going to be
184        generated.
185    """
186    json_files_dict = common_util.get_blueprint_json_files_relative_dict()
187    file_paths = []
188    for key in env_on:
189        if not env_on[key] == 'true' or key not in json_files_dict:
190            continue
191        file_paths.append(json_files_dict[key])
192    return file_paths
193
194
195def _show_files_reuse_message(file_paths):
196    """Shows the message of build failure but files existing and reusing them.
197
198    Args:
199        file_paths: A list of absolute file paths to be checked.
200    """
201    failed_or_file = ' or '.join(file_paths)
202    failed_and_file = ' and '.join(file_paths)
203    message = _GEN_JSON_FAILED.format(failed_or_file, failed_and_file)
204    print(constant.WARN_MSG.format(
205        common_util.COLORED_INFO('Warning:'), message))
206
207
208def _show_build_failed_message(module_info, main_project=None):
209    """Show build failed message.
210
211    Args:
212        module_info: A ModuleInfo instance contains data of module-info.json.
213        main_project: A string of the main project name.
214    """
215    if main_project:
216        _, main_project_path = common_util.get_related_paths(
217            module_info, main_project)
218        _build_failed_handle(main_project_path)
219
220
221def _is_new_json_file_generated(json_path, original_file_mtime):
222    """Check the new file is generated or not.
223
224    Args:
225        json_path: The path of the json file being to check.
226        original_file_mtime: the original file modified time.
227
228    Returns:
229        A boolean, True if the json_path file is new generated, otherwise False.
230    """
231    if not os.path.isfile(json_path):
232        return False
233    return original_file_mtime != os.path.getmtime(json_path)
234
235
236def _build_failed_handle(main_project_path):
237    """Handle build failures.
238
239    Args:
240        main_project_path: The main project directory.
241
242    Handle results:
243        1) There's no project file, raise BuildFailureError.
244        2) There exists a project file, ask users if they want to
245           launch IDE with the old project file.
246           a) If the answer is yes, return.
247           b) If the answer is not yes, sys.exit(1)
248    """
249    project_file = glob.glob(
250        os.path.join(main_project_path, _INTELLIJ_PROJECT_FILE_EXT))
251    if project_file:
252        query = _LAUNCH_PROJECT_QUERY % project_file[0]
253        input_data = input(query)
254        if not input_data.lower() in ['yes', 'y']:
255            sys.exit(1)
256    else:
257        raise errors.BuildFailureError(
258            'Failed to generate %s.' % common_util.get_blueprint_json_path(
259                constant.BLUEPRINT_JAVA_JSONFILE_NAME))
260
261
262def _merge_module_keys(m_dict, b_dict):
263    """Merge a module's dictionary into another module's dictionary.
264
265    Merge b_dict module data into m_dict.
266
267    Args:
268        m_dict: The module dictionary is going to merge b_dict into.
269        b_dict: Soong build system module dictionary.
270    """
271    for key, b_modules in b_dict.items():
272        m_dict[key] = sorted(list(set(m_dict.get(key, []) + b_modules)))
273
274
275def _copy_needed_items_from(mk_dict):
276    """Shallow copy needed items from Make build system module info dictionary.
277
278    Args:
279        mk_dict: Make build system dictionary is going to be copied.
280
281    Returns:
282        A merged dictionary.
283    """
284    merged_dict = dict()
285    for module in mk_dict.keys():
286        merged_dict[module] = dict()
287        for key in mk_dict[module].keys():
288            if key in _MERGE_NEEDED_ITEMS and mk_dict[module][key] != []:
289                merged_dict[module][key] = mk_dict[module][key]
290    return merged_dict
291
292
293def _merge_dict(mk_dict, bp_dict):
294    """Merge two dictionaries.
295
296    Linked function:
297        _merge_module_keys(m_dict, b_dict)
298
299    Args:
300        mk_dict: Make build system module info dictionary.
301        bp_dict: Soong build system module info dictionary.
302
303    Returns:
304        A merged dictionary.
305    """
306    merged_dict = _copy_needed_items_from(mk_dict)
307    for module in bp_dict.keys():
308        if module not in merged_dict.keys():
309            merged_dict[module] = dict()
310        _merge_module_keys(merged_dict[module], bp_dict[module])
311    return merged_dict
312
313
314def _generate_rust_project_link():
315    """Generates out/soong/rust-project.json symbolic link in Android root."""
316    root_dir = common_util.get_android_root_dir()
317    rust_project = os.path.join(
318        root_dir, common_util.get_blueprint_json_path(
319            constant.RUST_PROJECT_JSON))
320    if not os.path.isfile(rust_project):
321        message = _LINKFILE_WARNING.format(_RUST_PROJECT_JSON)
322        print(constant.WARN_MSG.format(
323            common_util.COLORED_INFO('Warning:'), message))
324        return
325    link_rust = os.path.join(root_dir, constant.RUST_PROJECT_JSON)
326    if os.path.islink(link_rust):
327        os.remove(link_rust)
328    os.symlink(rust_project, link_rust)
329