ciel 0.21.0.dev1__tar.gz → 1.0.0__tar.gz

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.
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ciel
3
- Version: 0.21.0.dev1
3
+ Version: 1.0.0
4
4
  Summary: An PDK builder/version manager for PDKs in the open_pdks format
5
5
  Home-page: https://github.com/fossi-foundation/ciel
6
6
  License: Apache-2.0
7
- Author: Efabless Corporation and Contributors
8
- Author-email: donn@efabless.com
7
+ Author: Mohamed Gaber
8
+ Author-email: me@donn.website
9
9
  Requires-Python: >=3.8
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: License :: OSI Approved :: Apache Software License
@@ -1,3 +1,7 @@
1
+ # Copyright 2025 The American University in Cairo
2
+ #
3
+ # Adapted from the Volare project
4
+ #
1
5
  # Copyright 2022-2023 Efabless Corporation
2
6
  #
3
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,11 +29,7 @@ from .common import (
25
29
  resolve_version,
26
30
  )
27
31
  from .click_common import (
28
- opt,
29
- opt_build,
30
- opt_push,
31
32
  opt_pdk_root,
32
- opt_token,
33
33
  )
34
34
  from .manage import (
35
35
  print_installed_list,
@@ -41,6 +41,8 @@ from .build import (
41
41
  build_cmd,
42
42
  push_cmd,
43
43
  )
44
+ from .github import opt_github_token
45
+ from .source import opt_data_source
44
46
 
45
47
 
46
48
  @click.command("output")
@@ -113,9 +115,10 @@ def rm_cmd(pdk_root, pdk, version):
113
115
 
114
116
 
115
117
  @click.command("ls")
116
- @opt_token
118
+ @opt_data_source
119
+ @opt_github_token
117
120
  @opt_pdk_root
118
- def list_cmd(pdk_root, pdk):
121
+ def list_cmd(data_source, pdk_root, pdk):
119
122
  """Lists PDK versions that are locally installed. JSON if not outputting to a tty."""
120
123
 
121
124
  pdk_versions = Version.get_all_installed(pdk_root, pdk)
@@ -125,6 +128,7 @@ def list_cmd(pdk_root, pdk):
125
128
  print_installed_list(
126
129
  pdk_root,
127
130
  pdk,
131
+ data_source=data_source,
128
132
  console=console,
129
133
  installed_list=pdk_versions,
130
134
  )
@@ -133,14 +137,14 @@ def list_cmd(pdk_root, pdk):
133
137
 
134
138
 
135
139
  @click.command("ls-remote")
136
- @opt_token
140
+ @opt_github_token
141
+ @opt_data_source
137
142
  @opt_pdk_root
138
- def list_remote_cmd(pdk_root, pdk):
143
+ def list_remote_cmd(data_source, pdk_root, pdk):
139
144
  """Lists PDK versions that are remotely available. JSON if not outputting to a tty."""
140
145
 
141
146
  try:
142
- all_versions = Version._from_github()
143
- pdk_versions = all_versions.get(pdk) or []
147
+ pdk_versions = data_source.get_available_versions(pdk)
144
148
 
145
149
  if sys.stdout.isatty():
146
150
  console = Console()
@@ -148,6 +152,13 @@ def list_remote_cmd(pdk_root, pdk):
148
152
  else:
149
153
  for version in pdk_versions:
150
154
  print(version.name)
155
+ except ValueError as e:
156
+ if sys.stdout.isatty():
157
+ console = Console()
158
+ console.print(f"[red]{e}")
159
+ else:
160
+ print(f"{e}", file=sys.stderr)
161
+ sys.exit(-1)
151
162
  except httpx.HTTPStatusError as e:
152
163
  if sys.stdout.isatty():
153
164
  console = Console()
@@ -184,7 +195,8 @@ def path_cmd(pdk_root, pdk, version):
184
195
 
185
196
 
186
197
  @click.command("enable")
187
- @opt_token
198
+ @opt_data_source
199
+ @opt_github_token
188
200
  @opt_pdk_root
189
201
  @click.option(
190
202
  "-f",
@@ -202,6 +214,7 @@ def path_cmd(pdk_root, pdk, version):
202
214
  )
203
215
  @click.argument("version", required=False)
204
216
  def enable_cmd(
217
+ data_source,
205
218
  pdk_root,
206
219
  pdk,
207
220
  tool_metadata_file_path,
@@ -234,6 +247,7 @@ def enable_cmd(
234
247
  version,
235
248
  include_libraries=include_libraries,
236
249
  output=console,
250
+ data_source=data_source,
237
251
  )
238
252
  except Exception as e:
239
253
  console.print(f"[red]{e}")
@@ -241,7 +255,8 @@ def enable_cmd(
241
255
 
242
256
 
243
257
  @click.command("fetch")
244
- @opt_token
258
+ @opt_data_source
259
+ @opt_github_token
245
260
  @opt_pdk_root
246
261
  @click.option(
247
262
  "-f",
@@ -259,6 +274,7 @@ def enable_cmd(
259
274
  )
260
275
  @click.argument("version", required=False)
261
276
  def fetch_cmd(
277
+ data_source,
262
278
  pdk_root,
263
279
  pdk,
264
280
  tool_metadata_file_path,
@@ -287,6 +303,7 @@ def fetch_cmd(
287
303
 
288
304
  try:
289
305
  version = fetch(
306
+ data_source=data_source,
290
307
  pdk_root=pdk_root,
291
308
  pdk=pdk,
292
309
  version=version,
@@ -300,79 +317,6 @@ def fetch_cmd(
300
317
  exit(-1)
301
318
 
302
319
 
303
- @click.command("enable_or_build", hidden=True)
304
- @opt_token
305
- @opt_pdk_root
306
- @opt_push
307
- @opt_build
308
- @opt("--also-push/--dont-push", default=False, help="Also push.")
309
- @click.option(
310
- "-f",
311
- "--metadata-file",
312
- "tool_metadata_file_path",
313
- default=None,
314
- help="Explicitly define a tool metadata file instead of searching for a metadata file",
315
- )
316
- @click.argument("version")
317
- def enable_or_build_cmd(
318
- include_libraries,
319
- jobs,
320
- pdk_root,
321
- pdk,
322
- owner,
323
- repository,
324
- pre,
325
- clear_build_artifacts,
326
- tool_metadata_file_path,
327
- also_push,
328
- version,
329
- use_repo_at,
330
- push_libraries,
331
- ):
332
- """
333
- Attempts to activate a given PDK version. If the version is not found locally or remotely,
334
- it will instead attempt to build said version.
335
-
336
- Parameters: <version>
337
- """
338
- if include_libraries == ():
339
- include_libraries = None
340
- if push_libraries == ():
341
- push_libraries = include_libraries
342
-
343
- console = Console()
344
- try:
345
- version = resolve_version(version, tool_metadata_file_path)
346
- except Exception as e:
347
- console.print(f"Could not determine open_pdks version: {e}")
348
- exit(-1)
349
- try:
350
- enable(
351
- pdk_root=pdk_root,
352
- pdk=pdk,
353
- version=version,
354
- build_if_not_found=True,
355
- also_push=also_push,
356
- build_kwargs={
357
- "include_libraries": include_libraries,
358
- "jobs": jobs,
359
- "clear_build_artifacts": clear_build_artifacts,
360
- "use_repo_at": use_repo_at,
361
- },
362
- push_kwargs={
363
- "owner": owner,
364
- "repository": repository,
365
- "pre": pre,
366
- "push_libraries": push_libraries,
367
- },
368
- include_libraries=include_libraries,
369
- output=console,
370
- )
371
- except Exception as e:
372
- console.print(f"[red]{e}")
373
- exit(-1)
374
-
375
-
376
320
  @click.group()
377
321
  @click.version_option(
378
322
  __version__,
@@ -404,7 +348,6 @@ cli.add_command(list_cmd)
404
348
  cli.add_command(list_remote_cmd)
405
349
  cli.add_command(enable_cmd)
406
350
  cli.add_command(fetch_cmd)
407
- cli.add_command(enable_or_build_cmd)
408
351
 
409
352
  try:
410
353
  import ssl # noqa: F401
@@ -1,3 +1,7 @@
1
+ # Copyright 2025 The American University in Cairo
2
+ #
3
+ # Adapted from the Volare project
4
+ #
1
5
  # Copyright 2022-2023 Efabless Corporation
2
6
  #
3
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,7 +32,7 @@ from rich.progress import Progress
28
32
  from ..github import (
29
33
  GitHubSession,
30
34
  get_commit_date,
31
- volare_repo,
35
+ opt_github_token,
32
36
  )
33
37
  from ..common import (
34
38
  Version,
@@ -40,7 +44,6 @@ from ..click_common import (
40
44
  opt_push,
41
45
  opt_build,
42
46
  opt_pdk_root,
43
- opt_token,
44
47
  )
45
48
  from ..families import Family
46
49
 
@@ -79,7 +82,7 @@ def build(
79
82
 
80
83
 
81
84
  @click.command("build")
82
- @opt_token
85
+ @opt_github_token
83
86
  @opt_pdk_root
84
87
  @opt_build
85
88
  @click.option(
@@ -135,16 +138,14 @@ def push(
135
138
  pdk,
136
139
  version,
137
140
  *,
138
- owner=volare_repo.owner,
139
- repository=volare_repo.name,
141
+ owner,
142
+ repository,
140
143
  pre=False,
141
144
  push_libraries=None,
142
- session: Optional[GitHubSession] = None,
143
145
  ):
144
146
  family = Family.by_name[pdk]
145
147
 
146
- if session is None:
147
- session = GitHubSession()
148
+ session = GitHubSession()
148
149
  if session.github_token is None:
149
150
  raise TypeError("No GitHub token was provided.")
150
151
 
@@ -231,7 +232,7 @@ def push(
231
232
 
232
233
 
233
234
  @click.command("push", hidden=True)
234
- @opt_token
235
+ @opt_github_token
235
236
  @opt_pdk_root
236
237
  @opt_push
237
238
  @click.argument("version")
@@ -1,3 +1,7 @@
1
+ # Copyright 2025 The American University in Cairo
2
+ #
3
+ # Adapted from the Volare project
4
+ #
1
5
  # Copyright 2022-2023 Efabless Corporation
2
6
  #
3
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,12 +17,11 @@
13
17
  # limitations under the License.
14
18
  import os
15
19
  from functools import partial
16
- from typing import Callable, Optional
20
+ from typing import Callable
17
21
 
18
22
  import click
19
23
 
20
24
  from .common import VOLARE_RESOLVED_HOME
21
- from .github import volare_repo, GitHubSession
22
25
 
23
26
  opt = partial(click.option, show_default=True)
24
27
 
@@ -77,12 +80,10 @@ def opt_build(function: Callable):
77
80
 
78
81
 
79
82
  def opt_push(function: Callable):
80
- function = opt("-o", "--owner", default=volare_repo.owner, help="Repository Owner")(
81
- function
82
- )
83
- function = opt("-r", "--repository", default=volare_repo.name, help="Repository")(
83
+ function = opt("-o", "--owner", default="efabless", help="Repository Owner")(
84
84
  function
85
85
  )
86
+ function = opt("-r", "--repository", default="volare", help="Repository")(function)
86
87
  function = opt(
87
88
  "--pre/--prod", default=False, help="Push as pre-release or production"
88
89
  )(function)
@@ -95,25 +96,3 @@ def opt_push(function: Callable):
95
96
  help="Push only libraries in this list. You can use -L multiple times to include multiple libraries. Pass 'None' to push all libraries built.",
96
97
  )(function)
97
98
  return function
98
-
99
-
100
- def set_token_cb(
101
- ctx: click.Context,
102
- param: click.Parameter,
103
- value: Optional[str],
104
- ):
105
- GitHubSession.Token.override = value
106
-
107
-
108
- def opt_token(function: Callable) -> Callable:
109
- function = opt(
110
- "-t",
111
- "--token",
112
- "session",
113
- default=None,
114
- required=False,
115
- expose_value=False,
116
- help="Replace the GitHub token used for GitHub requests, which is by default the value of the environment variable GITHUB_TOKEN or None.",
117
- callback=set_token_cb,
118
- )(function)
119
- return function
@@ -1,3 +1,7 @@
1
+ # Copyright 2025 The American University in Cairo
2
+ #
3
+ # Modified from the Volare project
4
+ #
1
5
  # Copyright 2022-2023 Efabless Corporation
2
6
  #
3
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,14 +16,12 @@
12
16
  # See the License for the specific language governing permissions and
13
17
  # limitations under the License.
14
18
  import os
15
- import re
16
19
  import shutil
17
20
  import pathlib
18
21
  from datetime import datetime
19
22
  from dataclasses import dataclass
20
- from typing import Iterable, Optional, List, Dict, Tuple
23
+ from typing import Optional, List
21
24
 
22
- from . import github
23
25
  from .families import Family
24
26
 
25
27
  # -- Assorted Helper Functions
@@ -142,72 +144,6 @@ class Version(object):
142
144
  if os.path.isdir(os.path.join(versions_dir, version))
143
145
  ]
144
146
 
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
147
 
212
148
  def resolve_version(
213
149
  version: Optional[str],
@@ -1,3 +1,7 @@
1
+ # Copyright 2025 The American University in Cairo
2
+ #
3
+ # Modified from the Volare project
4
+ #
1
5
  # Copyright 2022-2023 Efabless Corporation
2
6
  #
3
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,11 +16,12 @@
12
16
  # See the License for the specific language governing permissions and
13
17
  # limitations under the License.
14
18
  import os
15
- import subprocess
16
19
  import sys
20
+ import click
21
+ import subprocess
17
22
  from datetime import datetime
18
23
  from dataclasses import dataclass
19
- from typing import Any, ClassVar, List, Mapping, Optional
24
+ from typing import Any, ClassVar, Optional, Callable
20
25
 
21
26
  import httpx
22
27
  import ssl
@@ -28,6 +33,10 @@ class RepoInfo:
28
33
  owner: str
29
34
  name: str
30
35
 
36
+ @classmethod
37
+ def from_id(self, id_str: str) -> "RepoInfo":
38
+ return RepoInfo(*id_str.split("/", maxsplit=1))
39
+
31
40
  @property
32
41
  def id(self):
33
42
  return f"{self.owner}/{self.name}"
@@ -41,11 +50,6 @@ class RepoInfo:
41
50
  return f"https://api.github.com/repos/{self.id}"
42
51
 
43
52
 
44
- volare_repo = RepoInfo(
45
- os.getenv("VOLARE_REPO_OWNER", "efabless"),
46
- os.getenv("VOLARE_REPO_NAME", "volare"),
47
- )
48
-
49
53
  opdks_repo = RepoInfo(
50
54
  os.getenv("OPDKS_REPO_OWNER", "RTimothyEdwards"),
51
55
  os.getenv("OPDKS_REPO_NAME", "open_pdks"),
@@ -68,16 +72,10 @@ class GitHubSession(httpx.Client):
68
72
  # 0. Lowest priority: ghcli
69
73
  try:
70
74
  token = subprocess.check_output(
71
- [
72
- "gh",
73
- "auth",
74
- "token",
75
- ],
75
+ ["gh", "auth", "token"],
76
76
  encoding="utf8",
77
77
  ).strip()
78
- except FileNotFoundError:
79
- pass
80
- except subprocess.CalledProcessError:
78
+ except Exception:
81
79
  pass
82
80
 
83
81
  # 1. Higher priority: environment GITHUB_TOKEN
@@ -168,17 +166,23 @@ def get_commit_date(
168
166
  return commit_date
169
167
 
170
168
 
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")
169
+ def set_token_cb(
170
+ ctx: click.Context,
171
+ param: click.Parameter,
172
+ value: Optional[str],
173
+ ):
174
+ GitHubSession.Token.override = value
175
+
176
+
177
+ def opt_github_token(function: Callable) -> Callable:
178
+ function = click.option(
179
+ "-t",
180
+ "--github-token",
181
+ default=None,
182
+ required=False,
183
+ expose_value=False,
184
+ show_default=True,
185
+ help="Replace the token used for GitHub requests, which is by default the value of the environment variable GITHUB_TOKEN or None.",
186
+ callback=set_token_cb,
187
+ )(function)
188
+ return function
@@ -1,3 +1,7 @@
1
+ # Copyright 2025 The American University in Cairo
2
+ #
3
+ # Modified from the Volare project
4
+ #
1
5
  # Copyright 2022-2023 Efabless Corporation
2
6
  #
3
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,7 +31,6 @@ import zstandard as zstd
27
31
  from rich.console import Console
28
32
 
29
33
  from .build.git_multi_clone import mkdirp
30
- from .github import GitHubSession
31
34
  from .common import (
32
35
  Version,
33
36
  get_versions_dir,
@@ -35,6 +38,7 @@ from .common import (
35
38
  )
36
39
  from .build import build, push
37
40
  from .families import Family
41
+ from .source import DataSource
38
42
 
39
43
 
40
44
  class VersionNotFound(Exception):
@@ -45,9 +49,9 @@ def print_installed_list(
45
49
  pdk_root: str,
46
50
  pdk: str,
47
51
  *,
52
+ data_source: DataSource,
48
53
  console: Console,
49
54
  installed_list: List[Version],
50
- session: Optional[GitHubSession] = None,
51
55
  ):
52
56
  if len(installed_list) == 0:
53
57
  console.print("[red]No PDKs installed.")
@@ -56,8 +60,7 @@ def print_installed_list(
56
60
  versions = installed_list
57
61
 
58
62
  try:
59
- all_remote_versions = Version._from_github(session)
60
- remote_versions = all_remote_versions.get(pdk) or []
63
+ remote_versions = data_source.get_available_versions(pdk)
61
64
  remote_version_dict = {rv.name: rv for rv in remote_versions}
62
65
  for installed in installed_list:
63
66
  remote_version = remote_version_dict.get(installed.name)
@@ -118,17 +121,14 @@ def fetch(
118
121
  pdk: str,
119
122
  version: str,
120
123
  *,
124
+ data_source: DataSource,
121
125
  build_if_not_found=False,
122
126
  also_push=False,
123
127
  build_kwargs: dict = {},
124
128
  push_kwargs: dict = {},
125
129
  include_libraries: Optional[Iterable[str]] = None,
126
130
  output: Union[Console, io.TextIOWrapper] = Console(),
127
- session: Optional[GitHubSession] = None,
128
131
  ) -> Version:
129
- if session is None:
130
- session = GitHubSession()
131
-
132
132
  console = output
133
133
  if not isinstance(console, Console):
134
134
  console = Console(file=console)
@@ -178,16 +178,18 @@ def fetch(
178
178
 
179
179
  tarball_paths = []
180
180
  try:
181
- release_link_list = version_object.get_release_links(
182
- missing_libraries,
183
- include_common=common_missing,
184
- session=session,
185
- )
181
+ client, assets = data_source.get_downloads_for_version(version_object)
182
+ assets_filtered = []
183
+ for asset in assets:
184
+ if asset.content == "common" and common_missing:
185
+ assets_filtered.append(asset)
186
+ elif asset.content in missing_libraries:
187
+ assets_filtered.append(asset)
186
188
  tarball_directory = tempfile.TemporaryDirectory(suffix=".ciel")
187
- for name, link in release_link_list:
188
- tarball_path = os.path.join(tarball_directory.name, name)
189
+ for asset in assets_filtered:
190
+ tarball_path = os.path.join(tarball_directory.name, asset.filename)
189
191
  tarball_paths.append(tarball_path)
190
- with session.stream("get", link) as r, rich.progress.Progress(
192
+ with client.stream("get", asset.url) as r, rich.progress.Progress(
191
193
  console=console
192
194
  ) as p:
193
195
  total_str: Optional[str] = r.headers.get("Content-length", None)
@@ -195,7 +197,7 @@ def fetch(
195
197
  if total_str is not None:
196
198
  total_int = int(total_str)
197
199
  task = p.add_task(
198
- f"Downloading {name}…",
200
+ f"Downloading {asset.filename}…",
199
201
  total=total_int,
200
202
  )
201
203
  r.raise_for_status()
@@ -204,7 +206,7 @@ def fetch(
204
206
  p.advance(task, advance=len(chunk))
205
207
  f.write(chunk)
206
208
 
207
- with console.status(f"Unpacking {name}…"):
209
+ with console.status(f"Unpacking {asset.filename}…"):
208
210
  stream = zstd.open(tarball_path, mode="rb")
209
211
  with tarfile.TarFile(fileobj=stream, mode="r") as tf:
210
212
  for file in tf:
@@ -216,7 +218,7 @@ def fetch(
216
218
  io = tf.extractfile(file)
217
219
  if io is None:
218
220
  raise IOError(
219
- f"Failed to unpack file in {name}'s tarball: {file.name}."
221
+ f"Failed to unpack file in {asset.filename}'s tarball: {file.name}."
220
222
  )
221
223
  with open(final_path, "wb") as f:
222
224
  f.write(io.read())
@@ -242,7 +244,6 @@ def fetch(
242
244
  pdk_root=pdk_root,
243
245
  pdk=pdk,
244
246
  version=version,
245
- session=session,
246
247
  **push_kwargs,
247
248
  )
248
249
  else:
@@ -285,16 +286,14 @@ def enable(
285
286
  pdk: str,
286
287
  version: str,
287
288
  *,
289
+ data_source: DataSource,
288
290
  build_if_not_found: bool = False,
289
291
  also_push: bool = False,
290
292
  build_kwargs: dict = {},
291
293
  push_kwargs: dict = {},
292
294
  include_libraries: Optional[List[str]] = None,
293
295
  output: Union[Console, io.TextIOWrapper] = Console(),
294
- session: Optional[GitHubSession] = None,
295
296
  ) -> Version:
296
- if session is None:
297
- session = GitHubSession()
298
297
 
299
298
  console = output
300
299
  if not isinstance(console, Console):
@@ -315,13 +314,13 @@ def enable(
315
314
  pdk_root,
316
315
  pdk,
317
316
  version,
317
+ data_source=data_source,
318
318
  build_if_not_found=build_if_not_found,
319
319
  also_push=also_push,
320
320
  build_kwargs=build_kwargs,
321
321
  push_kwargs=push_kwargs,
322
322
  include_libraries=include_libraries,
323
323
  output=output,
324
- session=session,
325
324
  )
326
325
 
327
326
  current_file = os.path.join(get_ciel_dir(pdk_root, pdk), "current")
@@ -0,0 +1,229 @@
1
+ # Copyright 2025 The American University in Cairo
2
+ #
3
+ # Modified from the Volare project
4
+ #
5
+ # Copyright 2022-2023 Efabless Corporation
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ import re
19
+ import sys
20
+ from dataclasses import dataclass
21
+ from typing import Dict, ClassVar, List, Tuple, Type, Callable
22
+
23
+ import click
24
+ import httpx
25
+
26
+ from .github import GitHubSession, RepoInfo
27
+ from .common import Version, date_from_iso8601
28
+
29
+
30
+ @dataclass
31
+ class Asset:
32
+ content: str
33
+ filename: str
34
+ url: str
35
+
36
+
37
+ class DataSource(object):
38
+ factory: ClassVar[Dict[str, Type["DataSource"]]] = {}
39
+ default: ClassVar["DataSource"]
40
+
41
+ def __init__(self, argument: str):
42
+ raise NotImplementedError()
43
+
44
+ def get_available_versions(self, pdk: str) -> List[Version]:
45
+ raise NotImplementedError()
46
+
47
+ def get_downloads_for_version(
48
+ self, version: Version
49
+ ) -> Tuple[httpx.Client, List[Asset]]:
50
+ raise NotImplementedError()
51
+
52
+
53
+ class GitHubReleasesDataSource(DataSource):
54
+ def __init__(self, repo_id: str):
55
+ self.session = GitHubSession()
56
+ self.repo = RepoInfo.from_id(repo_id)
57
+
58
+ def get_available_versions(self, pdk: str) -> List[Version]:
59
+ page = 1
60
+ last = self.session.api(
61
+ self.repo,
62
+ "/releases",
63
+ "get",
64
+ params={"page": 1, "per_page": 100},
65
+ )
66
+ releases = last
67
+ while len(last) == 100:
68
+ page += 1
69
+ last = self.session.api(
70
+ self.repo,
71
+ "/releases",
72
+ "get",
73
+ params={"page": 1, "per_page": 100},
74
+ )
75
+ releases += last
76
+
77
+ versions = []
78
+ commit_rx = re.compile(r"released on ([\d\-\:TZ]+)")
79
+ for release in releases:
80
+ if release["draft"]:
81
+ continue
82
+
83
+ family, hash = release["tag_name"].rsplit("-", maxsplit=1)
84
+
85
+ if pdk != family:
86
+ continue
87
+
88
+ upload_date = date_from_iso8601(release["published_at"])
89
+ commit_date = None
90
+
91
+ commit_date_match = commit_rx.search(release["body"])
92
+ if commit_date_match is not None:
93
+ commit_date = date_from_iso8601(commit_date_match[1])
94
+
95
+ remote_version = Version(
96
+ name=hash,
97
+ pdk=family,
98
+ commit_date=commit_date,
99
+ upload_date=upload_date,
100
+ prerelease=release["prerelease"],
101
+ )
102
+ versions.append(remote_version)
103
+
104
+ versions.sort(reverse=True)
105
+ if len(versions) == 0:
106
+ raise ValueError(
107
+ f"No versions found for '{pdk}' on github.com/{self.repo.id}"
108
+ )
109
+ return versions
110
+
111
+ def get_downloads_for_version(
112
+ self, version: Version
113
+ ) -> Tuple[httpx.Client, List[Asset]]:
114
+ release = self.session.api(
115
+ self.repo,
116
+ f"/releases/tags/{version.pdk}-{version.name}",
117
+ "get",
118
+ )
119
+
120
+ assets = release["assets"]
121
+ zst_files = []
122
+ for asset in assets:
123
+ if asset["name"].endswith(".tar.zst"):
124
+ content = asset["name"][:-8]
125
+ zst_files.append(
126
+ Asset(content, asset["name"], asset["browser_download_url"])
127
+ )
128
+ return (self.session, zst_files)
129
+
130
+
131
+ DataSource.factory["github-releases"] = GitHubReleasesDataSource
132
+
133
+
134
+ class StaticWebDataSource(DataSource):
135
+ def __init__(self, base_url: str):
136
+ self.session = GitHubSession()
137
+ self.base_url = base_url
138
+
139
+ def get_available_versions(self, pdk: str) -> List[Version]:
140
+ req = self.session.request("GET", self.base_url + f"/{pdk}/manifest.json")
141
+ try:
142
+ req.raise_for_status()
143
+ except httpx.HTTPStatusError as e:
144
+ if e.response.status_code == 404:
145
+ raise ValueError(
146
+ f"No versions found for '{pdk}' at '{self.base_url}'"
147
+ ) from None
148
+ else:
149
+ raise e from None
150
+ manifest = req.json()
151
+
152
+ versions = []
153
+ for version in manifest["versions"]:
154
+ remote_version = Version(
155
+ name=version["version"],
156
+ pdk=manifest["pdk"],
157
+ commit_date=date_from_iso8601(version["date"]),
158
+ prerelease=version.get("prerelease", False),
159
+ )
160
+ versions.append(remote_version)
161
+
162
+ versions.sort(reverse=True)
163
+ if len(versions) == 0:
164
+ raise ValueError(f"No versions found for '{pdk}' on '{self.base_url}'")
165
+ return versions
166
+
167
+ def get_downloads_for_version(
168
+ self, version: Version
169
+ ) -> Tuple[httpx.Client, List[Asset]]:
170
+ req = self.session.request(
171
+ "GET", self.base_url + f"/{version.pdk}/{version.name}/manifest.json"
172
+ )
173
+ try:
174
+ req.raise_for_status()
175
+ except httpx.HTTPStatusError as e:
176
+ if e.response.status_code == 404:
177
+ raise ValueError(
178
+ f"Manifest for '{version.pdk}/{version.name}' at '{self.base_url}'"
179
+ ) from None
180
+ else:
181
+ raise e from None
182
+ manifest = req.json()
183
+ assets = []
184
+ for asset in manifest["assets"]:
185
+ assets.append(Asset(**asset))
186
+ return (self.session, assets)
187
+
188
+
189
+ DataSource.factory["static-web"] = StaticWebDataSource
190
+
191
+
192
+ def data_source_cb(
193
+ ctx: click.Context,
194
+ param: click.Parameter,
195
+ value: str,
196
+ ):
197
+ source_id = value
198
+ elements = source_id.split(":", maxsplit=1)
199
+ if len(elements) != 2:
200
+ print(
201
+ "Data source must be in the format '{{class_id}}:{{argument}}' where class_id is one of:",
202
+ file=sys.stderr,
203
+ )
204
+ for id in DataSource.factory:
205
+ print(f"* {id}", file=sys.stderr)
206
+ ctx.exit(-1)
207
+ cls_id, target = elements
208
+ cls = DataSource.factory.get(cls_id)
209
+ if cls is None:
210
+ print(
211
+ f"Unknown data source class '{cls_id}', must be one of:",
212
+ file=sys.stderr,
213
+ )
214
+ for id in DataSource.factory:
215
+ print(f"* {id}", file=sys.stderr)
216
+ ctx.exit(-1)
217
+ return cls(target)
218
+
219
+
220
+ def opt_data_source(function: Callable) -> Callable:
221
+ function = click.option(
222
+ "--data-source",
223
+ default="static-web:https://fossi-foundation.github.io/ciel-releases",
224
+ required=False,
225
+ show_default=True,
226
+ help="The data source to use for operations that may require contacting a remote server, in the format '{class_id}:{argument}'",
227
+ callback=data_source_cb,
228
+ )(function)
229
+ return function
@@ -1,8 +1,11 @@
1
1
  [tool.poetry]
2
2
  name = "ciel"
3
- version = "0.21.0dev1"
3
+ version = "1.0.0"
4
4
  description = "An PDK builder/version manager for PDKs in the open_pdks format"
5
- authors = ["Efabless Corporation and Contributors <donn@efabless.com>"]
5
+ authors = [
6
+ "Mohamed Gaber <me@donn.website>",
7
+ "Efabless Corporation"
8
+ ]
6
9
  readme = "Readme.md"
7
10
  license = "Apache-2.0"
8
11
  repository = "https://github.com/fossi-foundation/ciel"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes