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