1#!/usr/bin/env python3
2#
3# Copyright 2020 - 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"""Creates the xml files.
18
19Usage example:
20    vcs = XMLGenerator(module_path, 'vcs.xml')
21    if not vcs.xml_obj:
22        # Create the file directly.
23        common_util.file_generate(vcs.xml_abspath, xml_content)
24    else:
25        # Add/remove elements to vcs.xml_obj by the methods of
26        # ElementTree.Element object.
27        vcs.xml_obj.append()
28        vcs.xml_obj.makeelement()
29        vcs.xml_obj.remove()
30        # Update the XML content.
31        vcs.create_xml()
32"""
33
34from __future__ import absolute_import
35
36import os
37
38from xml.etree import ElementTree
39
40from aidegen import constant
41from aidegen import templates
42from aidegen.lib import aidegen_metrics
43from aidegen.lib import common_util
44from aidegen.lib import xml_util
45
46_GIT_PATH = '        <mapping directory="{GIT_DIR}" vcs="Git" />'
47_IGNORE_PATH = '            <path value="{GIT_DIR}" />'
48
49
50class XMLGenerator:
51    """Creates the xml file.
52
53    Attributes:
54        _xml_abspath: A string of the XML's absolute path.
55        _xml_obj: An ElementTree object.
56    """
57
58    def __init__(self, module_abspath, xml_name):
59        """Initializes XMLGenerator.
60
61        Args:
62            module_abspath: A string of the module's absolute path.
63            xml_name: A string of the xml file name.
64        """
65        self._xml_abspath = os.path.join(module_abspath, constant.IDEA_FOLDER,
66                                         xml_name)
67        self._xml_obj = None
68        self.parse()
69
70    def parse(self):
71        """Parses the XML file to an ElementTree object."""
72        if os.path.exists(self._xml_abspath):
73            self._xml_obj = xml_util.parse_xml(self._xml_abspath)
74
75    @property
76    def xml_path(self):
77        """Gets the xml absolute path."""
78        return self._xml_abspath
79
80    @property
81    def xml_obj(self):
82        """Gets the xml object."""
83        return self._xml_obj
84
85    def find_elements_by_name(self, element_type, name):
86        """Finds the target elements by name attribute.
87
88        Args:
89            element_type: A string of element's type.
90            name: A string of element's name.
91
92        Return:
93            List: ElementTree's element objects.
94        """
95        return [e for e in self._xml_obj.findall(element_type)
96                if e.get('name') == name]
97
98    @staticmethod
99    def append_node(parent, node_str):
100        """Appends a node string under the parent element.
101
102        Args:
103            parent: An element object, the new node's parent.
104            node_str: A string, the new node's content.
105        """
106        try:
107            parent.append(ElementTree.fromstring(node_str))
108        except ElementTree.ParseError as xml_err:
109            aidegen_metrics.send_exception_metrics(
110                exit_code=constant.XML_PARSING_FAILURE, stack_trace=xml_err,
111                log=node_str, err_msg='')
112
113    def create_xml(self):
114        """Creates the xml file."""
115        common_util.file_generate(self._xml_abspath, common_util.to_pretty_xml(
116            self._xml_obj.getroot()))
117
118
119def gen_vcs_xml(module_path, git_paths):
120    """Writes the git path into the .idea/vcs.xml.
121
122    For main module, the vcs.xml should include all modules' git path.
123    For the whole AOSP case, ignore creating the vcs.xml. Instead, add the
124    ignored Git paths in the workspace.xml.
125
126    Args:
127        module_path: A string, the absolute path of the module.
128        git_paths: A list of git paths.
129    """
130    git_mappings = [_GIT_PATH.format(GIT_DIR=p) for p in git_paths]
131    vcs = XMLGenerator(module_path, 'vcs.xml')
132    if module_path != common_util.get_android_root_dir() or not vcs.xml_obj:
133        common_util.file_generate(vcs.xml_path, templates.XML_VCS.format(
134            GIT_MAPPINGS='\n'.join(git_mappings)))
135
136
137def write_ignore_git_dirs_file(module_path, ignore_paths):
138    """Write the ignored git paths in the .idea/workspace.xml.
139
140    Args:
141        module_path: A string, the absolute path of the module.
142        ignore_paths: A list of git paths.
143    """
144    ignores = [_IGNORE_PATH.format(GIT_DIR=p) for p in ignore_paths]
145    workspace = XMLGenerator(module_path, 'workspace.xml')
146    if not workspace.xml_obj:
147        common_util.file_generate(workspace.xml_path,
148                                  templates.XML_WORKSPACE.format(
149                                      GITS=''.join(ignores)))
150        return
151    for conf in workspace.find_elements_by_name('component',
152                                                'VcsManagerConfiguration'):
153        workspace.xml_obj.getroot().remove(conf)
154    workspace.append_node(workspace.xml_obj.getroot(),
155                          templates.IGNORED_GITS.format(GITS=''.join(ignores)))
156    workspace.create_xml()
157