1#!/usr/bin/env python3
2#
3# Copyright 2019 - 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"""Project config class."""
18
19import os
20
21from aidegen import constant
22from aidegen.lib import common_util
23from aidegen.lib import errors
24
25SKIP_BUILD_INFO = ('If you are sure the related modules and dependencies have '
26                   'been already built, please try to use command {} to skip '
27                   'the building process.')
28_SKIP_BUILD_CMD = 'aidegen {} -s'
29_SKIP_BUILD_WARN = (
30    'You choose "--skip-build". Skip building jar and module might increase '
31    'the risk of the absence of some jar or R/AIDL/logtags java files and '
32    'cause the red lines to appear in IDE tool.')
33_INSTANCE_NOT_EXIST_ERROR = ('The instance of {} does not exist. Please '
34                             'initialize it before using.')
35
36
37class ProjectConfig():
38    """A singleton class manages AIDEGen's configurations.
39
40    ProjectConfig is a singleton class that can be accessed in other modules.
41
42    Usage:
43        1. Main module should do it once by instantiating a ProjectConfig with
44           users' input arguments and calling init_environment().
45           args = aidegen_main.main(sys.argv[1:])
46           project_config.ProjectConfig(args).init_environment()
47        2. All others can get the ProjectConfig instance by calling
48           get_instance().
49           project_config.ProjectConfig.get_instance()
50
51    Class attributes:
52        _instance: A singleton instance of ProjectConfig.
53
54    Attributes:
55        ide_name: The IDE name which users prefer to launch.
56        is_launch_ide: A boolean for launching IDE in the end of AIDEGen.
57        depth: The depth of module referenced by source.
58        full_repo: A boolean decides import whole Android source repo.
59        is_skip_build: A boolean decides skipping building jars or modules.
60        targets: A string list with Android module names or paths.
61        verbose: A boolean. If true, display DEBUG level logs.
62        ide_installed_path: A string of IDE installed path.
63        config_reset: A boolean if true to reset all saved configurations.
64        atest_module_info: A ModuleInfo instance.
65        language: The programming language users prefer to deal with.
66    """
67
68    _instance = None
69
70    def __init__(self, args):
71        """ProjectConfig initialize.
72
73        Args:
74            An argparse.Namespace object holds parsed args.
75        """
76        self.language, self.ide_name = common_util.determine_language_ide(
77            args.language[0], args.ide[0])
78        self.is_launch_ide = not args.no_launch
79        self.depth = args.depth
80        self.full_repo = args.android_tree
81        self.is_skip_build = args.skip_build
82        self.targets = args.targets.copy()
83        self.verbose = args.verbose
84        self.ide_installed_path = args.ide_installed_path
85        self.config_reset = args.config_reset
86        self.exclude_paths = args.exclude_paths
87        self.atest_module_info = None
88        ProjectConfig._instance = self
89
90    def init_environment(self):
91        """Initialize the environment settings for the whole project."""
92        self._show_skip_build_msg()
93        # TODO(b/159078170): Avoid CLion IDE case for now, we should avoid
94        # Android Studio's native project's case in the future.
95        targets = self.targets if self.language == constant.JAVA else None
96        self.atest_module_info = common_util.get_atest_module_info(targets)
97        self.exclude_paths = _transform_exclusive_paths(
98            self.atest_module_info, self.exclude_paths)
99        self.targets = _check_whole_android_tree(self.targets, self.full_repo)
100        self.full_repo = (self.targets[0] == constant.WHOLE_ANDROID_TREE_TARGET)
101
102    def _show_skip_build_msg(self):
103        """Display different messages if users skip building targets or not."""
104        if self.is_skip_build:
105            print('\n{} {}\n'.format(
106                common_util.COLORED_INFO('Warning:'), _SKIP_BUILD_WARN))
107        else:
108            msg = SKIP_BUILD_INFO.format(
109                common_util.COLORED_INFO(
110                    _SKIP_BUILD_CMD.format(' '.join(self.targets))))
111            print('\n{} {}\n'.format(common_util.COLORED_INFO('INFO:'), msg))
112
113    @classmethod
114    def get_instance(cls):
115        """Get a singleton's instance.
116
117        Returns:
118           A singleton instance of ProjectConfig.
119
120        Raises:
121           An exception of errors.InstanceNotExistError if users didn't
122           instantiate a ProjectConfig object before calling this method.
123        """
124        if not cls._instance:
125            raise errors.InstanceNotExistError(
126                _INSTANCE_NOT_EXIST_ERROR.format(str(cls)))
127        return cls._instance
128
129
130def _check_whole_android_tree(targets, android_tree):
131    """Check if it's a building project file for the whole Android tree.
132
133    The rules:
134    1. If users command aidegen under Android root, e.g.,
135       root$ aidegen
136       that implies users would like to launch the whole Android tree, AIDEGen
137       should set the flag android_tree True.
138    2. If android_tree is True, add whole Android tree to the project.
139
140    Args:
141        targets: A list of targets to be imported.
142        android_tree: A boolean, True if it's a whole Android tree case,
143                      otherwise False.
144
145    Returns:
146        A list of targets to be built.
147    """
148    if common_util.is_android_root(os.getcwd()) and targets == ['']:
149        return [constant.WHOLE_ANDROID_TREE_TARGET]
150    new_targets = targets.copy()
151    if android_tree:
152        new_targets.insert(0, constant.WHOLE_ANDROID_TREE_TARGET)
153    return new_targets
154
155
156def is_whole_android_tree(targets, android_tree):
157    """Checks is AIDEGen going to process whole android tree.
158
159    Args:
160        targets: A list of targets to be imported.
161        android_tree: A boolean, True if it's a whole Android tree case,
162                      otherwise False.
163    Returns:
164        A boolean, True when user is going to import whole Android tree.
165    """
166    return (android_tree or
167            (common_util.is_android_root(os.getcwd()) and targets == ['']))
168
169
170def _transform_exclusive_paths(atest_module_info, exclude_paths):
171    """Transforms exclusive paths to relative paths.
172
173    Args:
174        exclude_paths: A list of strings of exclusive paths.
175
176    Returns:
177        A list of relative paths.
178    """
179    if not exclude_paths:
180        return None
181    excludes = []
182    for path in exclude_paths:
183        exclude_path, _ = common_util.get_related_paths(atest_module_info, path)
184        excludes.append(exclude_path)
185    return excludes
186