Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/bcr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Validate BCR overlay

on:
pull_request:
paths:
- ".github/workflows/bcr.yml"
- ".github/workflows/publish-bcr.yml"
- "bcr/**"
push:
branches:
- main
paths:
- ".github/workflows/bcr.yml"
- ".github/workflows/publish-bcr.yml"
- "bcr/**"
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
validate:
name: Validate ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- name: macOS ARM64
runner: macos-15
- name: Linux AMD64
runner: ubuntu-24.04-16core
- name: Linux ARM64
runner: ubuntu-24.04-arm

steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- name: Setup Bazel
uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86
with:
bazelisk-cache: true
disk-cache: ${{ github.workflow }}-${{ matrix.runner }}
repository-cache: true

- name: Validate overlay
run: |
set -euo pipefail

version="$(jq -r '.versions[-1]' bcr/modules/codescythe/metadata.json)"
module="bcr/modules/codescythe/$version"
cmp "$module/MODULE.bazel" "$module/overlay/MODULE.bazel"

node -e '
const fs = require("node:fs");
const crypto = require("node:crypto");
const root = process.argv[1];
const source = JSON.parse(fs.readFileSync(`${root}/source.json`));
for (const [name, expected] of Object.entries(source.overlay)) {
const content = fs.readFileSync(`${root}/overlay/${name}`);
const actual = `sha256-${crypto.createHash("sha256").update(content).digest("base64")}`;
if (actual !== expected) throw new Error(`${name}: ${actual} !== ${expected}`);
}
' "$module"

consumer="$RUNNER_TEMP/codescythe-bcr"
mkdir -p "$consumer"
printf 'module(name = "codescythe_bcr_validation")\nbazel_dep(name = "codescythe", version = "%s")\n' \
"$version" > "$consumer/MODULE.bazel"
touch "$consumer/BUILD.bazel"
cd "$consumer"

bazel run \
--lockfile_mode=off \
--registry="file://$GITHUB_WORKSPACE/bcr" \
--registry=https://bcr.bazel.build \
@codescythe//:codescythe -- --version
8 changes: 8 additions & 0 deletions .github/workflows/cli-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,11 @@ jobs:
release/codescythe-linux-arm64 \
release/checksums.txt \
--clobber

publish-to-bcr:
name: Publish BCR overlay
needs: release
uses: ./.github/workflows/publish-bcr.yml
permissions:
contents: read
secrets: inherit
108 changes: 108 additions & 0 deletions .github/workflows/publish-bcr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: Publish BCR overlay

on:
workflow_call:
inputs:
version:
required: false
type: string
workflow_dispatch:
inputs:
version:
description: "Checked-in Codescythe module version"
required: false
type: string

permissions:
contents: read

jobs:
publish:
runs-on: ubuntu-24.04-16core

steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false

- name: Checkout Bazel Central Registry
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
repository: bazelbuild/bazel-central-registry
path: bazel-central-registry
persist-credentials: false

- name: Stage registry entry
id: module
env:
REQUESTED_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail

source=bcr/modules/codescythe
version="${REQUESTED_VERSION:-$(jq -r '.versions[-1]' "$source/metadata.json")}"
target=bazel-central-registry/modules/codescythe
test -d "$source/$version"
mkdir -p "$target"

if [[ -f "$target/metadata.json" ]]; then
jq --slurp \
'.[0] as $current | .[1] | .versions = (($current.versions + .versions) | unique)' \
"$target/metadata.json" "$source/metadata.json" > "$target/metadata.json.tmp"
mv "$target/metadata.json.tmp" "$target/metadata.json"
else
cp "$source/metadata.json" "$target/metadata.json"
fi

cp -R "$source/$version" "$target/$version"
echo "version=$version" >> "$GITHUB_OUTPUT"

- name: Publish registry branch
id: publish
env:
BCR_PUBLISH_TOKEN: ${{ secrets.BCR_PUBLISH_TOKEN }}
VERSION: ${{ steps.module.outputs.version }}
run: |
set -euo pipefail

branch="codescythe-$VERSION"
registry=bazel-central-registry
git -C "$registry" checkout -b "$branch"
git -C "$registry" add modules/codescythe
git -C "$registry" \
-c user.name=pplx-oss \
-c user.email=ossmaintainers@perplexity.ai \
commit -m "codescythe@$VERSION"
git -C "$registry" remote add publish \
"https://x-access-token:$BCR_PUBLISH_TOKEN@github.com/perplexityai/bazel-central-registry.git"
git -C "$registry" push --force publish "$branch"
echo "branch=$branch" >> "$GITHUB_OUTPUT"

- name: Open BCR pull request
env:
GH_TOKEN: ${{ secrets.BCR_PUBLISH_TOKEN }}
VERSION: ${{ steps.module.outputs.version }}
BRANCH: ${{ steps.publish.outputs.branch }}
run: |
set -euo pipefail

pr_url="$(gh api \
--method GET \
repos/bazelbuild/bazel-central-registry/pulls \
-f state=open \
-f "head=perplexityai:$BRANCH" \
--jq '.[0].html_url')"

if [[ -z "$pr_url" ]]; then
pr_url="$(gh api \
--method POST \
repos/bazelbuild/bazel-central-registry/pulls \
-f "title=codescythe@$VERSION" \
-f "head=perplexityai:$BRANCH" \
-f base=main \
-f body='Publish the Codescythe binary toolchain overlay.' \
--jq .html_url)"
fi

