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