Skip to content
Open
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
27 changes: 17 additions & 10 deletions .github/workflows/codeql-compilation-caches.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ jobs:
matrix: ${{ steps.plan.outputs.matrix || '[{"language":"skip","target":"skip"}]' }}
release: ${{ steps.version.outputs.release }}
skip: ${{ steps.existing.outputs.skip }}
verify_os: ${{ steps.plan.outputs.verify_os || '["ubuntu-latest"]' }}
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -115,7 +116,7 @@ jobs:
uses: actions/cache@v4
with:
path: ${{ env.CODEQL_BUNDLE_CACHE_DIR }}/sources/${{ steps.version.outputs.release }}
key: codeql-source-${{ steps.version.outputs.release }}-${{ runner.os }}
key: codeql-source-${{ steps.version.outputs.release }}-${{ runner.os }}-${{ runner.arch }}

- if: steps.existing.outputs.skip != 'true'
id: plan
Expand All @@ -130,6 +131,12 @@ jobs:
--output release-plan.json
echo "cache_release=$(jq -r '.cache_release' release-plan.json)" >> "$GITHUB_OUTPUT"
echo "matrix=$(jq -c '.targets' release-plan.json)" >> "$GITHUB_OUTPUT"
echo "verify_os=$(jq -c '[.source_assets[].platform | {
"linux64": "ubuntu-latest",
"linux-arm64": "ubuntu-24.04-arm",
"osx64": "macos-latest",
"win64": "windows-latest"
}[.]]' release-plan.json)" >> "$GITHUB_OUTPUT"

- if: steps.existing.outputs.skip != 'true'
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -162,7 +169,7 @@ jobs:
- uses: actions/cache@v4
with:
path: ${{ env.CODEQL_BUNDLE_CACHE_DIR }}/sources/${{ needs.plan.outputs.release }}
key: codeql-source-${{ needs.plan.outputs.release }}-${{ runner.os }}
key: codeql-source-${{ needs.plan.outputs.release }}-${{ runner.os }}-${{ runner.arch }}

