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"""It is an AIDEGen sub task: generate the .project file for Eclipse."""
18
19import os
20
21from aidegen import constant
22from aidegen import templates
23from aidegen.lib import common_util
24from aidegen.lib import project_file_gen
25
26
27class EclipseConf(project_file_gen.ProjectFileGenerator):
28    """Class to generate project file under the module path for Eclipse.
29
30    Attributes:
31        module_abspath: The absolute path of the target project.
32        module_relpath: The relative path of the target project.
33        module_name: The name of the target project.
34        jar_module_paths: A dict records a mapping of jar file and module path.
35        r_java_paths: A list contains the relative folder paths of the R.java
36                      files.
37        project_file: The absolute path of .project file.
38        project_content: A string ready to be written into project_file.
39        src_paths: A list contains the project's source paths.
40        classpath_file: The absolute path of .classpath file.
41        classpath_content: A string ready to be written into classpath_file.
42    """
43    # Constants of .project file
44    _PROJECT_LINK = ('                <link><name>{}</name><type>2</type>'
45                     '<location>{}</location></link>\n')
46    _PROJECT_FILENAME = '.project'
47    _OUTPUT_BIN_SYMBOLIC_NAME = 'bin'
48
49    # constants of .classpath file
50    _CLASSPATH_SRC_ENTRY = '    <classpathentry kind="src" path="{}"/>\n'
51    _EXCLUDE_ANDROID_BP_ENTRY = ('    <classpathentry excluding="Android.bp" '
52                                 'kind="src" path="{}"/>\n')
53    _CLASSPATH_LIB_ENTRY = ('    <classpathentry exported="true" kind="lib" '
54                            'path="{}" sourcepath="{}"/>\n')
55    _CLASSPATH_FILENAME = '.classpath'
56
57    def __init__(self, project):
58        """Initialize class.
59
60        Args:
61            project: A ProjectInfo instance.
62        """
63        super().__init__(project)
64        self.module_abspath = project.project_absolute_path
65        self.module_relpath = project.project_relative_path
66        self.module_name = project.module_name
67        self.jar_module_paths = project.source_path['jar_module_path']
68        self.r_java_paths = list(project.source_path['r_java_path'])
69        # Related value for generating .project.
70        self.project_file = os.path.join(self.module_abspath,
71                                         self._PROJECT_FILENAME)
72        self.project_content = ''
73        # Related value for generating .classpath.
74        self.src_paths = list(project.source_path['source_folder_path'])
75        self.src_paths.extend(project.source_path['test_folder_path'])
76        self.classpath_file = os.path.join(self.module_abspath,
77                                           self._CLASSPATH_FILENAME)
78        self.classpath_content = ''
79
80    def _gen_r_link(self):
81        """Generate the link resources of the R paths.
82
83        E.g.
84            <link>
85                <name>dependencies/out/target/common/R</name>
86                <type>2</type>
87                <location>{ANDROID_ROOT_PATH}/out/target/common/R</location>
88            </link>
89
90        Returns: A set contains R paths link resources strings.
91        """
92        return {self._gen_link(r_path) for r_path in self.r_java_paths}
93
94    def _gen_src_links(self, relpaths):
95        """Generate the link resources from relpaths.
96
97        The link resource is linked to a folder outside the module's path. It
98        cannot be a folder under the module's path.
99
100        Args:
101            relpaths: A list of module paths which are relative to
102                      ANDROID_BUILD_TOP.
103                      e.g. ['relpath/to/module1', 'relpath/to/module2', ...]
104
105        Returns: A set includes all unique link resources.
106        """
107        src_links = set()
108        for src_path in relpaths:
109            if not common_util.is_source_under_relative_path(
110                    src_path, self.module_relpath):
111                src_links.add(self._gen_link(src_path))
112        return src_links
113
114    @classmethod
115    def _gen_link(cls, relpath):
116        """Generate a link resource from a relative path.
117
118         E.g.
119            <link>
120                <name>dependencies/path/to/relpath</name>
121                <type>2</type>
122                <location>/absolute/path/to/relpath</location>
123            </link>
124
125        Args:
126            relpath: A string of a relative path to Android_BUILD_TOP.
127
128        Returns: A string of link resource.
129        """
130        alias_name = os.path.join(constant.KEY_DEPENDENCIES, relpath)
131        abs_path = os.path.join(common_util.get_android_root_dir(), relpath)
132        return cls._PROJECT_LINK.format(alias_name, abs_path)
133
134    def _gen_bin_link(self):
135        """Generate the link resource of the bin folder.
136
137        The bin folder will be set as default in the module's root path. But
138        doing so causes issues with the android build system. We should instead
139        move the bin under the out folder.
140        For example:
141        <link>
142                <name>bin</name>
143                <type>2</type>
144                <location>/home/user/aosp/out/Eclipse/framework</location>
145        </link>
146
147        Returns: A set includes a link resource of the bin folder.
148        """
149        real_bin_path = os.path.join(common_util.get_android_root_dir(),
150                                     common_util.get_android_out_dir(),
151                                     constant.IDE_ECLIPSE,
152                                     self.module_name)
153        if not os.path.exists(real_bin_path):
154            os.makedirs(real_bin_path)
155        return {self._PROJECT_LINK.format(self._OUTPUT_BIN_SYMBOLIC_NAME,
156                                          real_bin_path)}
157
158    def _get_other_src_folders(self):
159        """Get the source folders outside the module's path.
160
161        Some source folders are generated by build system and placed under the
162        out folder. They also need to be set as link resources in Eclipse.
163
164        Returns: A list of source folder paths.
165        """
166        return [p for p in self.src_paths
167                if not common_util.is_source_under_relative_path(
168                    p, self.module_relpath)]
169
170    def _create_project_content(self):
171        """Create the project file .project under the module."""
172        # links is a set to save unique link resources.
173        links = self._gen_src_links(self.jar_module_paths.values())
174        links.update(self._gen_src_links(self._get_other_src_folders()))
175        links.update(self._gen_r_link())
176        links.update(self._gen_bin_link())
177        self.project_content = templates.ECLIPSE_PROJECT_XML.format(
178            PROJECTNAME=self.module_name.replace('/', '_'),
179            LINKEDRESOURCES=''.join(sorted(list(links))))
180
181    def _gen_r_path_entries(self):
182        """Generate the class path entries for the R paths.
183
184        E.g.
185            <classpathentry kind="src"
186                path="dependencies/out/target/common/R"/>
187            <classpathentry kind="src"
188                path="dependencies/out/soong/.intermediates/packages/apps/
189                      Settings/Settings/android_common/gen/aapt2/R"/>
190
191        Returns: A list of the R path's class path entry.
192        """
193        r_entry_list = []
194        for r_path in self.r_java_paths:
195            alias_path = os.path.join(constant.KEY_DEPENDENCIES, r_path)
196            r_entry_list.append(self._CLASSPATH_SRC_ENTRY.format(alias_path))
197        return r_entry_list
198
199    def _gen_src_path_entries(self):
200        """Generate the class path entries from srcs.
201
202        If the Android.bp file exists, generate the following content in
203        .classpath file to avoid copying the Android.bp to the bin folder under
204        current project directory.
205        <classpathentry excluding="Android.bp" kind="src"
206                        path="clearcut_client"/>
207
208        If the source folder is under the module's path, revise the source
209        folder path as a relative path to the module's path.
210        E.g.
211            The source folder paths list:
212                ['packages/apps/Settings/src',
213                 'packages/apps/Settings/tests/robotests/src',
214                 'packages/apps/Settings/tests/uitests/src',
215                 'packages/apps/Settings/tests/unit/src'
216                ]
217            It will generate the related <classpathentry> list:
218                ['<classpathentry kind="src" path="src"/>',
219                 '<classpathentry kind="src" path="tests/robotests/src"/>',
220                 '<classpathentry kind="src" path="tests/uitests/src"/>',
221                 '<classpathentry kind="src" path="tests/unit/src"/>'
222                ]
223
224        Some source folders are not under the module's path:
225        e.g. out/path/src
226        The class path entry would be:
227        <classpathentry kind="src" path="dependencies/out/path/src"/>
228
229        Returns: A list of source folders' class path entries.
230        """
231        src_path_entries = []
232        for src in self.src_paths:
233            src_abspath = os.path.join(common_util.get_android_root_dir(), src)
234            if common_util.is_source_under_relative_path(
235                    src, self.module_relpath):
236                src = src.replace(self.module_relpath, '').strip(os.sep)
237            else:
238                src = os.path.join(constant.KEY_DEPENDENCIES, src)
239            if common_util.exist_android_bp(src_abspath):
240                src_path_entries.append(
241                    self._EXCLUDE_ANDROID_BP_ENTRY.format(src))
242            else:
243                src_path_entries.append(self._CLASSPATH_SRC_ENTRY.format(src))
244        return src_path_entries
245
246    def _gen_jar_path_entries(self):
247        """Generate the jar files' class path entries.
248
249        The self.jar_module_paths is a dictionary.
250        e.g.
251            {'/abspath/to/the/file.jar': 'relpath/to/the/module'}
252        This method will generate the <classpathentry> for each jar file.
253        The format of <classpathentry> looks like:
254        <classpathentry exported="true" kind="lib"
255            path="/abspath/to/the/file.jar"
256            sourcepath="dependencies/relpath/to/the/module"/>
257
258        Returns: A list of jar files' class path entries.
259        """
260        jar_entries = []
261        for jar_relpath, module_relpath in self.jar_module_paths.items():
262            jar_abspath = os.path.join(common_util.get_android_root_dir(),
263                                       jar_relpath)
264            alias_module_path = os.path.join(constant.KEY_DEPENDENCIES,
265                                             module_relpath)
266            jar_entries.append(self._CLASSPATH_LIB_ENTRY.format(
267                jar_abspath, alias_module_path))
268        return jar_entries
269
270    def _gen_bin_dir_entry(self):
271        """Generate the class path entry of the bin folder.
272
273        Returns: A list has a class path entry of the bin folder.
274        """
275        return [self._CLASSPATH_SRC_ENTRY.format(self._OUTPUT_BIN_SYMBOLIC_NAME)
276                ]
277
278    def _create_classpath_content(self):
279        """Create the project file .classpath under the module."""
280        src_entries = self._gen_src_path_entries()
281        src_entries.extend(self._gen_r_path_entries())
282        src_entries.extend(self._gen_bin_dir_entry())
283        jar_entries = self._gen_jar_path_entries()
284        self.classpath_content = templates.ECLIPSE_CLASSPATH_XML.format(
285            SRC=''.join(sorted(src_entries)),
286            LIB=''.join(sorted(jar_entries)))
287
288    def generate_project_file(self):
289        """Generate .project file of the target module."""
290        self._create_project_content()
291        common_util.file_generate(self.project_file, self.project_content)
292
293    def generate_classpath_file(self):
294        """Generate .classpath file of the target module."""
295        self._create_classpath_content()
296        common_util.file_generate(self.classpath_file, self.classpath_content)
297
298    @classmethod
299    def generate_ide_project_files(cls, projects):
300        """Generate Eclipse project files by a list of ProjectInfo instances.
301
302        Args:
303            projects: A list of ProjectInfo instances.
304        """
305        for project in projects:
306            eclipse_configure = EclipseConf(project)
307            eclipse_configure.generate_project_file()
308            eclipse_configure.generate_classpath_file()
309