• 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"""AIDEgen
18
19This CLI generates project files for using in IntelliJ, such as:
20    - iml
21    - .idea/compiler.xml
22    - .idea/misc.xml
23    - .idea/modules.xml
24    - .idea/vcs.xml
25    - .idea/.name
26    - .idea/copyright/Apache_2.xml
27    - .idea/copyright/progiles_settings.xml
28
29- Sample usage:
30    - Change directory to AOSP root first.
31    $ cd /user/home/aosp/
32    - Generating project files under packages/apps/Settings folder.
33    $ aidegen packages/apps/Settings
34    or
35    $ aidegen Settings
36    or
37    $ cd packages/apps/Settings;aidegen
38"""
39
40from __future__ import absolute_import
41
42import argparse
43import logging
44import os
45import sys
46import traceback
47
48from aidegen import constant
49from aidegen.lib.android_dev_os import AndroidDevOS
50from aidegen.lib import common_util
51from aidegen.lib.common_util import COLORED_INFO
52from aidegen.lib.common_util import COLORED_PASS
53from aidegen.lib.common_util import is_android_root
54from aidegen.lib.common_util import time_logged
55from aidegen.lib.errors import AIDEgenError
56from aidegen.lib.errors import IDENotExistError
57from aidegen.lib.ide_util import IdeUtil
58from aidegen.lib.aidegen_metrics import starts_asuite_metrics
59from aidegen.lib.aidegen_metrics import ends_asuite_metrics
60from aidegen.lib.module_info_util import generate_module_info_json
61from aidegen.lib.project_file_gen import generate_eclipse_project_files
62from aidegen.lib.project_file_gen import generate_ide_project_files
63from aidegen.lib.project_info import ProjectInfo
64from aidegen.lib.source_locator import multi_projects_locate_source
65
66AIDEGEN_REPORT_LINK = ('To report the AIDEGen tool problem, please use this '
67                       'link: https://goto.google.com/aidegen-bug')
68_NO_LAUNCH_IDE_CMD = """
69Can not find IDE in path: {}, you can:
70    - add IDE executable to your $PATH
71or  - specify the exact IDE executable path by "aidegen -p"
72or  - specify "aidegen -n" to generate project file only
73"""
74
75_CONGRATULATION = COLORED_PASS('CONGRATULATION:')
76_LAUNCH_SUCCESS_MSG = (
77    'IDE launched successfully. Please check your IDE window.')
78_IDE_CACHE_REMINDER_MSG = (
79    'To prevent the existed IDE cache from impacting your IDE dependency '
80    'analysis, please consider to clear IDE caches if necessary. To do that, in'
81    ' IntelliJ IDEA, go to [File > Invalidate Caches / Restart...].')
82
83_SKIP_BUILD_INFO = ('If you are sure the related modules and dependencies have '
84                    'been already built, please try to use command {} to skip '
85                    'the building process.')
86_MAX_TIME = 1
87_SKIP_BUILD_INFO_FUTURE = ''.join([
88    'AIDEGen build time exceeds {} minute(s).\n'.format(_MAX_TIME),
89    _SKIP_BUILD_INFO.rstrip('.'), ' in the future.'
90])
91_SKIP_BUILD_CMD = 'aidegen {} -s'
92_INFO = COLORED_INFO('INFO:')
93_SKIP_MSG = _SKIP_BUILD_INFO_FUTURE.format(
94    COLORED_INFO('aidegen [ module(s) ] -s'))
95_TIME_EXCEED_MSG = '\n{} {}\n'.format(_INFO, _SKIP_MSG)
96_LOG_FORMAT = '%(asctime)s %(filename)s:%(lineno)s:%(levelname)s: %(message)s'
97_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
98
99
100def _parse_args(args):
101    """Parse command line arguments.
102
103    Args:
104        args: A list of arguments.
105
106    Returns:
107        An argparse.Namespace class instance holding parsed args.
108    """
109    parser = argparse.ArgumentParser(
110        description=__doc__,
111        formatter_class=argparse.RawDescriptionHelpFormatter,
112        usage=('aidegen [module_name1 module_name2... '
113               'project_path1 project_path2...]'))
114    parser.required = False
115    parser.add_argument(
116        'targets',
117        type=str,
118        nargs='*',
119        default=[''],
120        help=('Android module name or path.'
121              'e.g. Settings or packages/apps/Settings'))
122    parser.add_argument(
123        '-d',
124        '--depth',
125        type=int,
126        choices=range(10),
127        default=0,
128        help='The depth of module referenced by source.')
129    parser.add_argument(
130        '-v',
131        '--verbose',
132        action='store_true',
133        help='Display DEBUG level logging.')
134    parser.add_argument(
135        '-i',
136        '--ide',
137        default=['j'],
138        help='Launch IDE type, j: IntelliJ, s: Android Studio, e: Eclipse.')
139    parser.add_argument(
140        '-p',
141        '--ide-path',
142        dest='ide_installed_path',
143        help='IDE installed path.')
144    parser.add_argument(
145        '-n', '--no_launch', action='store_true', help='Do not launch IDE.')
146    parser.add_argument(
147        '-r',
148        '--config-reset',
149        dest='config_reset',
150        action='store_true',
151        help='Reset all saved configurations, e.g., preferred IDE version.')
152    parser.add_argument(
153        '-s',
154        '--skip-build',
155        dest='skip_build',
156        action='store_true',
157        help=('Skip building jar or modules that create java files in build '
158              'time, e.g. R/AIDL/Logtags.'))
159    parser.add_argument(
160        '-a',
161        '--android-tree',
162        dest='android_tree',
163        action='store_true',
164        help='Generate whole Android source tree project file for IDE.')
165    return parser.parse_args(args)
166
167
168def _configure_logging(verbose):
169    """Configure the logger.
170
171    Args:
172        verbose: A boolean. If true, display DEBUG level logs.
173    """
174    log_format = _LOG_FORMAT
175    datefmt = _DATE_FORMAT
176    level = logging.DEBUG if verbose else logging.INFO
177    logging.basicConfig(level=level, format=log_format, datefmt=datefmt)
178
179
180def _get_ide_util_instance(args):
181    """Get an IdeUtil class instance for launching IDE.
182
183    Args:
184        args: A list of arguments.
185
186    Returns:
187        A IdeUtil class instance.
188    """
189    if args.no_launch:
190        return None
191    ide_util_obj = IdeUtil(args.ide_installed_path, args.ide[0],
192                           args.config_reset,
193                           AndroidDevOS.MAC == AndroidDevOS.get_os_type())
194    if not ide_util_obj.is_ide_installed():
195        ipath = args.ide_installed_path or ide_util_obj.get_default_path()
196        err = _NO_LAUNCH_IDE_CMD.format(ipath)
197        logging.error(err)
198        raise IDENotExistError(err)
199    return ide_util_obj
200
201
202def _check_skip_build(args):
203    """Check if users skip building target, display the warning message.
204
205    Args:
206        args: A list of arguments.
207    """
208    if not args.skip_build:
209        msg = _SKIP_BUILD_INFO.format(
210            COLORED_INFO(_SKIP_BUILD_CMD.format(' '.join(args.targets))))
211        print('\n{} {}\n'.format(_INFO, msg))
212
213
214def _generate_project_files(ide, projects):
215    """Generate project files by IDE type.
216
217    Args:
218        ide: A character to represent IDE type.
219        projects: A list of ProjectInfo instances.
220    """
221    if ide.lower() == 'e':
222        generate_eclipse_project_files(projects)
223    else:
224        generate_ide_project_files(projects)
225
226
227def _compile_targets_for_whole_android_tree(atest_module_info, targets, cwd):
228    """Compile a list of targets to include whole Android tree in the project.
229
230    Adding the whole Android tree to the project will do two things,
231    1. If current working directory is not Android root, change the target to
232       its relative path to root and change current working directory to root.
233       If we don't change directory it's hard to deal with the whole Android
234       tree together with the sub-project.
235    2. If the whole Android tree target is not in the target list, insert it to
236       the first one.
237
238    Args:
239        atest_module_info: A instance of atest module-info object.
240        targets: A list of targets to be built.
241        cwd: A path of current working directory.
242
243    Returns:
244        A list of targets after adjustment.
245    """
246    new_targets = []
247    if is_android_root(cwd):
248        new_targets = list(targets)
249    else:
250        for target in targets:
251            _, abs_path = common_util.get_related_paths(atest_module_info,
252                                                        target)
253            rel_path = os.path.relpath(abs_path, constant.ANDROID_ROOT_PATH)
254            new_targets.append(rel_path)
255        os.chdir(constant.ANDROID_ROOT_PATH)
256
257    if new_targets[0] != '':
258        new_targets.insert(0, '')
259    return new_targets
260
261
262def _launch_ide(ide_util_obj, project_absolute_path):
263    """Launch IDE through ide_util instance.
264
265    To launch IDE,
266    1. Set IDE config.
267    2. For IntelliJ, use .idea as open target is better than .iml file,
268       because open the latter is like to open a kind of normal file.
269    3. Show _LAUNCH_SUCCESS_MSG to remind users IDE being launched.
270
271    Args:
272        ide_util_obj: An ide_util instance.
273        project_absolute_path: A string of project absolute path.
274    """
275    ide_util_obj.config_ide()
276    ide_util_obj.launch_ide(project_absolute_path)
277    print('\n{} {}\n'.format(_CONGRATULATION, _LAUNCH_SUCCESS_MSG))
278
279
280def _check_whole_android_tree(atest_module_info, targets, android_tree):
281    """Check if it's a building project file for the whole Android tree.
282
283    The rules:
284    1. If users command aidegen under Android root, e.g.,
285       root$ aidegen
286       that implies users would like to launch the whole Android tree, AIDEGen
287       should set the flag android_tree True.
288    2. If android_tree is True, add whole Android tree to the project.
289
290    Args:
291        atest_module_info: A instance of atest module-info object.
292        targets: A list of targets to be built.
293        android_tree: A boolean, True if it's a whole Android tree case,
294                      otherwise False.
295
296    Returns:
297        A list of targets to be built.
298    """
299    cwd = os.getcwd()
300    if not android_tree and is_android_root(cwd) and targets == ['']:
301        android_tree = True
302    new_targets = targets
303    if android_tree:
304        new_targets = _compile_targets_for_whole_android_tree(
305            atest_module_info, targets, cwd)
306    return new_targets
307
308
309@time_logged(message=_TIME_EXCEED_MSG, maximum=_MAX_TIME)
310def main_with_message(args):
311    """Main entry with skip build message.
312
313    Args:
314        args: A list of system arguments.
315    """
316    aidegen_main(args)
317
318
319@time_logged
320def main_without_message(args):
321    """Main entry without skip build message.
322
323    Args:
324        args: A list of system arguments.
325    """
326    aidegen_main(args)
327
328
329# pylint: disable=broad-except
330def main(argv):
331    """Main entry.
332
333    Try to generates project files for using in IDE.
334
335    Args:
336        argv: A list of system arguments.
337    """
338    exit_code = constant.EXIT_CODE_NORMAL
339    try:
340        args = _parse_args(argv)
341        _configure_logging(args.verbose)
342        starts_asuite_metrics()
343        if args.skip_build:
344            main_without_message(args)
345        else:
346            main_with_message(args)
347    except BaseException as err:
348        exit_code = constant.EXIT_CODE_EXCEPTION
349        _, exc_value, exc_traceback = sys.exc_info()
350        if isinstance(err, AIDEgenError):
351            exit_code = constant.EXIT_CODE_AIDEGEN_EXCEPTION
352        # Filter out sys.Exit(0) case, which is not an exception case.
353        if isinstance(err, SystemExit) and exc_value.code == 0:
354            exit_code = constant.EXIT_CODE_NORMAL
355    finally:
356        if exit_code is not constant.EXIT_CODE_NORMAL:
357            error_message = str(exc_value)
358            traceback_list = traceback.format_tb(exc_traceback)
359            traceback_list.append(error_message)
360            traceback_str = ''.join(traceback_list)
361            # print out the trackback message for developers to debug
362            print(traceback_str)
363            ends_asuite_metrics(exit_code, traceback_str, error_message)
364        else:
365            ends_asuite_metrics(exit_code)
366
367
368def aidegen_main(args):
369    """AIDEGen main entry.
370
371    Try to generates project files for using in IDE.
372
373    Args:
374        args: A list of system arguments.
375    """
376    # Pre-check for IDE relevant case, then handle dependency graph job.
377    ide_util_obj = _get_ide_util_instance(args)
378    _check_skip_build(args)
379    atest_module_info = common_util.get_atest_module_info(args.targets)
380    targets = _check_whole_android_tree(atest_module_info, args.targets,
381                                        args.android_tree)
382    ProjectInfo.modules_info = generate_module_info_json(
383        atest_module_info, targets, args.verbose, args.skip_build)
384    projects = ProjectInfo.generate_projects(atest_module_info, targets)
385    multi_projects_locate_source(projects, args.verbose, args.depth,
386                                 constant.IDE_NAME_DICT[args.ide[0]],
387                                 args.skip_build)
388    _generate_project_files(args.ide[0], projects)
389    if ide_util_obj:
390        _launch_ide(ide_util_obj, projects[0].project_absolute_path)
391
392
393if __name__ == '__main__':
394    try:
395        main(sys.argv[1:])
396    finally:
397        print('\n{0} {1}\n\n{0} {2}\n'.format(_INFO, AIDEGEN_REPORT_LINK,
398                                              _IDE_CACHE_REMINDER_MSG))
399