• 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_module_info_json(atest_module_info, project, verbose)
26"""
27
28import glob
29import json
30import logging
31import os
32import subprocess
33import sys
34
35from aidegen import constant
36from aidegen.lib.common_util import COLORED_INFO
37from aidegen.lib.common_util import time_logged
38from aidegen.lib.common_util import get_related_paths
39from aidegen.lib import errors
40
41_BLUEPRINT_JSONFILE_NAME = 'module_bp_java_deps.json'
42_KEY_CLS = 'class'
43_KEY_PATH = 'path'
44_KEY_INS = 'installed'
45_KEY_DEP = 'dependencies'
46_KEY_SRCS = 'srcs'
47_MERGE_NEEDED_ITEMS = [_KEY_CLS, _KEY_PATH, _KEY_INS, _KEY_DEP, _KEY_SRCS]
48_INTELLIJ_PROJECT_FILE_EXT = '*.iml'
49_LAUNCH_PROJECT_QUERY = (
50    'There exists an IntelliJ project file: %s. Do you want '
51    'to launch it (yes/No)?')
52_GENERATE_JSON_COMMAND = ('SOONG_COLLECT_JAVA_DEPS=false make nothing;'
53                          'SOONG_COLLECT_JAVA_DEPS=true make nothing')
54
55
56@time_logged
57def generate_module_info_json(module_info, projects, verbose, skip_build=False):
58    """Generate a merged json dictionary.
59
60    Change directory to ANDROID_ROOT_PATH before making _GENERATE_JSON_COMMAND
61    to avoid command error: "make: *** No rule to make target 'nothing'.  Stop."
62    and change back to current directory after command completed.
63
64    Linked functions:
65        _build_target(project, verbose)
66        _get_soong_build_json_dict()
67        _merge_json(mk_dict, bp_dict)
68
69    Args:
70        module_info: A ModuleInfo instance contains data of module-info.json.
71        projects: A list of project names.
72        verbose: A boolean, if true displays full build output.
73        skip_build: A boolean, if true skip building _BLUEPRINT_JSONFILE_NAME if
74                    it exists, otherwise build it.
75
76    Returns:
77        A tuple of Atest module info instance and a merged json dictionary.
78    """
79    cwd = os.getcwd()
80    os.chdir(constant.ANDROID_ROOT_PATH)
81    _build_target([_GENERATE_JSON_COMMAND], projects[0], module_info, verbose,
82                  skip_build)
83    os.chdir(cwd)
84    bp_dict = _get_soong_build_json_dict()
85    return _merge_json(module_info.name_to_module_info, bp_dict)
86
87
88def _build_target(cmd, main_project, module_info, verbose, skip_build=False):
89    """Make nothing to generate module_bp_java_deps.json.
90
91    We build without environment setting SOONG_COLLECT_JAVA_DEPS and then build
92    with environment setting SOONG_COLLECT_JAVA_DEPS. In this way we can trigger
93    the process of collecting dependencies and generating
94    module_bp_java_deps.json.
95
96    Args:
97        cmd: A string list, build command.
98        main_project: The main project name.
99        module_info: A ModuleInfo instance contains data of module-info.json.
100        verbose: A boolean, if true displays full build output.
101        skip_build: A boolean, if true skip building _BLUEPRINT_JSONFILE_NAME if
102                    it exists, otherwise build it.
103
104    Build results:
105        1. Build successfully return.
106        2. Build failed:
107           1) There's no project file, raise BuildFailureError.
108           2) There exists a project file, ask users if they want to
109              launch IDE with the old project file.
110              a) If the answer is yes, return.
111              b) If the answer is not yes, sys.exit(1)
112    """
113    json_path = _get_blueprint_json_path()
114    original_json_mtime = None
115    if os.path.isfile(json_path):
116        if skip_build:
117            logging.info('%s file exists, skipping build.',
118                         _BLUEPRINT_JSONFILE_NAME)
119            return
120        original_json_mtime = os.path.getmtime(json_path)
121    try:
122        if verbose:
123            full_env_vars = os.environ.copy()
124            subprocess.check_call(
125                cmd, stderr=subprocess.STDOUT, env=full_env_vars, shell=True)
126        else:
127            subprocess.check_call(cmd, shell=True)
128        logging.info('Build successfully: %s.', cmd)
129    except subprocess.CalledProcessError:
130        if not _is_new_json_file_generated(json_path, original_json_mtime):
131            if os.path.isfile(json_path):
132                message = ('Generate new {0} failed, AIDEGen will proceed and '
133                           'reuse the old {0}.'.format(json_path))
134                print('\n{} {}\n'.format(COLORED_INFO('Warning:'), message))
135        else:
136            _, main_project_path = get_related_paths(module_info, main_project)
137            _build_failed_handle(main_project_path)
138
139
140def _is_new_json_file_generated(json_path, original_file_mtime):
141    """Check the new file is generated or not.
142
143    Args:
144        json_path: The path of the json file being to check.
145        original_file_mtime: the original file modified time.
146    """
147    if not original_file_mtime:
148        return os.path.isfile(json_path)
149    return original_file_mtime != os.path.getmtime(json_path)
150
151
152def _build_failed_handle(main_project_path):
153    """Handle build failures.
154
155    Args:
156        main_project_path: The main project directory.
157
158    Handle results:
159        1) There's no project file, raise BuildFailureError.
160        2) There exists a project file, ask users if they want to
161           launch IDE with the old project file.
162           a) If the answer is yes, return.
163           b) If the answer is not yes, sys.exit(1)
164    """
165    project_file = glob.glob(
166        os.path.join(main_project_path, _INTELLIJ_PROJECT_FILE_EXT))
167    if project_file:
168        query = (_LAUNCH_PROJECT_QUERY) % project_file[0]
169        input_data = input(query)
170        if not input_data.lower() in ['yes', 'y']:
171            sys.exit(1)
172    else:
173        raise errors.BuildFailureError(
174            'Failed to generate %s.' % _get_blueprint_json_path())
175
176
177def _get_soong_build_json_dict():
178    """Load a json file from path and convert it into a json dictionary.
179
180    Returns:
181        A json dictionary.
182    """
183    json_path = _get_blueprint_json_path()
184    try:
185        with open(json_path) as jfile:
186            json_dict = json.load(jfile)
187            return json_dict
188    except IOError as err:
189        raise errors.JsonFileNotExistError(
190            '%s does not exist, error: %s.' % (json_path, err))
191
192
193def _get_blueprint_json_path():
194    """Assemble the path of blueprint json file.
195
196    Returns:
197        Blueprint json path.
198    """
199    return os.path.join(constant.SOONG_OUT_DIR_PATH, _BLUEPRINT_JSONFILE_NAME)
200
201
202def _merge_module_keys(m_dict, b_dict):
203    """Merge a module's json dictionary into another module's json dictionary.
204
205    Args:
206        m_dict: The module dictionary is going to merge b_dict into.
207        b_dict: Soong build system module dictionary.
208    """
209    for key, b_modules in b_dict.items():
210        m_dict[key] = sorted(list(set(m_dict.get(key, []) + b_modules)))
211
212
213def _copy_needed_items_from(mk_dict):
214    """Shallow copy needed items from Make build system part json dictionary.
215
216    Args:
217        mk_dict: Make build system json dictionary is going to be copyed.
218
219    Returns:
220        A merged json dictionary.
221    """
222    merged_dict = dict()
223    for module in mk_dict.keys():
224        merged_dict[module] = dict()
225        for key in mk_dict[module].keys():
226            if key in _MERGE_NEEDED_ITEMS and mk_dict[module][key] != []:
227                merged_dict[module][key] = mk_dict[module][key]
228    return merged_dict
229
230
231def _merge_json(mk_dict, bp_dict):
232    """Merge two json dictionaries.
233
234    Linked function:
235        _merge_module_keys(m_dict, b_dict)
236
237    Args:
238        mk_dict: Make build system part json dictionary.
239        bp_dict: Soong build system part json dictionary.
240
241    Returns:
242        A merged json dictionary.
243    """
244    merged_dict = _copy_needed_items_from(mk_dict)
245    for module in bp_dict.keys():
246        if not module in merged_dict.keys():
247            merged_dict[module] = dict()
248        _merge_module_keys(merged_dict[module], bp_dict[module])
249    return merged_dict
250