xref: /llvm-project/clang/utils/analyzer/ProjectMap.py (revision bbb8f171364b78c6290fcdbf48b214a870dd1caf)
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 [project._asdict() for project in projects]
122