- name: Build and verify cache
run: |
Expand All @@ -189,10 +196,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
os: ${{ fromJSON(needs.plan.outputs.verify_os) }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand All @@ -216,7 +220,7 @@ jobs:
- uses: actions/cache@v4
with:
path: ${{ env.CODEQL_BUNDLE_CACHE_DIR }}/sources/${{ needs.plan.outputs.release }}
key: codeql-source-${{ needs.plan.outputs.release }}-${{ runner.os }}
key: codeql-source-${{ needs.plan.outputs.release }}-${{ runner.os }}-${{ runner.arch }}

- name: Verify all caches
run: codeql-bundle-cache verify-all --plan release-plan.json --assets-dir dist
Expand Down Expand Up @@ -299,13 +303,16 @@ jobs:

- name: Create candidate catalog entry
if: steps.recheck.outputs.skip != 'true'
shell: bash
run: |
validated_platform_args=()
while IFS= read -r platform; do
validated_platform_args+=(--validated-platform "$platform")
done < <(jq -r '.source_assets[].platform' release-plan.json)
codeql-bundle-cache catalog-entry \
--plan release-plan.json \
--assets-dir dist \
--validated-platform linux64 \
--validated-platform osx64 \
--validated-platform win64 \
"${validated_platform_args[@]}" \
--output catalog-entry.json
codeql-bundle-cache verify-entry \
--entry catalog-entry.json \
Expand Down
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ For more details on CodeQL customization packs see the section [CodeQL customiza
The CodeQL bundle application can be installed using `pip` with the command:

```bash
python3.11 -m pip install https://github.com/advanced-security/codeql-bundle/releases/download/v0.5.0/codeql_bundle-0.5.0-py3-none-any.whl
python3.11 -m pip install https://github.com/advanced-security/codeql-bundle/releases/download/v0.6.0/codeql_bundle-0.6.0-py3-none-any.whl
```

## Usage

The source bundle can be an existing local archive or directory, a
`github/codeql-action` release tag, or an HTTP(S) URL. Release tags and URLs are
downloaded into a persistent local cache.
`github/codeql-action` release tag, or an HTTP(S) URL. Release tags select the
current platform's bundle, and downloads are stored in a persistent local cache.

The CodeQL bundle application requires a [CodeQL workspace](https://codeql.github.com/docs/codeql-cli/about-codeql-workspaces/) to locate the packs you want to include in a custom bundle.
You can see the packs available in your workspace by running `codeql pack ls -- <dir>` where `<dir>` is the root directory of your CodeQL workspace.
Expand All @@ -37,11 +37,14 @@ with the command:
codeql-bundle --bundle codeql-bundle-v2.26.1 --output codeql-custom-bundle.tar.gz --workspace <path-to-workspace> --log INFO <packs>
```

If the source bundle is the platform agnostic bundle then you can create platform specific bundles to reduce the size of the used bundle(s).
The following example creates platform specific bundles for all the currently supported platforms.
Because the upstream all-platform bundle is
[deprecated](https://github.blog/changelog/2026-09-22-deprecation-notice-all-platform-codeql-bundle/),
release tags only build for the current target (`linux64`, `linux-arm64`,
`osx64`, or `win64`). Run once per target; local all-platform archives still
support multiple targets.

```bash
codeql-bundle --bundle <path-to-platform-agnostic-bundle> --output <path-to-bundles-dir> --workspace <path-to-workspace> --log INFO -p linux64 -p osx64 -p win64 <packs>
codeql-bundle --bundle codeql-bundle-v2.27.0 --output <path-to-bundles-dir> --workspace <path-to-workspace> --log INFO -p linux64 <packs>
```

### Compilation caches
Expand Down
33 changes: 22 additions & 11 deletions codeql_bundle/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
CACHE_FORMAT_VERSION = 1
DOWNLOAD_CHUNK_SIZE = 1024 * 1024
CATALOG_REFRESH_SECONDS = 60 * 60
BUNDLE_PLATFORMS = ("linux64", "linux-arm64", "osx64", "win64")
RELEASE_PATTERN = re.compile(r"^codeql-bundle-v\d+\.\d+\.\d+$")
CACHE_RELEASE_PATTERN = re.compile(
r"^codeql-compilation-cache-v\d+\.\d+\.\d+(?:-[A-Za-z0-9._-]+)?$"
Expand Down Expand Up @@ -172,14 +173,20 @@ def to_dict(self) -> dict[str, Any]:
}

def source_asset_for_platform(self, platform_name: str) -> Optional[SourceAsset]:
return next(
asset = next(
(
asset
for asset in self.source_assets
if asset.platform == platform_name
),
None,
)
if asset is not None:
return asset
return next(
(asset for asset in self.source_assets if asset.platform == "all"),
None,
)


class BundleCatalog:
Expand Down Expand Up @@ -323,7 +330,12 @@ def default_cache_dir() -> Path:
def current_bundle_platform() -> str:
system = platform.system()
if system == "Linux":
return "linux64"
machine = platform.machine().lower()
if machine in {"x86_64", "amd64"}:
return "linux64"
if machine in {"aarch64", "arm64"}:
return "linux-arm64"
raise CacheException(f"Unsupported Linux architecture: {machine}")
if system == "Darwin":
return "osx64"
if system == "Windows":
Expand Down Expand Up @@ -721,8 +733,6 @@ def _resolve_release(
bundle = self.catalog.find_release(release)
if bundle:
asset = bundle.source_asset_for_platform(platform_name)
if asset is None and platform_name != "all":
asset = bundle.source_asset_for_platform("all")
if asset is None:
raise CatalogException(
f"Bundle {release} has no source asset for {platform_name}."
Expand Down Expand Up @@ -1009,19 +1019,20 @@ def write_json(path: Path, value: dict[str, Any]) -> None:
def source_asset_name(platform_name: str) -> str:
if platform_name == "all":
return "codeql-bundle.tar.gz"
if platform_name not in {"linux64", "osx64", "win64"}:
if platform_name not in BUNDLE_PLATFORMS:
raise CacheException(f"Unsupported bundle platform: {platform_name}")
return f"codeql-bundle-{platform_name}.tar.gz"


def source_platform_for_request(requested_platforms: Iterable[str]) -> str:
requested = tuple(requested_platforms)
requested = set(requested_platforms)
current = current_bundle_platform()
if not requested:
return "all"
if requested == (current,):
return current
return "all"
if requested and requested != {current}:
raise CacheException(
f"Release tags can only build for the current platform ({current}); "
f"requested {', '.join(sorted(requested))}."
)
return current


def github_asset_digest(asset: dict[str, Any]) -> Optional[str]:
Expand Down
37 changes: 27 additions & 10 deletions codeql_bundle/cache_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from semantic_version import Version

from codeql_bundle.cache import (
BUNDLE_PLATFORMS,
CACHE_FORMAT_VERSION,
CODEQL_ACTION_REPOSITORY,
BundleCatalog,
Expand Down Expand Up @@ -50,7 +51,7 @@

logger = logging.getLogger(__name__)

SOURCE_PLATFORMS = ("all", "linux64", "osx64", "win64")
REQUIRED_SOURCE_PLATFORMS = frozenset(("linux64", "osx64", "win64"))
MAX_RELEASE_ASSET_SIZE = 2 * 1024 * 1024 * 1024
DEFAULT_COMPILATION_CACHE_SIZE_MB = 1536

Expand Down Expand Up @@ -142,7 +143,7 @@ def prune_cache(cache_dir: Path, max_age_days: float, dry_run: bool) -> None:
"--platform",
"platforms",
multiple=True,
type=click.Choice(["linux64", "osx64", "win64"]),
type=click.Choice(BUNDLE_PLATFORMS),
)
@click.option(
"--cache-dir",
Expand Down Expand Up @@ -212,11 +213,15 @@ def plan_release(
client = GitHubReleaseClient()
release_value = client.release(release)
source_assets = _release_source_assets(release_value)
platform_name = current_bundle_platform()
source_asset = next(
asset
for asset in source_assets
if asset.platform == current_bundle_platform()
(asset for asset in source_assets if asset.platform == platform_name),
None,
)
if source_asset is None:
raise click.ClickException(
f"Release {release} has no source bundle for {platform_name}."
)
source_path = cache_path(cache_dir, "sources", release, source_asset.name)
download_file(
source_asset.url,
Expand Down Expand Up @@ -493,7 +498,7 @@ def _verify_cache_with_bundle(
"validated_platforms",
multiple=True,
required=True,
type=click.Choice(["linux64", "osx64", "win64"]),
type=click.Choice(BUNDLE_PLATFORMS),
Comment thread
mbaluda marked this conversation as resolved.
)
@click.option(
"--output",
Expand All @@ -509,6 +514,15 @@ def catalog_entry(
) -> None:
"""Create a catalog entry for verified release assets."""
plan = _read_plan(plan_path)
if missing := sorted(
set(validated_platforms)
- {asset["platform"] for asset in plan["source_assets"]}
):
raise click.ClickException(
"Release plan has no source bundle for validated platform(s): "
f"{', '.join(missing)}."
)

compilation_caches = {}
for target in plan["targets"]:
asset_name = _cache_asset_name(target["language"])
Expand Down Expand Up @@ -581,10 +595,12 @@ def update_catalog(
def _release_source_assets(release: dict[str, Any]) -> tuple[SourceAsset, ...]:
assets = {asset["name"]: asset for asset in release.get("assets", [])}
result = []
for platform_name in SOURCE_PLATFORMS:
for platform_name in BUNDLE_PLATFORMS:
name = source_asset_name(platform_name)
asset = assets.get(name)
if asset is None:
if platform_name not in REQUIRED_SOURCE_PLATFORMS:
continue
raise click.ClickException(
f"Upstream release {release['tag_name']} has no {name}."
)
Expand Down Expand Up @@ -800,10 +816,11 @@ def _read_plan(path: Path) -> dict[str, Any]:
validate_remote_url(source.url)
source_platforms.add(source.platform)
if (
source_platforms != set(SOURCE_PLATFORMS)
or len(value["source_assets"]) != len(SOURCE_PLATFORMS)
not REQUIRED_SOURCE_PLATFORMS.issubset(source_platforms)
or not source_platforms.issubset(BUNDLE_PLATFORMS)
or len(value["source_assets"]) != len(source_platforms)
):
raise ValueError("incomplete source platform inventory")
raise ValueError("invalid source platform inventory")

target_names = set()
languages = set()
Expand Down
3 changes: 2 additions & 1 deletion codeql_bundle/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from codeql_bundle.helpers.codeql import CodeQLException
from codeql_bundle.helpers.bundle import CustomBundle, BundleException, BundlePlatform
from codeql_bundle.cache import (
BUNDLE_PLATFORMS,
BundleCatalog,
BundleSourceResolver,
CacheException,
Expand Down Expand Up @@ -65,7 +66,7 @@
"-p",
"--platform",
multiple=True,
type=click.Choice(["linux64", "osx64", "win64"], case_sensitive=False),
type=click.Choice(BUNDLE_PLATFORMS, case_sensitive=False),
help="Target platform for the bundle",
)
@click.option(
Expand Down
Loading
Loading