echo "$pr_url" >> "$GITHUB_STEP_SUMMARY"
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,28 @@ flowchart LR
n1 -->|"named import #bazel_generated/client:client"| n0
```

## Bazel

Add the published toolchain module to `MODULE.bazel`:

```starlark
bazel_dep(name = "codescythe", version = "0.10.1")
```

Run the binary selected for your execution platform:

```sh
bazel run @codescythe//:codescythe -- --help
```

The toolchain supports macOS ARM64, Linux AMD64, and Linux ARM64. Custom rules
can request `@codescythe//:toolchain_type` and access
`ctx.toolchains["@codescythe//:toolchain_type"].codescythe`.

Codescythe must inspect the complete configured source tree, including files
unreachable from Bazel targets. Do not use a `deps` closure or aspect as its
source inventory.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for the repository layout, architecture,
Expand Down
1 change: 1 addition & 0 deletions bcr/bazel_registry.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
12 changes: 12 additions & 0 deletions bcr/modules/codescythe/0.10.1/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module(
name = "codescythe",
version = "0.10.1",
bazel_compatibility = [">=8.5.0"],
)

bazel_dep(name = "platforms", version = "1.1.0")

codescythe = use_extension("//:extensions.bzl", "codescythe")
use_repo(codescythe, "codescythe_toolchains")

register_toolchains("@codescythe_toolchains//:all")
7 changes: 7 additions & 0 deletions bcr/modules/codescythe/0.10.1/overlay/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
load(":toolchain.bzl", "codescythe_binary")

package(default_visibility = ["//visibility:public"])

toolchain_type(name = "toolchain_type")

codescythe_binary(name = "codescythe")
12 changes: 12 additions & 0 deletions bcr/modules/codescythe/0.10.1/overlay/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module(
name = "codescythe",
version = "0.10.1",
bazel_compatibility = [">=8.5.0"],
)

bazel_dep(name = "platforms", version = "1.1.0")

codescythe = use_extension("//:extensions.bzl", "codescythe")
use_repo(codescythe, "codescythe_toolchains")

register_toolchains("@codescythe_toolchains//:all")
115 changes: 115 additions & 0 deletions bcr/modules/codescythe/0.10.1/overlay/extensions.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Download and register platform-specific Codescythe binaries."""

_PLATFORMS = {
"darwin_arm64": struct(
asset = "codescythe-darwin-arm64",
constraints = ["@platforms//os:macos", "@platforms//cpu:arm64"],
),
"linux_amd64": struct(
asset = "codescythe-linux-amd64",
constraints = ["@platforms//os:linux", "@platforms//cpu:x86_64"],
),
"linux_arm64": struct(
asset = "codescythe-linux-arm64",
constraints = ["@platforms//os:linux", "@platforms//cpu:arm64"],
),
}

def _codescythe_repository_impl(repository_ctx):
release_url = "https://github.com/perplexityai/codescythe/releases/download/codescythe_cli_v{}".format(repository_ctx.attr.version)
repository_ctx.download(
url = release_url + "/checksums.txt",
output = "checksums.txt",
)

checksum = None
for line in repository_ctx.read("checksums.txt").splitlines():
fields = [field for field in line.split(" ") if field]
if len(fields) == 2 and fields[1] == repository_ctx.attr.asset:
checksum = fields[0]
break

if checksum == None:
fail("No checksum found for {} in release {}".format(
repository_ctx.attr.asset,
repository_ctx.attr.version,
))

repository_ctx.download(
url = release_url + "/" + repository_ctx.attr.asset,
output = "codescythe",
sha256 = checksum,
executable = True,
)
repository_ctx.file(
"BUILD.bazel",
'exports_files(["codescythe"], visibility = ["//visibility:public"])\n',
)

_codescythe_repository = repository_rule(
implementation = _codescythe_repository_impl,
attrs = {
"asset": attr.string(mandatory = True),
"version": attr.string(mandatory = True),
},
)

def _codescythe_toolchains_repository_impl(repository_ctx):
lines = [
'load("@codescythe//:toolchain.bzl", "codescythe_toolchain")',
"",
'package(default_visibility = ["//visibility:public"])',
"",
]

for name, platform in _PLATFORMS.items():
lines.extend([
"codescythe_toolchain(",
' name = "{}_toolchain_impl",'.format(name),
' codescythe = "@codescythe_{}//:codescythe",'.format(name),
")",
"",
"toolchain(",
' name = "{}_toolchain",'.format(name),
" exec_compatible_with = {},".format(repr(platform.constraints)),
' toolchain = ":{}_toolchain_impl",'.format(name),
' toolchain_type = "@codescythe//:toolchain_type",',
")",
"",
])

repository_ctx.file("BUILD.bazel", "\n".join(lines))

_codescythe_toolchains_repository = repository_rule(
implementation = _codescythe_toolchains_repository_impl,
)

def _codescythe_impl(module_ctx):
version = None
for module in module_ctx.modules:
if module.name == "codescythe":
version = module.version

for module in module_ctx.modules:
for settings in module.tags.toolchain:
if settings.version:
version = settings.version

if not version:
fail("Could not determine the Codescythe release version")

for name, platform in _PLATFORMS.items():
_codescythe_repository(
name = "codescythe_" + name,
asset = platform.asset,
version = version,
)

_codescythe_toolchains_repository(name = "codescythe_toolchains")

codescythe = module_extension(
implementation = _codescythe_impl,
tag_classes = {
"toolchain": tag_class(attrs = {"version": attr.string()}),
},
)
Loading