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