xref: /llvm-project/clang/utils/analyzer/ProjectMap.py (revision dc8a77de7db71dc52b0c75b3bb5437d9ae0ccc8c)
1import json
2import os
3
4from enum import Enum
5from typing import Any, Dict, List, NamedTuple, Optional, Tuple
6
7
8JSON = Dict[str, Any]
9
10
11DEFAULT_MAP_FILE = "projects.json"
12
13
14class DownloadType(str, Enum):
15    GIT = "git"
16    ZIP = "zip"
17    SCRIPT = "script"
18
19
20class ProjectInfo(NamedTuple):
21    """
22    Information about a project to analyze.
23    """
24    name: str
25    mode: int
26    source: DownloadType = DownloadType.SCRIPT
27    origin: str = ""
28    commit: str = ""
29    enabled: bool = True
30
31
32class ProjectMap:
33    """
34    Project map stores info about all the "registered" projects.
35    """
36    def __init__(self, path: Optional[str] = None, should_exist: bool = True):
37        """
38        :param path: optional path to a project JSON file, when None defaults
39                     to DEFAULT_MAP_FILE.
40        :param should_exist: flag to tell if it's an exceptional situation when
41                             the project file doesn't exist, creates an empty
42                             project list instead if we are not expecting it to
43                             exist.
44        """
45        if path is None:
46            path = os.path.join(os.path.abspath(os.curdir), DEFAULT_MAP_FILE)
47
48        if not os.path.exists(path):
49            if should_exist:
50                raise ValueError(
51                    f"Cannot find the project map file {path}"
52                    f"\nRunning script for the wrong directory?\n")
53            else:
54                self._create_empty(path)
55
56        self.path = path
57        self._load_projects()
58
59    def save(self):
60        """
61        Save project map back to its original file.
62        """
63        self._save(self.projects, self.path)
64
65    def _load_projects(self):
66        with open(self.path) as raw_data:
67            raw_projects = json.load(raw_data)
68
69            if not isinstance(raw_projects, list):
70                raise ValueError(
71                    "Project map should be a list of JSON objects")
72
73            self.projects = self._parse(raw_projects)
74
75    @staticmethod
76    def _parse(raw_projects: List[JSON]) -> List[ProjectInfo]:
77        return [ProjectMap._parse_project(raw_project)
78                for raw_project in raw_projects]
79
80    @staticmethod
81    def _parse_project(raw_project: JSON) -> ProjectInfo:
82        try:
83            name: str = raw_project["name"]
84            build_mode: int = raw_project["mode"]
85            enabled: bool = raw_project.get("enabled", True)
86            source: DownloadType = raw_project.get("source", "zip")
87
88            if source == DownloadType.GIT:
89                origin, commit = ProjectMap._get_git_params(raw_project)
90            else:
91                origin, commit = "", ""
92
93            return ProjectInfo(name, build_mode, source, origin, commit,
94                               enabled)
95
96        except KeyError as e:
97            raise ValueError(
98                f"Project info is required to have a '{e.args[0]}' field")
99
100    @staticmethod
101    def _get_git_params(raw_project: JSON) -> Tuple[str, str]:
102        try:
103            return raw_project["origin"], raw_project["commit"]
104        except KeyError as e:
105            raise ValueError(
106                f"Profect info is required to have a '{e.args[0]}' field "
107                f"if it has a 'git' source")
108
109    @staticmethod
110    def _create_empty(path: str):
111        ProjectMap._save([], path)
112
113    @staticmethod
114    def _save(projects: List[ProjectInfo], path: str):
115        with open(path, "w") as output:
116            json.dump(ProjectMap._convert_infos_to_dicts(projects),
117                      output, indent=2)
118
119    @staticmethod
120    def _convert_infos_to_dicts(projects: List[ProjectInfo]) -> List[JSON]:
121        return [ProjectMap._convert_info_to_dict(project)
122                for project in projects]
123
124    @staticmethod
125    def _convert_info_to_dict(project: ProjectInfo) -> JSON:
126        whole_dict = project._asdict()
127        defaults = project._field_defaults
128
129        # there is no need in serializing fields with default values
130        for field, default_value in defaults.items():
131            if whole_dict[field] == default_value:
132                del whole_dict[field]
133
134        return whole_dict
135