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