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