diff --git a/.github/scripts/generate_tag.py b/.github/scripts/generate_tag.py deleted file mode 100644 index c3d8afc..0000000 --- a/.github/scripts/generate_tag.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# Copyright 2020 Efabless Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import os -import sys - -from gh import gh - -sys.path.insert(0, os.getcwd()) - -import ciel # noqa: E402 - -print("Getting tags…") - -latest_tag = None -latest_tag_commit = None -tags = [pair[1] for pair in gh.ciel.tags] - -tag_exists = ciel.__version__ in tags - -if tag_exists: - print("Tag already exists. Leaving NEW_TAG unaltered.") -else: - new_tag = ciel.__version__ - - print("Found new tag %s." % new_tag) - gh.export_env("NEW_TAG", new_tag) diff --git a/.github/scripts/gh.py b/.github/scripts/gh.py deleted file mode 100644 index f8c1416..0000000 --- a/.github/scripts/gh.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2020 Efabless Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import os -import subprocess -from types import SimpleNamespace - - -def export_env_default(key, value): - with open(os.getenv("GITHUB_ENV"), "a") as f: - f.write("%s=%s\n" % (key, value)) - - -export_env = export_env_default - - -class Repo(object): - def __init__(self, name, url, branch_rx=None, extraction_rx=None): - print("[Repo Object] Initializing repo %s with URL %s…" % (name, url)) - self.name = name - self.url = url - self.commit = None - self.branch_rx = branch_rx - self.extraction_rx = extraction_rx - - self._latest_commit = None - self._branches = None - self._tags = None - - @property - def latest_commit(self): - if self._latest_commit is None: - print("[Repo Object] Fetching latest commit for %s…" % self.name) - p = subprocess.check_output(["git", "ls-remote", self.url]).decode("utf8") - for line in p.split("\n"): - if "HEAD" in line: - self._latest_commit = line[:40] - return self._latest_commit - - @property - def branches(self): - if self._branches is None: - print("[Repo Object] Fetching branches for %s…" % self.name) - p = subprocess.check_output( - ["git", "ls-remote", "--heads", self.url] - ).decode("utf8") - branches = [] - for line in p.split("\n"): - if line.strip() == "": - continue - - match = line.split() - - hash = match[0] - name = match[1] - - branches.append((hash, name)) - self._branches = branches - return self._branches - - @property - def tags(self): - if self._tags is None: - print("[Repo Object] Fetching tags for %s…" % self.name) - p = subprocess.check_output( - ["git", "ls-remote", "--tags", "--sort=creatordate", self.url] - ).decode("utf8") - - tags = [] - for line in p.split("\n"): - if line.strip() == "": - continue - - match = line.split() - - hash = match[0] - name = match[1].split("/")[2] - - tags.append((hash, name)) - self._tags = tags - return self._tags - - def out_of_date(self): - return self.commit != self.latest_commit - - -if os.getenv("GITHUB_ACTIONS") != "true": - dn = os.path.dirname - git_directory = dn(dn(dn(os.path.realpath(__file__)))) - - def git_command(*args): - return subprocess.check_output(["git"] + list(args), cwd=git_directory).decode( - "utf-8" - )[:-1] - - repo_url = git_command("remote", "get-url", "origin") - branch = git_command("branch", "--show-current") - - os.environ["REPO_URL"] = repo_url - os.environ["GITHUB_WORKSPACE"] = git_directory - os.environ["GITHUB_EVENT_NAME"] = "workspace_dispatch" - os.environ["GITHUB_RUN_ID"] = "mock_gha_run" - - def export_env_alt(key, value): - os.environ[key] = value - print("Setting ENV[%s] to %s..." % (key, value)) - - export_env = export_env_alt - -origin = os.getenv("REPO_URL") -repo = Repo("ciel", origin) - -# public -gh = SimpleNamespace( - **{ - "run_id": os.getenv("GITHUB_RUN_ID"), - "origin": origin, - "root": os.getenv("GITHUB_WORKSPACE"), - "pdk": os.getenv("PDK_ROOT"), - "tool": os.getenv("TOOL"), - "event": SimpleNamespace(**{"name": os.getenv("GITHUB_EVENT_NAME")}), - "export_env": export_env, - "Repo": Repo, - "ciel": repo, - } -) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d79f32e..9b4274c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,16 +4,30 @@ name: CI on: push: branches: - - "*" + - "main" pull_request: +concurrency: + # Behavior: + # - Group all pull requests: latest push cancels previous ones + # - Group all branches: + # - If main/version- branches: run serially + # - Else, latest push cancels previous ones + group: > + ${{ + (github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number)) || + (github.event_name == 'push' && format('branch-{0}', github.ref_name)) || + '' + }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: lint: name: Lint Python Code runs-on: ubuntu-24.04 steps: - name: Check out Git repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Linters run: make venv - name: Lint @@ -24,7 +38,7 @@ jobs: needs: lint steps: - name: Check Git repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set Up Python uses: actions/setup-python@v4 with: @@ -66,46 +80,51 @@ jobs: runs-on: ubuntu-24.04 if: github.event_name == 'push' && github.ref_name == 'main' outputs: - new_tag: ${{ steps.new_tag.outputs.new_tag }} + version: ${{ steps.get_version.outputs.version }} steps: - name: Check out Git repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - - name: Export Repo URL - run: echo "REPO_URL=https://github.com/${{ github.repository }}" >> $GITHUB_ENV - - name: Set Up Python - uses: actions/setup-python@v4 - with: - python-version: "3.8" - - name: Set default for env.NEW_TAG - run: echo "NEW_TAG=NO_NEW_TAG" >> $GITHUB_ENV - - name: Check for new version + - id: get_version + name: Extract new version run: | - make venv - cd ${GITHUB_WORKSPACE}/ && ./venv/bin/python3 .github/scripts/generate_tag.py - - id: new_tag - name: Set new tag as job output - run: | - echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT + VERSION="$(perl -ne 'print $1 if /^version\s*=\s*\"(.+?)\"/' pyproject.toml)" + if ! gh release view $VERSION 2>&1 > /dev/null; then + echo "Uploading new version $VERSION if CI passes." + echo "version=$VERSION" >> $GITHUB_OUTPUT + fi publish: name: Publish runs-on: ubuntu-24.04 needs: [lint, build, test, check_new_version] - if: needs.check_new_version.outputs.new_tag != 'NO_NEW_TAG' + if: needs.check_new_version.outputs.version != '' environment: pypi permissions: # IMPORTANT: this permission is mandatory for Trusted Publishing id-token: write + contents: write steps: - uses: actions/download-artifact@v8 with: name: wheel path: ./dist - - name: Tag Commit - uses: tvdias/github-tagger@v0.0.1 - with: - tag: "${{ needs.check_new_version.outputs.new_tag }}" - repo-token: "${{ secrets.BOT_TOKEN }}" + # 2nd to last because it is immutable but likely to fail, so if it fails + # I don't want PyPI publishing + - name: Create release + run: | + prerelease_arg=() + version="${{ needs.check_new_version.outputs.version }}" + if [[ "$version" = *b* ]] || [[ "$version" = *a* ]] || [[ "$version" = *.dev* ]] || [[ "$version" = *rc* ]]; then + prerelease_arg+=( "--prerelease" ) + fi + + gh release create $version \ + --generate-notes \ + --target "${{ github.ref_name }}" \ + "${prerelease_arg[@]}" \ + ./dist/* + env: + GH_TOKEN: ${{ github.token }} - name: Publish uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/Authors.md b/Authors.md new file mode 100644 index 0000000..bb21b96 --- /dev/null +++ b/Authors.md @@ -0,0 +1,14 @@ +# Authors + +All categories arranged alphabetically. + +> This list may be non-exhaustive and primarily reflects copyright holders for +> significant portions of the code. See +> https://github.com/librelane/librelane/graphs/contributors for a full list of +> human authors. + +* Efabless Corporation \ (until February 2023) + * Mohamed Gaber \ + * Kareem Farid \ +* Leo Moser \ +* Mohamed Gaber \ diff --git a/OSAcknowledgements b/OSAcknowledgements deleted file mode 100644 index 160b963..0000000 --- a/OSAcknowledgements +++ /dev/null @@ -1,9 +0,0 @@ -sky130-builds - -©2021-2022 The American University in Cairo & The Cloud V Project - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Readme.md b/Readme.md index 85a57f0..6b32f68 100644 --- a/Readme.md +++ b/Readme.md @@ -46,11 +46,11 @@ ciel --version # About the builds In its current inception, ciel supports builds of **sky130** and **gf180mcu** PDKs using [Open-PDKs](https://github.com/rtimothyedwards/open_pdks), including the following libraries: -|sky130|gf180mcu|ihp-sg13g2| +|sky130|gf180mcu|ihp-sg13| |-|-|-| |sky130_fd_io|gf180mcu_fd_io|sg13g2_io| |sky130_fd_pr|gf180mcu_fd_pr|sg13g2_pr| -|sky130_fd_pr_reram|gf180mcu_fd_pr|sg13g2_pr| +|sky130_fd_pr_reram|-|-| |sky130_fd_sc_hd|gf180mcu_fd_sc_mcu7t5v0|sg13g2_stdcell| |sky130_ml_xx_hd|gf180mcu_fd_sc_mcu9t5v0|-| |sky130_fd_sc_hvl|gf180mcu_osu_sc_gp9t3v3|-| diff --git a/ciel/__init__.py b/ciel/__init__.py index 22c4904..4277d4a 100644 --- a/ciel/__init__.py +++ b/ciel/__init__.py @@ -1,3 +1,7 @@ +# Copyright 2026 Ciel Contributors +# +# Adapted from the Volare Project +# # Copyright 2022-2023 Efabless Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,14 +18,17 @@ from .manage import ( VersionNotFound, enable, - get, fetch, ) from .common import ( get_ciel_home, Version, ) -from .families import Family +from .families import ( + Family, + resolve_pdk_family, + resolve_pdk_variant, +) from .github import ( GitHubSession, ) diff --git a/ciel/__main__.py b/ciel/__main__.py index a351741..58f618a 100755 --- a/ciel/__main__.py +++ b/ciel/__main__.py @@ -1,4 +1,4 @@ -# Copyright 2025 The American University in Cairo +# Copyright 2025 Ciel Contributors # # Adapted from the Volare project # @@ -29,7 +29,7 @@ get_ciel_home, ) from .click_common import ( - opt_pdk_root, + opt_pdk, arg_version, ) from .manage import ( @@ -49,35 +49,27 @@ @click.command("output") -@opt_pdk_root -def output_cmd(pdk_root, pdk_family): - """Outputs the currently enabled PDK version. - - If not outputting to a tty, the output is either the version string - unembellished, or, if no current version is enabled, an empty output with an - exit code of 1. - """ +@opt_pdk +def output_cmd(pdk_root, pdk_tuple): + """Outputs the currently enabled PDK version.""" + pdk_family, _ = pdk_tuple version = Version.get_current(pdk_root, pdk_family) - if sys.stdout.isatty(): - if version is None: - print( - f"No version of the PDK {pdk_family} is currently enabled at {pdk_root}." - ) - print("Invoke ciel --help for assistance installing and enabling versions.") - exit(1) - else: - print(f"Installed: {pdk_family} v{version.name}") - print("Invoke ciel --help for assistance installing and enabling versions.") - else: - if version is None: - exit(1) - else: - print(version.name, end="") + if version is None: + print( + f"No version of the PDK {pdk_family} is currently enabled at {pdk_root}.", + file=sys.stderr, + ) + print( + "Invoke ciel --help for assistance installing and enabling versions.", + file=sys.stderr, + ) + sys.exit(1) + print(version.name, end="") @click.command("prune") -@opt_pdk_root +@opt_pdk @click.option( "--yes", is_flag=True, @@ -85,9 +77,10 @@ def output_cmd(pdk_root, pdk_family): expose_value=False, prompt="Are you sure? This will delete all non-enabled versions of the PDK from your computer.", ) -def prune_cmd(pdk_root, pdk_family): +def prune_cmd(pdk_root, pdk_tuple): """Removes all PDKs other than, if it exists, the one currently set as 'enabled' in the PDK root.""" + pdk_family, _ = pdk_tuple pdk_versions = Version.get_all_installed(pdk_root, pdk_family) for version in pdk_versions: if version.is_current(pdk_root): @@ -100,7 +93,7 @@ def prune_cmd(pdk_root, pdk_family): @click.command("optimize") -@opt_pdk_root +@opt_pdk @arg_version def optimize_cmd(pdk_root, pdk_family, version): """ @@ -120,8 +113,8 @@ def optimize_cmd(pdk_root, pdk_family, version): @click.command("optimize-all") -@opt_pdk_root -def optimize_all_cmd(pdk_root, pdk_family): +@opt_pdk +def optimize_all_cmd(pdk_root, pdk_tuple): """ [Experimental] This command attempts to save space by converting identical files across variants for all versions of a specific PDK family to symbolic @@ -134,6 +127,7 @@ def optimize_all_cmd(pdk_root, pdk_family): """ recovered = 0 + pdk_family, _ = pdk_tuple for version in Version.get_all_installed(pdk_root, pdk_family): recovered += optimize(pdk_root, version) @@ -142,7 +136,7 @@ def optimize_all_cmd(pdk_root, pdk_family): @click.command("rm") -@opt_pdk_root +@opt_pdk @click.option( "--yes", is_flag=True, @@ -151,9 +145,10 @@ def optimize_all_cmd(pdk_root, pdk_family): prompt="Are you sure? This will delete this version of the PDK from your computer.", ) @arg_version -def rm_cmd(pdk_root, pdk_family, version): +def rm_cmd(pdk_root, pdk_tuple, version): """Removes the PDK version specified.""" + pdk_family, _ = pdk_tuple version_object = Version(version, pdk_family) try: version_object.uninstall(pdk_root) @@ -166,10 +161,16 @@ def rm_cmd(pdk_root, pdk_family, version): @click.command("ls") @opt_data_source @opt_github_token -@opt_pdk_root -def list_cmd(data_source, pdk_root, pdk_family): - """Lists PDK versions that are locally installed. JSON if not outputting to a tty.""" +@opt_pdk +def list_cmd(data_source, pdk_root, pdk_tuple): + """ + Lists PDK versions that are locally installed. + + If not outputting to a tty, each version will be output on its own line + in plain text. + """ + pdk_family, _ = pdk_tuple pdk_versions = Version.get_all_installed(pdk_root, pdk_family) if sys.stdout.isatty(): @@ -182,16 +183,23 @@ def list_cmd(data_source, pdk_root, pdk_family): installed_list=pdk_versions, ) else: - print(json.dumps([version.name for version in pdk_versions]), end="") + for version in pdk_versions: + print(version.name) @click.command("ls-remote") @opt_github_token @opt_data_source -@opt_pdk_root -def list_remote_cmd(data_source, pdk_root, pdk_family): - """Lists PDK versions that are remotely available. JSON if not outputting to a tty.""" +@opt_pdk +def list_remote_cmd(data_source, pdk_root, pdk_tuple): + """ + Lists PDK versions that are remotely available. + + If not outputting to a tty, each version will be output on its own line + in plain text. + """ + pdk_family, _ = pdk_tuple try: pdk_versions = data_source.get_available_versions(pdk_family) @@ -202,40 +210,32 @@ def list_remote_cmd(data_source, pdk_root, pdk_family): for version in pdk_versions: print(version.name) except ValueError as e: - if sys.stdout.isatty(): - console = Console() - console.print(f"[red]{e}") - else: - print(f"{e}", file=sys.stderr) + console = Console(stderr=True) + console.print(f"[red]{e}") sys.exit(-1) except httpx.HTTPStatusError as e: - if sys.stdout.isatty(): - console = Console() - console.print(f"[red]Encountered an error when polling version list: {e}") - else: - print(f"Failed to get version list: {e}", file=sys.stderr) + console = Console(stderr=True) + console.print(f"[red]Encountered an error when polling version list: {e}") sys.exit(-1) except httpx.NetworkError as e: - if sys.stdout.isatty(): - console = Console() - console.print( - "[red]You don't appear to be connected to the Internet. ls-remote cannot be used." - ) - else: - print(f"Failed to connect to remote server: {e}", file=sys.stderr) + console = Console(stderr=True) + console.print( + f"[red]You don't appear to be connected to the Internet. ls-remote cannot be used.: {e}" + ) sys.exit(-1) @click.command("path") -@opt_pdk_root +@opt_pdk @arg_version -def path_cmd(pdk_root, pdk_family, version): +def path_cmd(pdk_root, pdk_tuple, version): """ Prints the path of the ciel PDK root. If a version is provided over the commandline, it prints the path to this version instead. """ + pdk_family, _ = pdk_tuple if version is not None: version = Version(version, pdk_family) print(version.get_dir(pdk_root), end="") @@ -246,7 +246,7 @@ def path_cmd(pdk_root, pdk_family, version): @click.command("enable") @opt_data_source @opt_github_token -@opt_pdk_root +@opt_pdk @click.option( "-l", "--include-libraries", @@ -258,7 +258,7 @@ def path_cmd(pdk_root, pdk_family, version): def enable_cmd( data_source, pdk_root, - pdk_family, + pdk_tuple, version, include_libraries, ): @@ -274,7 +274,7 @@ def enable_cmd( try: enable( pdk_root, - pdk_family, + pdk_tuple, version, include_libraries=include_libraries, output=console, @@ -288,7 +288,7 @@ def enable_cmd( @click.command("fetch") @opt_data_source @opt_github_token -@opt_pdk_root +@opt_pdk @click.option( "-l", "--include-libraries", @@ -300,7 +300,7 @@ def enable_cmd( def fetch_cmd( data_source, pdk_root, - pdk_family, + pdk_tuple, version, include_libraries, ): @@ -316,10 +316,10 @@ def fetch_cmd( try: version = fetch( + pdk_root, + pdk_tuple, + version, data_source=data_source, - pdk_root=pdk_root, - pdk=pdk_family, - version=version, include_libraries=include_libraries, output=console, ) diff --git a/ciel/build/__init__.py b/ciel/build/__init__.py index 18918c2..2f55703 100644 --- a/ciel/build/__init__.py +++ b/ciel/build/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2025 The American University in Cairo +# Copyright 2025 Ciel Contributors # # Adapted from the Volare project # @@ -22,7 +22,7 @@ import tempfile import importlib import subprocess -from typing import Optional, List, Dict +from typing import Optional, List, Dict, Tuple import click import zstandard as zstd @@ -42,7 +42,7 @@ from ..click_common import ( opt_push, opt_build, - opt_pdk_root, + opt_pdk, arg_version, ) from ..families import Family @@ -50,7 +50,7 @@ def build( pdk_root: str, - pdk_family: str, + pdk_tuple: Tuple[str, str], version: str, jobs: int = 1, sram: bool = True, # Deprecated @@ -58,6 +58,8 @@ def build( include_libraries: Optional[List[str]] = None, use_repo_at: Optional[List[str]] = None, ): + pdk_family, pdk_variant = pdk_tuple + use_repos = {} if use_repo_at is not None: for repo in use_repo_at: @@ -69,6 +71,7 @@ def build( kwargs = { "pdk_root": pdk_root, + "pdk_variant": pdk_variant, "version": version, "jobs": jobs, "clear_build_artifacts": clear_build_artifacts, @@ -83,14 +86,14 @@ def build( @click.command("build") @opt_github_token -@opt_pdk_root +@opt_pdk @opt_build @arg_version def build_cmd( include_libraries, jobs, pdk_root, - pdk_family, + pdk_tuple, clear_build_artifacts, version, use_repo_at, @@ -109,7 +112,7 @@ def build_cmd( build( pdk_root=pdk_root, - pdk_family=pdk_family, + pdk_tuple=pdk_tuple, version=version, jobs=jobs, clear_build_artifacts=clear_build_artifacts, @@ -120,7 +123,7 @@ def build_cmd( def push( pdk_root, - pdk_family, + pdk_tuple, version, *, owner, @@ -128,7 +131,11 @@ def push( pre=False, push_libraries=None, ): - family = Family.by_name[pdk_family] + # variant doesn't matter, we're pushing whatever we can unless an explicit + # list is provided + pdk_family_name, _ = pdk_tuple + + pdk_family = Family.by_name[pdk_family_name] session = GitHubSession() if session.github_token is None: @@ -137,10 +144,10 @@ def push( console = Console() if push_libraries is None or len(push_libraries) == 0: - push_libraries = family.all_libraries + push_libraries = pdk_family.all_libraries library_list = set(push_libraries) - version_object = Version(version, pdk_family) + version_object = Version(version, pdk_family_name) version_directory = version_object.get_dir(pdk_root) if not os.path.isdir(version_directory): raise FileNotFoundError(f"Version {version} not found.") @@ -181,15 +188,15 @@ def push( progress.remove_task(task) final_tarballs.append(tarball_path) - tag = f"{pdk_family}-{version}" + tag = f"{pdk_family_name}-{version}" # If someone wants to rewrite this to not use ghr, please, by all means. console.log("Starting upload…") - body = f"{pdk_family} variants built using ciel" - date = get_commit_date(version, family.repo, session) + body = f"{pdk_family_name} variants built using ciel" + date = get_commit_date(version, pdk_family.repo, session) if date is not None: - body = f"{pdk_family} variants (released on {date_to_iso8601(date)})" + body = f"{pdk_family_name} variants (released on {date_to_iso8601(date)})" for tarball_path in final_tarballs: subprocess.check_call( @@ -218,7 +225,7 @@ def push( @click.command("push", hidden=True) @opt_github_token -@opt_pdk_root +@opt_pdk @opt_push @click.argument("version") def push_cmd( @@ -226,7 +233,7 @@ def push_cmd( repository, pre, pdk_root, - pdk_family, + pdk_tuple, version, push_libraries, ): @@ -241,7 +248,7 @@ def push_cmd( try: push( pdk_root, - pdk_family, + pdk_tuple, version, owner=owner, repository=repository, diff --git a/ciel/build/gf180mcu.py b/ciel/build/gf180mcu.py index 62022d7..7886312 100644 --- a/ciel/build/gf180mcu.py +++ b/ciel/build/gf180mcu.py @@ -1,3 +1,7 @@ +# Copyright 2026 Ciel Contributors +# +# Adapted from Volare +# # Copyright 2022-2023 Efabless Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -225,6 +229,7 @@ def install_gf180mcu(build_directory, pdk_root, version): def build( pdk_root: str, + pdk_variant: str, version: str, jobs: int = 1, clear_build_artifacts: bool = True, @@ -232,7 +237,7 @@ def build( using_repos: Optional[Dict[str, str]] = None, ): family = Family.by_name["gf180mcu"] - library_set = family.resolve_libraries(include_libraries) + library_set = family.resolve_libraries(include_libraries, pdk_variant) if using_repos is None: using_repos = {} diff --git a/ciel/build/ihp-sg13g2.py b/ciel/build/ihp-sg13.py similarity index 77% rename from ciel/build/ihp-sg13g2.py rename to ciel/build/ihp-sg13.py index 12d8e1d..bffacf6 100644 --- a/ciel/build/ihp-sg13g2.py +++ b/ciel/build/ihp-sg13.py @@ -1,3 +1,7 @@ +# Copyright 2025 Ciel Contributors +# +# Adapted from the Volare project +# # Copyright 2022-2023 Efabless Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +18,7 @@ import os import shutil import subprocess +from pathlib import Path from datetime import datetime from typing import Optional, List, Tuple, Dict from concurrent.futures import ThreadPoolExecutor @@ -67,25 +72,37 @@ def get_ihp( def build_ihp(build_directory, ihp_path): # """Build""" + def filter(dir_s, files): + dir = Path(dir_s) + if dir.name == ".git": + return files + rejects = [".git", ".DS_Store"] + for file in files: + # ignore bad symlinks + if not (Path(dir) / file).resolve().exists(): + rejects.append(file) + return rejects + + ihp_sg13_family = Family.by_name["ihp-sg13"] try: - shutil.rmtree(os.path.join(build_directory, "ihp-sg13g2")) + for variant in ihp_sg13_family.variants: + shutil.rmtree(os.path.join(build_directory, variant)) except FileNotFoundError: pass - shutil.copytree( - os.path.join(ihp_path, "ihp-sg13g2"), - os.path.join(build_directory, "ihp-sg13g2"), - ignore=lambda dir, files: ( - files if ".git" in os.path.split(dir) else [".git", ".DS_Store"] - ), - ) + for variant in ihp_sg13_family.variants: + shutil.copytree( + os.path.join(ihp_path, variant), + os.path.join(build_directory, variant), + ignore=filter, + ) def install_ihp(build_directory, pdk_root, version): console = Console() with console.status("Adding build to list of installed versions…"): - ihp_sg13g2_family = Family.by_name["ihp-sg13g2"] + ihp_sg13_family = Family.by_name["ihp-sg13"] - version_directory = Version(version, "ihp-sg13g2").get_dir(pdk_root) + version_directory = Version(version, "ihp-sg13").get_dir(pdk_root) if ( os.path.exists(version_directory) and len(os.listdir(version_directory)) != 0 @@ -94,9 +111,7 @@ def install_ihp(build_directory, pdk_root, version): it = 0 while os.path.exists(backup_path) and len(os.listdir(backup_path)) != 0: it += 1 - backup_path = Version(f"{version}.bk{it}", "ihp-sg13g2").get_dir( - pdk_root - ) + backup_path = Version(f"{version}.bk{it}", "ihp-sg13").get_dir(pdk_root) console.log( f"Build already found at {version_directory}, moving to {backup_path}…" ) @@ -105,7 +120,7 @@ def install_ihp(build_directory, pdk_root, version): console.log("Copying…") mkdirp(version_directory) - for variant in ihp_sg13g2_family.variants: + for variant in ihp_sg13_family.variants: variant_build_path = os.path.join(build_directory, variant) variant_install_path = os.path.join(version_directory, variant) if os.path.isdir(variant_build_path): @@ -116,6 +131,7 @@ def install_ihp(build_directory, pdk_root, version): def build( pdk_root: str, + pdk_variant: str, version: str, jobs: int = 1, clear_build_artifacts: bool = True, @@ -131,10 +147,8 @@ def build( if using_repos is None: using_repos = {} - build_directory = os.path.join( - get_ciel_dir(pdk_root, "ihp-sg13g2"), "build", version - ) - timestamp = datetime.now().strftime("build_ihp-sg13g2-%Y-%m-%d-%H-%M-%S") + build_directory = os.path.join(get_ciel_dir(pdk_root, "ihp-sg13"), "build", version) + timestamp = datetime.now().strftime("build_ihp-sg13-%Y-%m-%d-%H-%M-%S") log_dir = os.path.join(build_directory, "logs", timestamp) mkdirp(log_dir) diff --git a/ciel/build/sky130.py b/ciel/build/sky130.py index 6f25432..83bdd1d 100644 --- a/ciel/build/sky130.py +++ b/ciel/build/sky130.py @@ -1,3 +1,7 @@ +# Copyright 2026 Ciel Contributors +# +# Adapted from Volare +# # Copyright 2022-2023 Efabless Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -315,6 +319,7 @@ def install_sky130(build_directory, pdk_root, version): def build( pdk_root: str, + pdk_variant: str, version: str, jobs: int = 1, clear_build_artifacts: bool = True, @@ -322,7 +327,7 @@ def build( using_repos: Optional[Dict[str, str]] = None, ): family = Family.by_name["sky130"] - library_set = family.resolve_libraries(include_libraries) + library_set = family.resolve_libraries(include_libraries, pdk_variant) if using_repos is None: using_repos = {} diff --git a/ciel/click_common.py b/ciel/click_common.py index ea5a20b..13de6e7 100644 --- a/ciel/click_common.py +++ b/ciel/click_common.py @@ -1,4 +1,4 @@ -# Copyright 2025 The American University in Cairo +# Copyright 2025 Ciel Contributors # # Adapted from the Volare project # @@ -22,10 +22,9 @@ from .common import ( CIEL_RESOLVED_HOME, - resolve_pdk_family, resolve_version, ) -from .families import Family +from .families import Family, resolve_pdk_family, resolve_pdk_variant opt = partial(click.option, show_default=True) @@ -93,21 +92,23 @@ def process_value(self, ctx: click.Context, value): value = self.callback(ctx, self, value) try: - resolved = resolve_pdk_family(value) + family = resolve_pdk_family(value) + variant = resolve_pdk_variant(value) except ValueError as e: raise click.BadParameter(str(e), ctx=ctx, param=self) - return resolved + return (family, variant) -def opt_pdk_root(function: Callable): +def opt_pdk(function: Callable): function = opt( "--pdk-family", "--pdk", + "pdk_tuple", cls=PDKOption, required=True, envvar=["PDK_FAMILY", "PDK"], - help="A valid PDK family or variant (the latter of which is resolved to a family). If the environment PDK_FAMILY or PDK are set, they are used as secondary sources for this value.", + help="A valid PDK family or variant. If the environment PDK_FAMILY or PDK are set, they are used as secondary sources for this value.", )(function) function = opt( "--pdk-root", diff --git a/ciel/common.py b/ciel/common.py index 8cd6703..e0f7698 100644 --- a/ciel/common.py +++ b/ciel/common.py @@ -1,4 +1,4 @@ -# Copyright 2025 The American University in Cairo +# Copyright 2025 Ciel Contributors # # Modified from the Volare project # @@ -17,8 +17,7 @@ # limitations under the License. import os import shutil -import pathlib -import warnings +from pathlib import Path from datetime import datetime from dataclasses import dataclass from typing import Optional, List @@ -38,7 +37,7 @@ def date_from_iso8601(string: str) -> datetime: def mkdirp(path): - return pathlib.Path(path).mkdir(parents=True, exist_ok=True) + return Path(path).mkdir(parents=True, exist_ok=True) # -- API Variables @@ -61,83 +60,16 @@ def _get_current_version(pdk_root: str, pdk: str) -> Optional[str]: return version -def get_ciel_home(pdk_root: Optional[str] = None) -> str: - return pdk_root or CIEL_RESOLVED_HOME +def get_ciel_home(pdk_root: Optional[str] = None) -> Path: + return Path(pdk_root or CIEL_RESOLVED_HOME) -def get_ciel_dir(pdk_root: str, pdk: str) -> str: - return os.path.join(pdk_root, "ciel", pdk) +def get_ciel_dir(pdk_root: str, pdk: str) -> Path: + return Path(pdk_root) / "ciel" / pdk -def get_versions_dir(pdk_root: str, pdk: str) -> str: - return os.path.join(get_ciel_dir(pdk_root, pdk), "versions") - - -def resolve_pdk_family(selector: Optional[str]): - """ - :returns: - If selector is a valid PDK family, the same string. - - If selector is a valid PDK variant, the family the variant belongs to. - - If selector is None, the PDK_FAMILY and PDK environment variables are - used as fallbacks. If all are None, the function will simply return None. - - Starting Ciel 3.0.0, supplying None will no longer work and the selector - will be a string. - - If the selector is invalid, a ValueError will be raised. "ihp_sg13g2" - will resolve to "ihp-sg13g2" however for some semblance of backwards - compatibility with previous versions of Ciel/Volare. - """ - if selector is None: - warnings.warn( - "Passing None to resolve_pdk_family is deprecated and will be removed in Ciel 3.0.0. Please resolve any environment variables manually.", - DeprecationWarning, - stacklevel=2, - ) - if environment_specified_pdk := os.getenv("PDK_FAMILY") or os.getenv("PDK"): - selector = environment_specified_pdk - if selector is None: - return None - - if selector == "ihp_sg13g2": - selector = "ihp-sg13g2" - - if selector in Family.by_name: - return selector - - for pdk_family in Family.by_name.values(): - if selector in pdk_family.variants: - return pdk_family.name - - raise ValueError(f"'{selector}' is not a valid PDK family or variant.") - - -def resolve_pdk_variant(selector: Optional[str]): - """ - :returns: - If selector is a valid PDK variant, the same string. - - If selector is a valid PDK family, the default variant of said PDK. - - If selector is None, the PDK environment variables is used as a - fallback. If all are None, the function will simply return None. - - If the selector is invalid, a ValueError will be raised. - """ - selector = selector or os.getenv("PDK") - if selector is None: - return None - - if family := Family.by_name.get(selector): - return family.default_variant - - for pdk_family in Family.by_name.values(): - if selector in pdk_family.variants: - return selector - - raise ValueError(f"'{selector}' is not a valid PDK family or variant.") +def get_versions_dir(pdk_root: str, pdk: str) -> Path: + return get_ciel_dir(pdk_root, pdk) / "versions" @dataclass @@ -147,6 +79,7 @@ class Version(object): commit_date: Optional[datetime] = None upload_date: Optional[datetime] = None prerelease: bool = False + data_source_pdk_override: Optional[str] = None def __lt__(self, rhs: "Version"): return (self.commit_date or datetime.min) < (rhs.commit_date or datetime.min) @@ -161,8 +94,8 @@ def is_installed(self, pdk_root: str) -> bool: def is_current(self, pdk_root: str) -> bool: return self.name == _get_current_version(pdk_root, self.pdk) - def get_dir(self, pdk_root: str) -> str: - return os.path.join(get_versions_dir(pdk_root, self.pdk), self.name) + def get_dir(self, pdk_root: str) -> Path: + return get_versions_dir(pdk_root, self.pdk) / self.name def unset_current(self, pdk_root: str): if not self.is_installed(pdk_root): @@ -202,14 +135,14 @@ def get_current(Self, pdk_root: str, pdk: str) -> Optional["Version"]: @classmethod def get_all_installed(Self, pdk_root: str, pdk: str) -> List["Version"]: versions_dir = get_versions_dir(pdk_root, pdk) - mkdirp(versions_dir) + versions_dir.mkdir(parents=True, exist_ok=True) return [ Version( name=version, pdk=pdk, ) for version in os.listdir(versions_dir) - if os.path.isdir(os.path.join(versions_dir, version)) + if (versions_dir / version).is_dir() ] diff --git a/ciel/families.py b/ciel/families.py index e9b825e..15a0a48 100644 --- a/ciel/families.py +++ b/ciel/families.py @@ -1,3 +1,7 @@ +# Copyright 2026 Ciel Contributors +# +# Adapted from Volare +# # Copyright 2022-2023 Efabless Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,6 +15,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import fnmatch from dataclasses import dataclass from typing import Iterable, List, Dict, Optional, Set, ClassVar @@ -20,6 +25,7 @@ @dataclass class Family(object): by_name: ClassVar[Dict[str, "Family"]] = {} + by_variant: ClassVar[Dict[str, "Family"]] = {} name: str variants: List[str] @@ -27,17 +33,22 @@ class Family(object): repo: RepoInfo # lol no implicitly unwrapped optionals default_variant: str = None # type: ignore - default_includes: List[str] = None # type: ignore + default_includes: Dict[str, List[str]] = None # type: ignore def __post_init__(self): if self.default_variant is None: self.default_variant = self.variants[0] if self.default_includes is None: - self.default_includes = self.all_libraries.copy() + self.default_includes = {"*": self.all_libraries.copy()} + + Family.by_name[self.name] = self + for variant in self.variants: + Family.by_variant[variant] = self def resolve_libraries( self, input: Optional[Iterable[str]], + variant: str, ) -> Set[str]: if input is None: input = ("default",) @@ -47,7 +58,9 @@ def resolve_libraries( final_set = set(self.all_libraries) return final_set elif element.lower() == "default": - final_set = final_set.union(set(self.default_includes)) + for pattern, includes in self.default_includes.items(): + if fnmatch.fnmatch(variant, pattern): + final_set = final_set.union(includes) elif element in self.all_libraries: final_set.add(element) else: @@ -55,8 +68,7 @@ def resolve_libraries( return final_set -Family.by_name = {} -Family.by_name["sky130"] = Family( +Family( name="sky130", variants=["sky130A", "sky130B"], default_variant="sky130A", @@ -74,17 +86,21 @@ def resolve_libraries( "sky130_sram_macros", "sky130_fd_pr_reram", ], - default_includes=[ - "sky130_fd_io", - "sky130_fd_pr", - "sky130_fd_sc_hd", - "sky130_fd_sc_hvl", - "sky130_ml_xx_hd", - "sky130_sram_macros", - ], + default_includes={ + "*": [ + "sky130_fd_io", + "sky130_fd_pr", + "sky130_fd_sc_hd", + "sky130_fd_sc_hvl", + "sky130_ml_xx_hd", + "sky130_sram_macros", + ], + "sky130B": ["sky130_fd_pr_reram"], + }, repo=opdks_repo, ) -Family.by_name["gf180mcu"] = Family( + +Family( name="gf180mcu", variants=["gf180mcuA", "gf180mcuB", "gf180mcuC", "gf180mcuD"], default_variant="gf180mcuD", @@ -104,23 +120,89 @@ def resolve_libraries( "gf180mcu_ocd_alpha_large", "gf180mcu_ocd_alpha_misc", ], - default_includes=[ - "gf180mcu_fd_io", - "gf180mcu_fd_pr", - "gf180mcu_fd_sc_mcu7t5v0", - "gf180mcu_fd_sc_mcu9t5v0", - "gf180mcu_fd_ip_sram", - ], + default_includes={ + "*": [ + "gf180mcu_fd_io", + "gf180mcu_fd_pr", + "gf180mcu_fd_sc_mcu7t5v0", + "gf180mcu_fd_sc_mcu9t5v0", + "gf180mcu_fd_ip_sram", + ] + }, repo=opdks_repo, ) -Family.by_name["ihp-sg13g2"] = Family( - name="ihp-sg13g2", - variants=["ihp-sg13g2"], + +Family( + name="ihp-sg13", + variants=["ihp-sg13g2", "ihp-sg13cmos5l"], all_libraries=[ "sg13g2_io", "sg13g2_pr", "sg13g2_sram", "sg13g2_stdcell", + "sg13cmos5l_io", + "sg13cmos5l_sram", + "sg13cmos5l_stdcell", ], + default_includes={ + "ihp-sg13g2": [ + "sg13g2_io", + "sg13g2_pr", + "sg13g2_sram", + "sg13g2_stdcell", + ], + "ihp-sg13cmos5l": [ + "sg13cmos5l_io", + "sg13cmos5l_sram", + "sg13cmos5l_stdcell", + ], + }, repo=ihp_repo, ) + + +def resolve_pdk_family(selector: str): + """ + :returns: + If selector is a valid PDK family, the same string. + + If selector is a valid PDK variant, the family the variant belongs to. + + If the selector is invalid, a ValueError will be raised. "ihp_sg13g2" + will resolve to "ihp-sg13g2" however for some semblance of backwards + compatibility with previous versions of Ciel/Volare. + """ + if selector == "ihp_sg13g2": + selector = "ihp-sg13" + + if selector in Family.by_name: + return selector + + for pdk_family in Family.by_name.values(): + if selector in pdk_family.variants: + return pdk_family.name + + raise ValueError(f"'{selector}' is not a valid PDK family or variant.") + + +def resolve_pdk_variant(selector: Optional[str]): + """ + :returns: + If selector is a valid PDK variant, the same string. + + If selector is a valid PDK family, the default variant of said PDK. + + If selector is None, the function will simply return None. + + If the selector is invalid, a ValueError will be raised. + """ + if selector is None: + return None + + if selector in Family.by_variant: + return str(selector) + + if family := Family.by_name.get(selector): + return family.default_variant + + raise ValueError(f"'{selector}' is not a valid PDK family or variant.") diff --git a/ciel/github.py b/ciel/github.py index 0ce8ada..63c8e4a 100644 --- a/ciel/github.py +++ b/ciel/github.py @@ -1,4 +1,4 @@ -# Copyright 2025 The American University in Cairo +# Copyright 2025 Ciel Contributors # # Modified from the Volare project # diff --git a/ciel/manage.py b/ciel/manage.py index aa3f124..71df50d 100644 --- a/ciel/manage.py +++ b/ciel/manage.py @@ -1,4 +1,4 @@ -# Copyright 2025 The American University in Cairo +# Copyright 2025 Ciel Contributors # # Modified from the Volare project # @@ -21,8 +21,8 @@ import hashlib import tarfile import tempfile -import warnings -from typing import Dict, Iterable, List, Optional, Union +from pathlib import Path +from typing import Dict, Iterable, List, Optional, Union, Tuple import rich import httpx @@ -37,7 +37,7 @@ get_versions_dir, get_ciel_dir, ) -from .build import build, push +from .build import build from .families import Family from .source import DataSource @@ -119,30 +119,30 @@ def print_remote_list( def fetch( pdk_root: str, - pdk: str, + pdk_tuple: Tuple[str, str], version: str, *, data_source: DataSource, build_if_not_found=False, - also_push=False, build_kwargs: dict = {}, - push_kwargs: dict = {}, include_libraries: Optional[Iterable[str]] = None, output: Union[Console, io.TextIOWrapper] = Console(), ) -> Version: + pdk_family_name, pdk_variant_name = pdk_tuple + console = output if not isinstance(console, Console): console = Console(file=console) - version_object = Version(version, pdk) + version_object = Version(version, pdk_family_name) version_directory = version_object.get_dir(pdk_root) - pdk_family = Family.by_name.get(pdk) + pdk_family = Family.by_name.get(pdk_family_name) if pdk_family is None: - raise ValueError(f"Unsupported PDK family '{pdk}'.") + raise ValueError(f"Unsupported PDK family '{pdk_family_name}'.") - library_set = pdk_family.resolve_libraries(include_libraries) + library_set = pdk_family.resolve_libraries(include_libraries, pdk_variant_name) variants = pdk_family.variants @@ -163,7 +163,7 @@ def fetch( if not found: missing_libraries.add(library) - affected_paths = [] + affected_paths: List[Path] = [] if len(missing_libraries) != 0 or common_missing: if common_missing: console.print( @@ -174,10 +174,10 @@ def fetch( console.print(f"Libraries {missing_libraries} not found, downloading them…") for variant in variants: affected_paths.append( - os.path.join(version_directory, variant, "libs.ref", library) + version_directory / variant / "libs.ref" / library ) - tarball_paths = [] + tarball_paths: List[Path] = [] try: client, assets = data_source.get_downloads_for_version(version_object) assets_filtered = [] @@ -186,9 +186,10 @@ def fetch( assets_filtered.append(asset) elif asset.content in missing_libraries: assets_filtered.append(asset) - tarball_directory = tempfile.TemporaryDirectory(suffix=".ciel") + tarball_directory_obj = tempfile.TemporaryDirectory(suffix=".ciel") + tarball_directory = Path(tarball_directory_obj.name) for asset in assets_filtered: - tarball_path = os.path.join(tarball_directory.name, asset.filename) + tarball_path = tarball_directory / asset.filename tarball_paths.append(tarball_path) with client.stream("get", asset.url) as r, rich.progress.Progress( console=console @@ -213,9 +214,8 @@ def fetch( for file in tf: if file.isdir(): continue - final_path = os.path.join(version_directory, file.name) - final_dir = os.path.dirname(final_path) - mkdirp(final_dir) + final_path = version_directory / file.name + final_path.parent.mkdir(parents=True, exist_ok=True) io = tf.extractfile(file) if io is None: raise IOError( @@ -232,21 +232,10 @@ def fetch( ) build( pdk_root=pdk_root, - pdk_family=pdk, + pdk_tuple=pdk_tuple, version=version, **build_kwargs, ) - if also_push: - if push_kwargs["push_libraries"] is None: - push_kwargs["push_libraries"] = Family.by_name[ - pdk - ].default_includes.copy() - push( - pdk_root=pdk_root, - pdk_family=pdk, - version=version, - **push_kwargs, - ) else: if e.response is not None: raise RuntimeError( @@ -279,33 +268,32 @@ def fetch( with open(variant_sources_file, "w") as f: print(f"{pdk_family.repo.name} {version}", file=f) - return Version(version, pdk) + return Version(version, pdk_family_name) def enable( pdk_root: str, - pdk: str, + pdk_tuple: Tuple[str, str], version: str, *, data_source: DataSource, build_if_not_found: bool = False, - also_push: bool = False, build_kwargs: dict = {}, - push_kwargs: dict = {}, include_libraries: Optional[List[str]] = None, output: Union[Console, io.TextIOWrapper] = Console(), ) -> Version: + pdk_family_name, _ = pdk_tuple console = output if not isinstance(console, Console): console = Console(file=console) - version_object = Version(version, pdk) + version_object = Version(version, pdk_family_name) version_directory = version_object.get_dir(pdk_root) - pdk_family = Family.by_name.get(pdk) + pdk_family = Family.by_name.get(pdk_family_name) if pdk_family is None: - raise ValueError(f"Unsupported PDK family '{pdk}'.") + raise ValueError(f"Unsupported PDK family '{pdk_family_name}'.") variants = pdk_family.variants version_paths = [os.path.join(version_directory, variant) for variant in variants] @@ -313,18 +301,16 @@ def enable( fetch( pdk_root, - pdk, + pdk_tuple, version, data_source=data_source, build_if_not_found=build_if_not_found, - also_push=also_push, build_kwargs=build_kwargs, - push_kwargs=push_kwargs, include_libraries=include_libraries, output=output, ) - current_file = os.path.join(get_ciel_dir(pdk_root, pdk), "current") + current_file = os.path.join(get_ciel_dir(pdk_root, pdk_family_name), "current") current_file_dir = os.path.dirname(current_file) mkdirp(current_file_dir) @@ -346,15 +332,10 @@ def enable( with open(current_file, "w") as f: f.write(version) - console.print(f"Version {version} enabled for the {pdk} PDK.") + console.print(f"Version {version} enabled for the {pdk_family_name} PDK.") return version_object -def get(*args, **kwargs): - warnings.warn("get() has been deprecated: use fetch()") - return fetch(*args, **kwargs) - - def optimize(pdk_root, version_object: Version): if os.name != "posix": return 0 diff --git a/ciel/source.py b/ciel/source.py index f208aab..c22e0e9 100644 --- a/ciel/source.py +++ b/ciel/source.py @@ -1,4 +1,4 @@ -# Copyright 2025 The American University in Cairo +# Copyright 2025 Ciel Contributors # # Modified from the Volare project # @@ -25,6 +25,7 @@ from .github import GitHubSession, RepoInfo from .common import Version, date_from_iso8601 +from .families import Family @dataclass @@ -56,6 +57,7 @@ def __init__(self, repo_id: str): self.repo = RepoInfo.from_id(repo_id) def get_available_versions(self, pdk: str) -> List[Version]: + pdk_family = Family.by_name[pdk] page = 1 last = self.session.api( self.repo, @@ -80,9 +82,12 @@ def get_available_versions(self, pdk: str) -> List[Version]: if release["draft"]: continue - family, hash = release["tag_name"].rsplit("-", maxsplit=1) + release_family_name, hash = release["tag_name"].rsplit("-", maxsplit=1) - if pdk != family: + if ( + release_family_name != pdk_family.name + and release_family_name not in pdk_family.variants + ): continue upload_date = date_from_iso8601(release["published_at"]) @@ -94,10 +99,11 @@ def get_available_versions(self, pdk: str) -> List[Version]: remote_version = Version( name=hash, - pdk=family, + pdk=pdk_family.name, commit_date=commit_date, upload_date=upload_date, prerelease=release["prerelease"], + data_source_pdk_override=release_family_name, ) versions.append(remote_version) @@ -111,9 +117,11 @@ def get_available_versions(self, pdk: str) -> List[Version]: def get_downloads_for_version( self, version: Version ) -> Tuple[httpx.Client, List[Asset]]: + release_family_name = version.data_source_pdk_override or version.pdk + release = self.session.api( self.repo, - f"/releases/tags/{version.pdk}-{version.name}", + f"/releases/tags/{release_family_name}-{version.name}", "get", ) diff --git a/pyproject.toml b/pyproject.toml index 8724c2b..b7207a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ciel" -version = "2.6.1" +version = "3.0.0" description = "An PDK builder/version manager for PDKs in the open_pdks format" authors = ["Mohamed Gaber ", "Efabless Corporation"] readme = "Readme.md"