• 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/profiles_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 os
44import sys
45import traceback
46
47from pathlib import Path
48
49from aidegen import constant
50from aidegen.lib import aidegen_metrics
51from aidegen.lib import common_util
52from aidegen.lib import eclipse_project_file_gen
53from aidegen.lib import errors
54from aidegen.lib import ide_util
55from aidegen.lib import module_info
56from aidegen.lib import native_module_info
57from aidegen.lib import native_project_info
58from aidegen.lib import native_util
59from aidegen.lib import project_config
60from aidegen.lib import project_file_gen
61from aidegen.lib import project_info
62from aidegen.vscode import vscode_native_project_file_gen
63from aidegen.vscode import vscode_workspace_file_gen
64
65AIDEGEN_REPORT_LINK = ('To report an AIDEGen tool problem, please use this '
66                       'link: https://goto.google.com/aidegen-bug')
67_CONGRATULATIONS = common_util.COLORED_PASS('CONGRATULATIONS:')
68_LAUNCH_SUCCESS_MSG = (
69    'IDE launched successfully. Please check your IDE window.')
70_LAUNCH_ECLIPSE_SUCCESS_MSG = (
71    'The project files .classpath and .project are generated under '
72    '{PROJECT_PATH} and AIDEGen doesn\'t import the project automatically, '
73    'please import the project manually by steps: File -> Import -> select \''
74    'General\' -> \'Existing Projects into Workspace\' -> click \'Next\' -> '
75    'Choose the root directory -> click \'Finish\'.')
76_IDE_CACHE_REMINDER_MSG = (
77    'To prevent the existed IDE cache from impacting your IDE dependency '
78    'analysis, please consider to clear IDE caches if necessary. To do that, '
79    'in IntelliJ IDEA, go to [File > Invalidate Caches -> '
80    'Invalidate and Restart].')
81
82_MAX_TIME = 1
83_SKIP_BUILD_INFO_FUTURE = ''.join([
84    'AIDEGen build time exceeds {} minute(s).\n'.format(_MAX_TIME),
85    project_config.SKIP_BUILD_INFO.rstrip('.'), ' in the future.'
86])
87_INFO = common_util.COLORED_INFO('INFO:')
88_SKIP_MSG = _SKIP_BUILD_INFO_FUTURE.format(
89    common_util.COLORED_INFO('aidegen [ module(s) ] -s'))
90_TIME_EXCEED_MSG = '\n{} {}\n'.format(_INFO, _SKIP_MSG)
91_LAUNCH_CLION_IDES = [
92    constant.IDE_CLION, constant.IDE_INTELLIJ, constant.IDE_ECLIPSE]
93_CHOOSE_LANGUAGE_MSG = ('The scope of your modules contains {} different '
94                        'languages as follows:\n{}\nPlease select the one you '
95                        'would like to implement.\t')
96_LANGUAGE_OPTIONS = [constant.JAVA, constant.C_CPP]
97_NO_ANY_PROJECT_EXIST = 'There is no Java, C/C++ or Rust target.'
98_NO_LANGUAGE_PROJECT_EXIST = 'There is no {} target.'
99_NO_IDE_LAUNCH_PATH = 'Can not find the IDE path : {}'
100
101
102def _parse_args(args):
103    """Parse command line arguments.
104
105    Args:
106        args: A list of arguments.
107
108    Returns:
109        An argparse.Namespace class instance holding parsed args.
110    """
111    parser = argparse.ArgumentParser(
112        description=__doc__,
113        formatter_class=argparse.RawDescriptionHelpFormatter,
114        usage=('aidegen [module_name1 module_name2... '
115               'project_path1 project_path2...]'))
116    parser.required = False
117    parser.add_argument(
118        'targets',
119        type=str,
120        nargs='*',
121        default=[''],
122        help=('Android module name or path.'
123              'e.g. Settings or packages/apps/Settings'))
124    parser.add_argument(
125        '-d',
126        '--depth',
127        type=int,
128        choices=range(10),
129        default=0,
130        help='The depth of module referenced by source.')
131    parser.add_argument(
132        '-v',
133        '--verbose',
134        action='store_true',
135        help='Display DEBUG level logging.')
136    parser.add_argument(
137        '-i',
138        '--ide',
139        default=['u'],
140        help=('Launch IDE type, j: IntelliJ, s: Android Studio, e: Eclipse, '
141              'c: CLion, v: VS Code. The default value is \'u\': undefined.'))
142    parser.add_argument(
143        '-p',
144        '--ide-path',
145        dest='ide_installed_path',
146        help='IDE installed path.')
147    parser.add_argument(
148        '-n', '--no_launch', action='store_true', help='Do not launch IDE.')
149    parser.add_argument(
150        '-r',
151        '--config-reset',
152        dest='config_reset',
153        action='store_true',
154        help='Reset all saved configurations, e.g., preferred IDE version.')
155    parser.add_argument(
156        '-s',
157        '--skip-build',
158        dest='skip_build',
159        action='store_true',
160        help=('Skip building jars or modules that create java files in build '
161              'time, e.g. R/AIDL/Logtags.'))
162    parser.add_argument(
163        '-a',
164        '--android-tree',
165        dest='android_tree',
166        action='store_true',
167        help='Generate whole Android source tree project file for IDE.')
168    parser.add_argument(
169        '-e',
170        '--exclude-paths',
171        dest='exclude_paths',
172        nargs='*',
173        help='Exclude the directories in IDE.')
174    parser.add_argument(
175        '-V',
176        '--version',
177        action='store_true',
178        help='Print aidegen version string.')
179    parser.add_argument(
180        '-l',
181        '--language',
182        default=['u'],
183        help=('Launch IDE with a specific language, j: Java, c: C/C++, r: '
184              'Rust. The default value is \'u\': undefined.'))
185    return parser.parse_args(args)
186
187
188def _generate_project_files(projects):
189    """Generate project files by IDE type.
190
191    Args:
192        projects: A list of ProjectInfo instances.
193    """
194    config = project_config.ProjectConfig.get_instance()
195    if config.ide_name == constant.IDE_ECLIPSE:
196        eclipse_project_file_gen.EclipseConf.generate_ide_project_files(
197            projects)
198    else:
199        project_file_gen.ProjectFileGenerator.generate_ide_project_files(
200            projects)
201
202
203def _launch_ide(ide_util_obj, project_absolute_path):
204    """Launch IDE through ide_util instance.
205
206    To launch IDE,
207    1. Set IDE config.
208    2. For IntelliJ, use .idea as open target is better than .iml file,
209       because open the latter is like to open a kind of normal file.
210    3. Show _LAUNCH_SUCCESS_MSG to remind users IDE being launched.
211
212    Args:
213        ide_util_obj: An ide_util instance.
214        project_absolute_path: A string of project absolute path.
215    """
216    ide_util_obj.config_ide(project_absolute_path)
217    if ide_util_obj.ide_name() == constant.IDE_ECLIPSE:
218        launch_msg = ' '.join([_LAUNCH_SUCCESS_MSG,
219                               _LAUNCH_ECLIPSE_SUCCESS_MSG.format(
220                                   PROJECT_PATH=project_absolute_path)])
221    else:
222        launch_msg = _LAUNCH_SUCCESS_MSG
223    print('\n{} {}\n'.format(_CONGRATULATIONS, launch_msg))
224    print('\n{} {}\n'.format(_INFO, _IDE_CACHE_REMINDER_MSG))
225    # Send the end message to Clearcut server before launching IDE to make sure
226    # the execution time is correct.
227    aidegen_metrics.ends_asuite_metrics(constant.EXIT_CODE_EXCEPTION)
228    ide_util_obj.launch_ide()
229
230
231def _launch_native_projects(ide_util_obj, args, cmakelists):
232    """Launches C/C++ projects with IDE.
233
234    AIDEGen provides the IDE argument for CLion, but there's still an implicit
235    way to launch it. The rules to launch it are:
236    1. If no target IDE, we don't have to launch any IDE for C/C++ project.
237    2. If the target IDE is IntelliJ or Eclipse, we should launch C/C++
238       projects with CLion.
239
240    Args:
241        ide_util_obj: An ide_util instance.
242        args: An argparse.Namespace class instance holding parsed args.
243        cmakelists: A list of CMakeLists.txt file paths.
244    """
245    if not ide_util_obj:
246        return
247    native_ide_util_obj = ide_util_obj
248    ide_name = constant.IDE_NAME_DICT[args.ide[0]]
249    if ide_name in _LAUNCH_CLION_IDES:
250        native_ide_util_obj = ide_util.get_ide_util_instance('c')
251    if native_ide_util_obj:
252        _launch_ide(native_ide_util_obj, ' '.join(cmakelists))
253
254
255def _create_and_launch_java_projects(ide_util_obj, targets):
256    """Launches Android of Java(Kotlin) projects with IDE.
257
258    Args:
259        ide_util_obj: An ide_util instance.
260        targets: A list of build targets.
261    """
262    projects = project_info.ProjectInfo.generate_projects(targets)
263
264    project_info.ProjectInfo.multi_projects_locate_source(projects)
265    _generate_project_files(projects)
266    if ide_util_obj:
267        _launch_ide(ide_util_obj, projects[0].project_absolute_path)
268
269
270def _launch_ide_by_module_contents(args, ide_util_obj, language,
271                                   lang_targets, all_langs=False):
272    """Deals with the suitable IDE launch action.
273
274    The rules of AIDEGen launching IDE with languages are:
275      1. If no IDE or language is specific, the priority of the language is:
276         a) Java
277            aidegen frameworks/base
278            launch Java projects of frameworks/base in IntelliJ.
279         b) C/C++
280            aidegen hardware/interfaces/vibrator/aidl/default
281            launch C/C++ project of hardware/interfaces/vibrator/aidl/default
282            in CLion.
283         c) Rust
284            aidegen external/rust/crates/protobuf
285            launch Rust project of external/rust/crates/protobuf in VS Code.
286      2. If the IDE is specific, launch related projects in the IDE.
287         a) aidegen frameworks/base -i j
288            launch Java projects of frameworks/base in IntelliJ.
289            aidegen frameworks/base -i s
290            launch Java projects of frameworks/base in Android Studio.
291            aidegen frameworks/base -i e
292            launch Java projects of frameworks/base in Eclipse.
293         b) aidegen frameworks/base -i c
294            launch C/C++ projects of frameworks/base in CLion.
295         c) aidegen external/rust/crates/protobuf -i v
296            launch Rust project of external/rust/crates/protobuf in VS Code.
297      3. If the language is specific, launch relative language projects in the
298         relative IDE.
299         a) aidegen frameworks/base -l j
300            launch Java projects of frameworks/base in IntelliJ.
301         b) aidegen frameworks/base -l c
302            launch C/C++ projects of frameworks/base in CLion.
303         c) aidegen external/rust/crates/protobuf -l r
304            launch Rust projects of external/rust/crates/protobuf in VS Code.
305      4. Both of the IDE and language are specific, launch the IDE with the
306         relative language projects. If the IDE conflicts with the language, the
307         IDE is prior to the language.
308         a) aidegen frameworks/base -i j -l j
309            launch Java projects of frameworks/base in IntelliJ.
310         b) aidegen frameworks/base -i s -l c
311            launch C/C++ projects of frameworks/base in Android Studio.
312         c) aidegen frameworks/base -i c -l j
313            launch Java projects of frameworks/base in CLion.
314
315    Args:
316        args: A list of system arguments.
317        ide_util_obj: An ide_util instance.
318        language: A string of the language to be edited in the IDE.
319        lang_targets: A dict contains None or list of targets of different
320            languages. E.g.
321            {
322               'Java': ['modules1', 'modules2'],
323               'C/C++': ['modules3'],
324               'Rust': None,
325               ...
326            }
327        all_langs: A boolean, True to launch all languages else False.
328    """
329    if lang_targets is None:
330        print(constant.WARN_MSG.format(
331            common_util.COLORED_INFO('Warning:'), _NO_ANY_PROJECT_EXIST))
332        return
333
334    targets = lang_targets
335    java_list = targets[constant.JAVA] if constant.JAVA in targets else None
336    c_cpp_list = targets[constant.C_CPP] if constant.C_CPP in targets else None
337    rust_list = targets[constant.RUST] if constant.RUST in targets else None
338
339    if all_langs:
340        _launch_vscode(ide_util_obj, project_info.ProjectInfo.modules_info,
341                       java_list, c_cpp_list, rust_list)
342        return
343    if not (java_list or c_cpp_list or rust_list):
344        print(constant.WARN_MSG.format(
345            common_util.COLORED_INFO('Warning:'), _NO_ANY_PROJECT_EXIST))
346        return
347    if language == constant.JAVA:
348        if not java_list:
349            print(constant.WARN_MSG.format(
350                common_util.COLORED_INFO('Warning:'),
351                _NO_LANGUAGE_PROJECT_EXIST.format(constant.JAVA)))
352            return
353        _create_and_launch_java_projects(ide_util_obj, java_list)
354        return
355    if language == constant.C_CPP:
356        if not c_cpp_list:
357            print(constant.WARN_MSG.format(
358                common_util.COLORED_INFO('Warning:'),
359                _NO_LANGUAGE_PROJECT_EXIST.format(constant.C_CPP)))
360            return
361        native_project_info.NativeProjectInfo.generate_projects(c_cpp_list)
362        native_project_file = native_util.generate_clion_projects(c_cpp_list)
363        if native_project_file:
364            _launch_native_projects(ide_util_obj, args, [native_project_file])
365
366
367def _launch_vscode(ide_util_obj, atest_module_info, jtargets, ctargets,
368                   rtargets):
369    """Launches targets with VSCode IDE.
370
371    Args:
372        ide_util_obj: An ide_util instance.
373        atest_module_info: A ModuleInfo instance contains the data of
374                module-info.json.
375        jtargets: A list of Java project targets.
376        ctargets: A list of C/C++ project targets.
377        rtargets: A list of Rust project targets.
378    """
379    abs_paths = []
380    if jtargets:
381        abs_paths.extend(_get_java_project_paths(jtargets, atest_module_info))
382    if ctargets:
383        abs_paths.extend(_get_cc_project_paths(ctargets))
384    if rtargets:
385        root_dir = common_util.get_android_root_dir()
386        abs_paths.extend(_get_rust_project_paths(rtargets, root_dir))
387    if not (jtargets or ctargets or rtargets):
388        print(constant.WARN_MSG.format(
389            common_util.COLORED_INFO('Warning:'), _NO_ANY_PROJECT_EXIST))
390        return
391    vs_path = vscode_workspace_file_gen.generate_code_workspace_file(abs_paths)
392    if not ide_util_obj:
393        return
394    _launch_ide(ide_util_obj, vs_path)
395
396
397def _get_java_project_paths(jtargets, atest_module_info):
398    """Gets the Java absolute project paths from the input Java targets.
399
400    Args:
401        jtargets: A list of strings of Java targets.
402        atest_module_info: A ModuleInfo instance contains the data of
403                module-info.json.
404
405    Returns:
406        A list of the Java absolute project paths.
407    """
408    abs_paths = []
409    for target in jtargets:
410        _, abs_path = common_util.get_related_paths(atest_module_info, target)
411        if abs_path:
412            abs_paths.append(abs_path)
413    return abs_paths
414
415
416def _get_cc_project_paths(ctargets):
417    """Gets the C/C++ absolute project paths from the input C/C++ targets.
418
419    Args:
420        ctargets: A list of strings of C/C++ targets.
421
422    Returns:
423        A list of the C/C++ absolute project paths.
424    """
425    abs_paths = []
426    cc_module_info = native_module_info.NativeModuleInfo()
427    native_project_info.NativeProjectInfo.generate_projects(ctargets)
428    vs_gen = vscode_native_project_file_gen.VSCodeNativeProjectFileGenerator
429    for target in ctargets:
430        _, abs_path = common_util.get_related_paths(cc_module_info, target)
431        if not abs_path:
432            continue
433        vs_native = vs_gen(abs_path)
434        vs_native.generate_c_cpp_properties_json_file()
435        if abs_path not in abs_paths:
436            abs_paths.append(abs_path)
437    return abs_paths
438
439
440def _get_rust_project_paths(rtargets, root_dir):
441    """Gets the Rust absolute project paths from the input Rust targets.
442
443    Args:
444        rtargets: A list of strings of Rust targets.
445        root_dir: A string of the Android root directory.
446
447    Returns:
448        A list of the Rust absolute project paths.
449    """
450    abs_paths = []
451    for rtarget in rtargets:
452        path = rtarget
453        # If rtarget is not an absolute path, make it an absolute one.
454        if not common_util.is_source_under_relative_path(rtarget, root_dir):
455            path = os.path.join(root_dir, rtarget)
456        abs_paths.append(path)
457    return abs_paths
458
459
460def _get_targets_from_args(targets, android_tree):
461    """Gets targets for specific argument.
462
463    For example:
464        $aidegen     : targets = ['.']
465        $aidegen -a  : targets = []
466        $aidegen .   : targets = ['.']
467        $aidegen . -a: targets = []
468
469    Args:
470        targets: A list of strings of targets.
471        android_tree: A boolean, True with '-a' argument else False.
472
473    Returns:
474        A list of the Rust absolute project paths.
475    """
476    if targets == [''] and not android_tree:
477        return ['.']
478    if android_tree:
479        return []
480    return targets
481
482
483@common_util.time_logged(message=_TIME_EXCEED_MSG, maximum=_MAX_TIME)
484def main_with_message(args):
485    """Main entry with skip build message.
486
487    Args:
488        args: A list of system arguments.
489    """
490    aidegen_main(args)
491
492
493@common_util.time_logged
494def main_without_message(args):
495    """Main entry without skip build message.
496
497    Args:
498        args: A list of system arguments.
499    """
500    aidegen_main(args)
501
502
503# pylint: disable=broad-except
504def main(argv):
505    """Main entry.
506
507    Show skip build message in aidegen main process if users command skip_build
508    otherwise remind them to use it and include metrics supports.
509
510    Args:
511        argv: A list of system arguments.
512    """
513    exit_code = constant.EXIT_CODE_NORMAL
514    launch_ide = True
515    ask_version = False
516    try:
517        args = _parse_args(argv)
518        args.targets = _get_targets_from_args(args.targets, args.android_tree)
519        if args.version:
520            ask_version = True
521            version_file = os.path.join(os.path.dirname(__file__),
522                                        constant.VERSION_FILE)
523            print(common_util.read_file_content(version_file))
524            sys.exit(constant.EXIT_CODE_NORMAL)
525        if args.ide_installed_path:
526            if not Path(args.ide_installed_path).exists():
527                print(_NO_IDE_LAUNCH_PATH.format(args.ide_installed_path))
528                sys.exit(constant.EXIT_CODE_NORMAL)
529
530        launch_ide = not args.no_launch
531        common_util.configure_logging(args.verbose)
532        is_whole_android_tree = project_config.is_whole_android_tree(
533            args.targets, args.android_tree)
534        references = [constant.ANDROID_TREE] if is_whole_android_tree else []
535        aidegen_metrics.starts_asuite_metrics(references)
536        if args.skip_build:
537            main_without_message(args)
538        else:
539            main_with_message(args)
540    except BaseException as err:
541        exit_code = constant.EXIT_CODE_EXCEPTION
542        _, exc_value, exc_traceback = sys.exc_info()
543        if isinstance(err, errors.AIDEgenError):
544            exit_code = constant.EXIT_CODE_AIDEGEN_EXCEPTION
545        # Filter out sys.Exit(0) case, which is not an exception case.
546        if isinstance(err, SystemExit) and exc_value.code == 0:
547            exit_code = constant.EXIT_CODE_NORMAL
548        if exit_code is not constant.EXIT_CODE_NORMAL:
549            error_message = str(exc_value)
550            traceback_list = traceback.format_tb(exc_traceback)
551            traceback_list.append(error_message)
552            traceback_str = ''.join(traceback_list)
553            aidegen_metrics.ends_asuite_metrics(exit_code, traceback_str,
554                                                error_message)
555            # print out the traceback message for developers to debug
556            print(traceback_str)
557            raise err
558    finally:
559        if not ask_version:
560            print('\n{0} {1}\n'.format(_INFO, AIDEGEN_REPORT_LINK))
561            # Send the end message here on ignoring launch IDE case.
562            if not launch_ide and exit_code is constant.EXIT_CODE_NORMAL:
563                aidegen_metrics.ends_asuite_metrics(exit_code)
564
565
566def aidegen_main(args):
567    """AIDEGen main entry.
568
569    Try to generate project files for using in IDE. The process is:
570      1. Instantiate a ProjectConfig singleton object and initialize its
571         environment. After creating a singleton instance for ProjectConfig,
572         other modules can use project configurations by
573         ProjectConfig.get_instance().
574      2. Get an IDE instance from ide_util, ide_util.get_ide_util_instance will
575         use ProjectConfig.get_instance() inside the function.
576      3. Setup project_info.ProjectInfo.modules_info by instantiate
577         AidegenModuleInfo.
578      4. Check if projects contain C/C++ projects and launch related IDE.
579
580    Args:
581        args: A list of system arguments.
582    """
583    config = project_config.ProjectConfig(args)
584    config.init_environment()
585    targets = config.targets
586    project_info.ProjectInfo.modules_info = module_info.AidegenModuleInfo()
587    cc_module_info = native_module_info.NativeModuleInfo()
588    jtargets, ctargets, rtargets = native_util.get_java_cc_and_rust_projects(
589        project_info.ProjectInfo.modules_info, cc_module_info, targets)
590    config.language, config.ide_name = common_util.determine_language_ide(
591        args.language[0], args.ide[0], jtargets, ctargets, rtargets)
592    # Called ide_util for pre-check the IDE existence state.
593    ide_util_obj = ide_util.get_ide_util_instance(
594        constant.IDE_DICT[config.ide_name])
595    all_langs = config.ide_name == constant.IDE_VSCODE
596    # Backward compatible strategy, when both java and C/C++ module exist,
597    # check the preferred target from the user and launch single one.
598    language_targets = {constant.JAVA: jtargets,
599                        constant.C_CPP: ctargets,
600                        constant.RUST: rtargets}
601    _launch_ide_by_module_contents(args, ide_util_obj, config.language,
602                                   language_targets, all_langs)
603
604
605if __name__ == '__main__':
606    main(sys.argv[1:])
607