ciel 0.21.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ciel/__init__.py +29 -0
- ciel/__main__.py +429 -0
- ciel/__version__.py +44 -0
- ciel/build/__init__.py +267 -0
- ciel/build/common.py +98 -0
- ciel/build/gf180mcu.py +263 -0
- ciel/build/git_multi_clone.py +220 -0
- ciel/build/ihp_sg13g2.py +148 -0
- ciel/build/sky130.py +358 -0
- ciel/click_common.py +119 -0
- ciel/common.py +256 -0
- ciel/families.py +119 -0
- ciel/github.py +184 -0
- ciel/manage.py +355 -0
- ciel/py.typed +0 -0
- ciel-0.21.0.dev0.dist-info/METADATA +168 -0
- ciel-0.21.0.dev0.dist-info/RECORD +19 -0
- ciel-0.21.0.dev0.dist-info/WHEEL +4 -0
- ciel-0.21.0.dev0.dist-info/entry_points.txt +3 -0
ciel/common.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Copyright 2022-2023 Efabless Corporation
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import shutil
|
|
17
|
+
import pathlib
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Iterable, Optional, List, Dict, Tuple
|
|
21
|
+
|
|
22
|
+
from . import github
|
|
23
|
+
from .families import Family
|
|
24
|
+
|
|
25
|
+
# -- Assorted Helper Functions
|
|
26
|
+
ISO8601_FMT = "%Y-%m-%dT%H:%M:%SZ"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def date_to_iso8601(date: datetime) -> str:
|
|
30
|
+
return date.strftime(ISO8601_FMT)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def date_from_iso8601(string: str) -> datetime:
|
|
34
|
+
return datetime.strptime(string, ISO8601_FMT)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def mkdirp(path):
|
|
38
|
+
return pathlib.Path(path).mkdir(parents=True, exist_ok=True)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# -- API Variables
|
|
42
|
+
|
|
43
|
+
# -- PDK Root Management
|
|
44
|
+
VOLARE_DEFAULT_HOME = os.path.join(os.path.expanduser("~"), ".cielo")
|
|
45
|
+
VOLARE_RESOLVED_HOME = os.getenv("PDK_ROOT") or VOLARE_DEFAULT_HOME
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _get_current_version(pdk_root: str, pdk: str) -> Optional[str]:
|
|
49
|
+
current_file = os.path.join(get_cielo_dir(pdk_root, pdk), "current")
|
|
50
|
+
current_file_dir = os.path.dirname(current_file)
|
|
51
|
+
mkdirp(current_file_dir)
|
|
52
|
+
version = None
|
|
53
|
+
try:
|
|
54
|
+
version = open(current_file).read().strip()
|
|
55
|
+
except FileNotFoundError:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
return version
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_cielo_home(pdk_root: Optional[str] = None) -> str:
|
|
62
|
+
return pdk_root or VOLARE_RESOLVED_HOME
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_cielo_dir(pdk_root: str, pdk: str) -> str:
|
|
66
|
+
return os.path.join(pdk_root, "cielo", pdk)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_versions_dir(pdk_root: str, pdk: str) -> str:
|
|
70
|
+
return os.path.join(get_cielo_dir(pdk_root, pdk), "versions")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class Version(object):
|
|
75
|
+
name: str
|
|
76
|
+
pdk: str
|
|
77
|
+
commit_date: Optional[datetime] = None
|
|
78
|
+
upload_date: Optional[datetime] = None
|
|
79
|
+
prerelease: bool = False
|
|
80
|
+
|
|
81
|
+
def __lt__(self, rhs: "Version"):
|
|
82
|
+
return (self.commit_date or datetime.min) < (rhs.commit_date or datetime.min)
|
|
83
|
+
|
|
84
|
+
def __str__(self) -> str:
|
|
85
|
+
return self.name
|
|
86
|
+
|
|
87
|
+
def is_installed(self, pdk_root: str) -> bool:
|
|
88
|
+
version_dir = self.get_dir(pdk_root)
|
|
89
|
+
return os.path.isdir(version_dir)
|
|
90
|
+
|
|
91
|
+
def is_current(self, pdk_root: str) -> bool:
|
|
92
|
+
return self.name == _get_current_version(pdk_root, self.pdk)
|
|
93
|
+
|
|
94
|
+
def get_dir(self, pdk_root: str) -> str:
|
|
95
|
+
return os.path.join(get_versions_dir(pdk_root, self.pdk), self.name)
|
|
96
|
+
|
|
97
|
+
def unset_current(self, pdk_root: str):
|
|
98
|
+
if not self.is_installed(pdk_root):
|
|
99
|
+
return
|
|
100
|
+
if not self.is_current(pdk_root):
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
for variant in Family.by_name[self.pdk].variants:
|
|
104
|
+
try:
|
|
105
|
+
os.unlink(os.path.join(pdk_root, variant))
|
|
106
|
+
except FileNotFoundError:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
current_file = os.path.join(get_cielo_dir(pdk_root, self.pdk), "current")
|
|
110
|
+
os.unlink(current_file)
|
|
111
|
+
|
|
112
|
+
def uninstall(self, pdk_root: str):
|
|
113
|
+
if not self.is_installed(pdk_root):
|
|
114
|
+
raise ValueError(
|
|
115
|
+
f"Version {self.name} of the {self.pdk} PDK is not installed."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
self.unset_current(pdk_root)
|
|
119
|
+
|
|
120
|
+
version_dir = self.get_dir(pdk_root)
|
|
121
|
+
|
|
122
|
+
shutil.rmtree(version_dir)
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def get_current(Self, pdk_root: str, pdk: str) -> Optional["Version"]:
|
|
126
|
+
current_version = _get_current_version(pdk_root, pdk)
|
|
127
|
+
if current_version is None:
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
return Version(current_version, pdk)
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def get_all_installed(Self, pdk_root: str, pdk: str) -> List["Version"]:
|
|
134
|
+
versions_dir = get_versions_dir(pdk_root, pdk)
|
|
135
|
+
mkdirp(versions_dir)
|
|
136
|
+
return [
|
|
137
|
+
Version(
|
|
138
|
+
name=version,
|
|
139
|
+
pdk=pdk,
|
|
140
|
+
)
|
|
141
|
+
for version in os.listdir(versions_dir)
|
|
142
|
+
if os.path.isdir(os.path.join(versions_dir, version))
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def _from_github(
|
|
147
|
+
Self,
|
|
148
|
+
session: Optional[github.GitHubSession] = None,
|
|
149
|
+
) -> Dict[str, List["Version"]]:
|
|
150
|
+
releases = github.get_releases(session)
|
|
151
|
+
|
|
152
|
+
rvs_by_pdk: Dict[str, List["Version"]] = {}
|
|
153
|
+
|
|
154
|
+
commit_rx = re.compile(r"released on ([\d\-\:TZ]+)")
|
|
155
|
+
|
|
156
|
+
for release in releases:
|
|
157
|
+
if release["draft"]:
|
|
158
|
+
continue
|
|
159
|
+
family, hash = release["tag_name"].rsplit("-", maxsplit=1)
|
|
160
|
+
|
|
161
|
+
upload_date = date_from_iso8601(release["published_at"])
|
|
162
|
+
commit_date = None
|
|
163
|
+
|
|
164
|
+
commit_date_match = commit_rx.search(release["body"])
|
|
165
|
+
if commit_date_match is not None:
|
|
166
|
+
commit_date = date_from_iso8601(commit_date_match[1])
|
|
167
|
+
|
|
168
|
+
remote_version = Self(
|
|
169
|
+
name=hash,
|
|
170
|
+
pdk=family,
|
|
171
|
+
commit_date=commit_date,
|
|
172
|
+
upload_date=upload_date,
|
|
173
|
+
prerelease=release["prerelease"],
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if rvs_by_pdk.get(family) is None:
|
|
177
|
+
rvs_by_pdk[family] = rvs_by_pdk.get(family) or []
|
|
178
|
+
|
|
179
|
+
rvs_by_pdk[family].append(remote_version)
|
|
180
|
+
|
|
181
|
+
for family in rvs_by_pdk.keys():
|
|
182
|
+
rvs_by_pdk[family].sort(reverse=True)
|
|
183
|
+
|
|
184
|
+
return rvs_by_pdk
|
|
185
|
+
|
|
186
|
+
def get_release_links(
|
|
187
|
+
self,
|
|
188
|
+
scl_filter: Iterable[str],
|
|
189
|
+
include_common: bool,
|
|
190
|
+
session: Optional[github.GitHubSession] = None,
|
|
191
|
+
) -> List[Tuple[str, str]]:
|
|
192
|
+
release = github.get_release_links(f"{self.pdk}-{self.name}", session)
|
|
193
|
+
|
|
194
|
+
assets = release["assets"]
|
|
195
|
+
zst_files = []
|
|
196
|
+
for asset in assets:
|
|
197
|
+
if asset["name"].endswith(".tar.zst"):
|
|
198
|
+
asset_scl = asset["name"][:-8]
|
|
199
|
+
if (
|
|
200
|
+
asset_scl == "common" and include_common
|
|
201
|
+
) or asset_scl in scl_filter:
|
|
202
|
+
zst_files.append((asset["name"], asset["browser_download_url"]))
|
|
203
|
+
|
|
204
|
+
if len(zst_files) == 0:
|
|
205
|
+
raise ValueError(
|
|
206
|
+
f"No files found for standard cell libraries: {scl_filter}."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return zst_files
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def resolve_version(
|
|
213
|
+
version: Optional[str],
|
|
214
|
+
tool_metadata_file_path: Optional[str],
|
|
215
|
+
) -> str:
|
|
216
|
+
"""
|
|
217
|
+
Takes an optional version and tool_metadata_file_path.
|
|
218
|
+
|
|
219
|
+
If version is set, it is returned.
|
|
220
|
+
|
|
221
|
+
If not, tool_metadata_file_path is checked if it exists.
|
|
222
|
+
|
|
223
|
+
If not specified, ./tool_metadata.yml and ./dependencies/tool_metadata.yml
|
|
224
|
+
are both checked if they exist.
|
|
225
|
+
|
|
226
|
+
If none are specified, execution is halted.
|
|
227
|
+
|
|
228
|
+
Otherwise, the resulting metadata file is parsed for an open_pdks version,
|
|
229
|
+
which is then returned.
|
|
230
|
+
"""
|
|
231
|
+
if version is not None:
|
|
232
|
+
return version
|
|
233
|
+
|
|
234
|
+
import yaml
|
|
235
|
+
|
|
236
|
+
if tool_metadata_file_path is None:
|
|
237
|
+
tool_metadata_file_path = os.path.join(".", "tool_metadata.yml")
|
|
238
|
+
if not os.path.isfile(tool_metadata_file_path):
|
|
239
|
+
tool_metadata_file_path = os.path.join(
|
|
240
|
+
".", "dependencies", "tool_metadata.yml"
|
|
241
|
+
)
|
|
242
|
+
if not os.path.isfile(tool_metadata_file_path):
|
|
243
|
+
raise FileNotFoundError(
|
|
244
|
+
"Any of ./tool_metadata.yml or ./dependencies/tool_metadata.yml not found. You'll need to specify the file path or the commits explicitly."
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
tool_metadata = yaml.safe_load(open(tool_metadata_file_path).read())
|
|
248
|
+
|
|
249
|
+
open_pdks_list = [tool for tool in tool_metadata if tool["name"] == "open_pdks"]
|
|
250
|
+
|
|
251
|
+
if len(open_pdks_list) < 1:
|
|
252
|
+
raise ValueError("No entry for open_pdks found in tool_metadata.yml")
|
|
253
|
+
|
|
254
|
+
version = open_pdks_list[0]["commit"]
|
|
255
|
+
|
|
256
|
+
return version
|
ciel/families.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Copyright 2022-2023 Efabless Corporation
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Iterable, List, Dict, Optional, Set, ClassVar
|
|
16
|
+
|
|
17
|
+
from .github import RepoInfo, opdks_repo, ihp_repo
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Family(object):
|
|
22
|
+
by_name: ClassVar[Dict[str, "Family"]] = {}
|
|
23
|
+
|
|
24
|
+
name: str
|
|
25
|
+
variants: List[str]
|
|
26
|
+
all_libraries: List[str]
|
|
27
|
+
repo: RepoInfo
|
|
28
|
+
# lol no implicitly unwrapped optionals
|
|
29
|
+
default_variant: str = None # type: ignore
|
|
30
|
+
default_includes: List[str] = None # type: ignore
|
|
31
|
+
|
|
32
|
+
def __post_init__(self):
|
|
33
|
+
if self.default_variant is None:
|
|
34
|
+
self.default_variant = self.variants[0]
|
|
35
|
+
if self.default_includes is None:
|
|
36
|
+
self.default_includes = self.all_libraries.copy()
|
|
37
|
+
|
|
38
|
+
def resolve_libraries(
|
|
39
|
+
self,
|
|
40
|
+
input: Optional[Iterable[str]],
|
|
41
|
+
) -> Set[str]:
|
|
42
|
+
if input is None:
|
|
43
|
+
input = ("default",)
|
|
44
|
+
final_set: Set[str] = set()
|
|
45
|
+
for element in input:
|
|
46
|
+
if element.lower() == "all":
|
|
47
|
+
final_set = set(self.all_libraries)
|
|
48
|
+
return final_set
|
|
49
|
+
elif element.lower() == "default":
|
|
50
|
+
final_set = final_set.union(set(self.default_includes))
|
|
51
|
+
elif element in self.all_libraries:
|
|
52
|
+
final_set.add(element)
|
|
53
|
+
else:
|
|
54
|
+
raise ValueError(f"Unknown library {element} for PDK {self.name}")
|
|
55
|
+
return final_set
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
Family.by_name = {}
|
|
59
|
+
Family.by_name["sky130"] = Family(
|
|
60
|
+
name="sky130",
|
|
61
|
+
variants=["sky130A", "sky130B"],
|
|
62
|
+
default_variant="sky130A",
|
|
63
|
+
all_libraries=[
|
|
64
|
+
"sky130_fd_io",
|
|
65
|
+
"sky130_fd_pr",
|
|
66
|
+
"sky130_ml_xx_hd",
|
|
67
|
+
"sky130_fd_sc_hd",
|
|
68
|
+
"sky130_fd_sc_hdll",
|
|
69
|
+
"sky130_fd_sc_lp",
|
|
70
|
+
"sky130_fd_sc_hvl",
|
|
71
|
+
"sky130_fd_sc_ls",
|
|
72
|
+
"sky130_fd_sc_ms",
|
|
73
|
+
"sky130_fd_sc_hs",
|
|
74
|
+
"sky130_sram_macros",
|
|
75
|
+
"sky130_fd_pr_reram",
|
|
76
|
+
],
|
|
77
|
+
default_includes=[
|
|
78
|
+
"sky130_fd_io",
|
|
79
|
+
"sky130_fd_pr",
|
|
80
|
+
"sky130_fd_sc_hd",
|
|
81
|
+
"sky130_fd_sc_hvl",
|
|
82
|
+
"sky130_ml_xx_hd",
|
|
83
|
+
"sky130_sram_macros",
|
|
84
|
+
],
|
|
85
|
+
repo=opdks_repo,
|
|
86
|
+
)
|
|
87
|
+
Family.by_name["gf180mcu"] = Family(
|
|
88
|
+
name="gf180mcu",
|
|
89
|
+
variants=["gf180mcuA", "gf180mcuB", "gf180mcuC", "gf180mcuD"],
|
|
90
|
+
default_variant="gf180mcuD",
|
|
91
|
+
all_libraries=[
|
|
92
|
+
"gf180mcu_fd_io",
|
|
93
|
+
"gf180mcu_fd_pr",
|
|
94
|
+
"gf180mcu_fd_sc_mcu7t5v0",
|
|
95
|
+
"gf180mcu_fd_sc_mcu9t5v0",
|
|
96
|
+
"gf180mcu_fd_ip_sram",
|
|
97
|
+
"gf180mcu_osu_sc_gp12t3v3",
|
|
98
|
+
"gf180mcu_osu_sc_gp9t3v3",
|
|
99
|
+
],
|
|
100
|
+
default_includes=[
|
|
101
|
+
"gf180mcu_fd_io",
|
|
102
|
+
"gf180mcu_fd_pr",
|
|
103
|
+
"gf180mcu_fd_sc_mcu7t5v0",
|
|
104
|
+
"gf180mcu_fd_sc_mcu9t5v0",
|
|
105
|
+
"gf180mcu_fd_ip_sram",
|
|
106
|
+
],
|
|
107
|
+
repo=opdks_repo,
|
|
108
|
+
)
|
|
109
|
+
Family.by_name["ihp_sg13g2"] = Family(
|
|
110
|
+
name="ihp_sg13g2",
|
|
111
|
+
variants=["ihp-sg13g2"],
|
|
112
|
+
all_libraries=[
|
|
113
|
+
"sg13g2_io",
|
|
114
|
+
"sg13g2_pr",
|
|
115
|
+
"sg13g2_sram",
|
|
116
|
+
"sg13g2_stdcell",
|
|
117
|
+
],
|
|
118
|
+
repo=ihp_repo,
|
|
119
|
+
)
|
ciel/github.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Copyright 2022-2023 Efabless Corporation
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import os
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import Any, ClassVar, List, Mapping, Optional
|
|
20
|
+
|
|
21
|
+
import httpx
|
|
22
|
+
import ssl
|
|
23
|
+
from .__version__ import __version__
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class RepoInfo:
|
|
28
|
+
owner: str
|
|
29
|
+
name: str
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def id(self):
|
|
33
|
+
return f"{self.owner}/{self.name}"
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def link(self):
|
|
37
|
+
return f"https://github.com/{self.id}"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def api(self):
|
|
41
|
+
return f"https://api.github.com/repos/{self.id}"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
volare_repo = RepoInfo(
|
|
45
|
+
os.getenv("VOLARE_REPO_OWNER", "efabless"),
|
|
46
|
+
os.getenv("VOLARE_REPO_NAME", "volare"),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
opdks_repo = RepoInfo(
|
|
50
|
+
os.getenv("OPDKS_REPO_OWNER", "RTimothyEdwards"),
|
|
51
|
+
os.getenv("OPDKS_REPO_NAME", "open_pdks"),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
ihp_repo = RepoInfo(
|
|
55
|
+
os.getenv("IHP_REPO_OWNER", "IHP-GmbH"),
|
|
56
|
+
os.getenv("IHP_REPO_NAME", "IHP-Open-PDK"),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class GitHubSession(httpx.Client):
|
|
61
|
+
class Token(object):
|
|
62
|
+
override: ClassVar[Optional[str]] = None
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def get_gh_token(Self) -> Optional[str]:
|
|
66
|
+
token = None
|
|
67
|
+
|
|
68
|
+
# 0. Lowest priority: ghcli
|
|
69
|
+
try:
|
|
70
|
+
token = subprocess.check_output(
|
|
71
|
+
[
|
|
72
|
+
"gh",
|
|
73
|
+
"auth",
|
|
74
|
+
"token",
|
|
75
|
+
],
|
|
76
|
+
encoding="utf8",
|
|
77
|
+
).strip()
|
|
78
|
+
except FileNotFoundError:
|
|
79
|
+
pass
|
|
80
|
+
except subprocess.CalledProcessError:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
# 1. Higher priority: environment GITHUB_TOKEN
|
|
84
|
+
env_token = os.getenv("GITHUB_TOKEN")
|
|
85
|
+
if env_token is not None and env_token.strip() != "":
|
|
86
|
+
token = env_token
|
|
87
|
+
|
|
88
|
+
# 2. Highest priority: the -t flag
|
|
89
|
+
if Self.override is not None:
|
|
90
|
+
token = Self.override
|
|
91
|
+
|
|
92
|
+
return token
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
*,
|
|
97
|
+
follow_redirects: bool = True,
|
|
98
|
+
github_token: Optional[str] = None,
|
|
99
|
+
ssl_context=None,
|
|
100
|
+
**kwargs,
|
|
101
|
+
):
|
|
102
|
+
if ssl_context is None:
|
|
103
|
+
try:
|
|
104
|
+
import truststore
|
|
105
|
+
|
|
106
|
+
ssl_context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
107
|
+
except ImportError:
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
super().__init__(
|
|
112
|
+
follow_redirects=follow_redirects,
|
|
113
|
+
verify=ssl_context,
|
|
114
|
+
**kwargs,
|
|
115
|
+
)
|
|
116
|
+
except ValueError as e:
|
|
117
|
+
if "Unknown scheme for proxy URL" in e.args[0] and "socks://" in e.args[0]:
|
|
118
|
+
print(
|
|
119
|
+
f"Invalid SOCKS proxy: Cielo only supports http://, https:// and socks5:// schemes: {e.args[0]}",
|
|
120
|
+
file=sys.stderr,
|
|
121
|
+
)
|
|
122
|
+
exit(-1)
|
|
123
|
+
else:
|
|
124
|
+
raise e from None
|
|
125
|
+
github_token = github_token or GitHubSession.Token.get_gh_token()
|
|
126
|
+
self.github_token = github_token
|
|
127
|
+
|
|
128
|
+
raw_headers = {
|
|
129
|
+
"User-Agent": type(self).get_user_agent(),
|
|
130
|
+
}
|
|
131
|
+
if github_token is not None:
|
|
132
|
+
raw_headers["Authorization"] = f"Bearer {github_token}"
|
|
133
|
+
self.headers = httpx.Headers(raw_headers)
|
|
134
|
+
|
|
135
|
+
def api(
|
|
136
|
+
self,
|
|
137
|
+
repo: RepoInfo,
|
|
138
|
+
endpoint: str,
|
|
139
|
+
method: str,
|
|
140
|
+
*args,
|
|
141
|
+
**kwargs,
|
|
142
|
+
) -> Any:
|
|
143
|
+
url = repo.api + endpoint
|
|
144
|
+
req = self.request(method, url, *args, **kwargs)
|
|
145
|
+
req.raise_for_status()
|
|
146
|
+
return req.json()
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def get_user_agent(Self) -> str:
|
|
150
|
+
return f"cielo/{__version__}"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def get_commit_date(
|
|
154
|
+
commit: str,
|
|
155
|
+
repo: RepoInfo,
|
|
156
|
+
session: Optional[GitHubSession] = None,
|
|
157
|
+
) -> Optional[datetime]:
|
|
158
|
+
if session is None:
|
|
159
|
+
session = GitHubSession()
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
response = session.api(repo, f"/commits/{commit}", "get")
|
|
163
|
+
except httpx.HTTPError:
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
date = response["commit"]["author"]["date"]
|
|
167
|
+
commit_date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ")
|
|
168
|
+
return commit_date
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def get_releases(session: Optional[GitHubSession] = None) -> List[Mapping[str, Any]]:
|
|
172
|
+
if session is None:
|
|
173
|
+
session = GitHubSession()
|
|
174
|
+
|
|
175
|
+
return session.api(volare_repo, "/releases", "get", params={"per_page": 100})
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def get_release_links(
|
|
179
|
+
release: str, session: Optional[GitHubSession] = None
|
|
180
|
+
) -> Mapping[str, Any]:
|
|
181
|
+
if session is None:
|
|
182
|
+
session = GitHubSession()
|
|
183
|
+
|
|
184
|
+
return session.api(volare_repo, f"/releases/tags/{release}", "get")
|