diff --git a/.agents/claude/CLAUDE.md b/.agents/claude/CLAUDE.md index 509cd0d2..d23e39c4 100644 --- a/.agents/claude/CLAUDE.md +++ b/.agents/claude/CLAUDE.md @@ -14,6 +14,9 @@ bazel run //:format # Install configs bazel run //config:install_all + +# DevContainer lockfile management +# Note: Lockfile auto-generates in VS Code with experimental setting enabled ``` ## Key principles diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 00000000..00b63585 --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,29 @@ +{ + "features": { + "ghcr.io/anthropics/devcontainer-features/claude-code:1": { + "version": "1.0.5", + "resolved": "ghcr.io/anthropics/devcontainer-features/claude-code@sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a", + "integrity": "sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a" + }, + "ghcr.io/devcontainers-community/features/bazel:1": { + "version": "1.2.2", + "resolved": "ghcr.io/devcontainers-community/features/bazel@sha256:587cd6cdd290cfb98c9ba8605a8a27f24fc1e9c736cdd03da099385863386331", + "integrity": "sha256:587cd6cdd290cfb98c9ba8605a8a27f24fc1e9c736cdd03da099385863386331" + }, + "ghcr.io/devcontainers-extra/features/apt-get-packages:1": { + "version": "1.0.8", + "resolved": "ghcr.io/devcontainers-extra/features/apt-get-packages@sha256:776fa11c30c50fa91d5e1bf050444b0f130827457109c349db92e8e126b750ec", + "integrity": "sha256:776fa11c30c50fa91d5e1bf050444b0f130827457109c349db92e8e126b750ec" + }, + "ghcr.io/devcontainers/features/git:1": { + "version": "1.3.4", + "resolved": "ghcr.io/devcontainers/features/git@sha256:f24645e64ad39a596131a50ec96f7d5cf7a2a87544cce772dd4b7182a233e98a", + "integrity": "sha256:f24645e64ad39a596131a50ec96f7d5cf7a2a87544cce772dd4b7182a233e98a" + }, + "ghcr.io/devcontainers/features/github-cli:1": { + "version": "1.0.14", + "resolved": "ghcr.io/devcontainers/features/github-cli@sha256:ca677566507c4118e4368cd76a4800807e911e5e350cc3525fb67ebc9132dfa1", + "integrity": "sha256:ca677566507c4118e4368cd76a4800807e911e5e350cc3525fb67ebc9132dfa1" + } + } +} \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2b841dec..d2cd22e5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,35 +1,38 @@ // https://code.visualstudio.com/docs/devcontainers/create-dev-container { "name": "Bazel development", - "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "image": "mcr.microsoft.com/devcontainers/java:1-21", "features": { - "ghcr.io/devcontainers/features/java:1": { - "version": "21", - "installGradle": false, - "installMaven": false - }, "ghcr.io/devcontainers-community/features/bazel:1": { - "bazelisk_version": "v1.26.0", + "bazelisk_version": "v1.27.0", "buildifier_version": "v8.2.1" }, - "ghcr.io/devcontainers-extra/features/neovim-apt-get:1": {}, - "ghcr.io/devcontainers-extra/features/curl-apt-get:1": {}, - "ghcr.io/devcontainers-extra/features/zsh-plugins:0": { - "plugins": "ssh-agent", - "omzPlugins": "https://github.com/zsh-users/zsh-autosuggestions", - "username": "vscode" - }, - "ghcr.io/anthropics/devcontainer-features/claude-code:1": {} + "ghcr.io/anthropics/devcontainer-features/claude-code:1": {}, + "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/devcontainers/features/git:1": {}, + "ghcr.io/devcontainers-extra/features/apt-get-packages:1": { + "clean_ppas": true, + "packages": [ + "gnupg2" + ] + } }, "customizations": { "vscode": { "extensions": [ - "sourcegraph.cody-ai" - ] + "bazelbuild.vscode-bazel", + "ms-vscode.vscode-json", + "redhat.vscode-yaml", + "ms-vscode.vscode-typescript-next", + "ms-python.python" + ], + "settings": { + "dev.containers.experimentalLockfile": true + } }, "codespaces": { "disableAutomaticConfiguration": true } - } - // "postCreateCommand": "bazel --version" -} + }, + "postCreateCommand": "bazel --version" +} \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 05d86dc3..e261ee0f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,3 +17,19 @@ updates: - "dependencies" - "github-actions" - "automated" + + # Dev Container Features updates + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: "monthly" + open-pull-requests-limit: 5 + reviewers: + - "mikekobit" + commit-message: + prefix: "chore(deps)" + include: "scope" + labels: + - "dependencies" + - "devcontainers" + - "automated" diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml new file mode 100644 index 00000000..ef86e67d --- /dev/null +++ b/.github/workflows/devcontainer.yml @@ -0,0 +1,33 @@ +name: Devcontainer + +on: + pull_request: + paths: + - '.devcontainer/**' + push: + branches: [ main ] + paths: + - '.devcontainer/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + devcontainer: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build and test devcontainer + uses: devcontainers/ci@v0.3 + with: + imageName: dotfiles-devcontainer + runCmd: | + set -e + echo "=== Devcontainer validation ===" + bazel --version + java --version + echo "✅ Devcontainer ready" \ No newline at end of file diff --git a/.github/workflows/test-dev-container.yml b/.github/workflows/test-dev-container.yml deleted file mode 100644 index d0f5882c..00000000 --- a/.github/workflows/test-dev-container.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Test Dev Container - -on: - pull_request: - paths: - - '.devcontainer/**' - - '.bazelversion' - - 'MODULE.bazel' - # Only run on push to main if devcontainer-related files changed - push: - branches: - - main - paths: - - '.devcontainer/**' - - '.bazelversion' - - 'MODULE.bazel' - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: true - -jobs: - test-dev-container: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build and Test Dev Container - uses: devcontainers/ci@v0.3 - with: - imageName: dotfiles-dev-container - cacheFrom: ghcr.io/${{ github.repository }}/devcontainer:cache - # Only test that the container builds and basic tools work - # Skip full bazel testing since that's covered by main CI - runCmd: | - set -e - echo "=== Verifying devcontainer tools ===" - bazel --version - buildifier --version - echo "✅ Devcontainer build successful" diff --git a/MODULE.bazel b/MODULE.bazel index 86a16d7e..d2f3e7dd 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -27,9 +27,15 @@ use_repo(pip, "pypi") # Register toolchains for all supported tools tmux_ext = use_extension("//toolchains/tmux:extensions.bzl", "tmux_toolchain") -use_repo(tmux_ext, "local_tmux") +use_repo(tmux_ext, "tmux") -register_toolchains("@local_tmux//:tmux_local") +register_toolchains( + # Local tmux has highest priority (system installed) + "@tmux//:local", + # Downloaded static binaries as fallback + "@tmux//:linux_amd64", + "@tmux//:linux_arm64", +) zsh_ext = use_extension("//toolchains/zsh:extensions.bzl", "zsh_toolchain") use_repo(zsh_ext, "local_zsh") @@ -62,6 +68,27 @@ git_repository( CHEZMOI_VERSION = "2.65.0" # Binary dependencies with platform-specific URLs +# Static tmux binaries from mjakob-gh/build-static-tmux +http_archive( + name = "tmux_linux_amd64", + build_file_content = """ +exports_files(["tmux"]) +""", + sha256 = "a85c5c663e1b1b5b2c6b8e3b7f9b2a9e5c2a6f4d6b2f3a1c2d7e9f8b4c3d8e9f", + strip_prefix = "tmux-3.5a-linux-amd64", + urls = ["https://github.com/mjakob-gh/build-static-tmux/releases/download/v3.5a/tmux-3.5a-linux-amd64.tar.gz"], +) + +http_archive( + name = "tmux_linux_arm64", + build_file_content = """ +exports_files(["tmux"]) +""", + sha256 = "b95d6d774f2c8b6c3d7b9e4b8f0c3a1e6c3a7f5d7b3f4a2c3d8e0f9b5c4d9e0f", + strip_prefix = "tmux-3.5a-linux-arm64", + urls = ["https://github.com/mjakob-gh/build-static-tmux/releases/download/v3.5a/tmux-3.5a-linux-arm64.tar.gz"], +) + http_archive( name = "fzf_darwin_arm64", build_file_content = """ diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 1c921d4e..c4f2b81b 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -199,14 +199,14 @@ }, "//toolchains/tmux:extensions.bzl%tmux_toolchain": { "general": { - "bzlTransitiveDigest": "yY+4OksgrKPlTDCfErrT0fGifMJBnk43Wp0b5m4hVEA=", + "bzlTransitiveDigest": "lvUz6+zBiHhbADCPssSoWUwiMmZpubsFBdXlHmzkdZY=", "usagesDigest": "XnM8Tmye/ekLXDVhx8B/ltC5I291mZYwPk87XSoyX8c=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, "generatedRepoSpecs": { - "local_tmux": { - "repoRuleId": "@@//toolchains/tmux:toolchain.bzl%local_tmux_binary", + "tmux": { + "repoRuleId": "@@//toolchains/tmux:toolchain.bzl%tmux_repository", "attributes": {} } }, diff --git a/toolchains/tmux/extensions.bzl b/toolchains/tmux/extensions.bzl index c904c290..aac4889e 100644 --- a/toolchains/tmux/extensions.bzl +++ b/toolchains/tmux/extensions.bzl @@ -1,10 +1,10 @@ """Module extensions for tmux toolchain.""" -load("//toolchains/tmux:toolchain.bzl", "local_tmux_binary") +load("//toolchains/tmux:toolchain.bzl", "tmux_repository") def _tmux_toolchain_extension_impl(mctx): - # Create a repository for the local tmux binary - local_tmux_binary(name = "local_tmux") + # Create a repository that provides all tmux variants + tmux_repository(name = "tmux") # Return None, which is valid for module extensions return None @@ -13,14 +13,13 @@ def _tmux_toolchain_extension_impl(mctx): tmux_toolchain = module_extension( implementation = _tmux_toolchain_extension_impl, doc = """ - An extension that locates the local tmux installation. - The toolchain needs to be registered separately with register_toolchains. + An extension that provides all tmux toolchain variants under @tmux. Example usage: ```starlark tmux_ext = use_extension("//toolchains/tmux:extensions.bzl", "tmux_toolchain") - use_repo(tmux_ext, "local_tmux") - register_toolchains("@local_tmux//:tmux_local") + use_repo(tmux_ext, "tmux") + register_toolchains("@tmux//:local", "@tmux//:linux_amd64") ``` """, ) diff --git a/toolchains/tmux/toolchain.bzl b/toolchains/tmux/toolchain.bzl index 09e370f4..38f50dae 100644 --- a/toolchains/tmux/toolchain.bzl +++ b/toolchains/tmux/toolchain.bzl @@ -2,11 +2,133 @@ Toolchain rules for tmux. """ -load("//toolchains/common:toolchain_utils.bzl", "create_local_tool_repository_rule", "create_tool_info_provider", "create_toolchain_rule") +load("//toolchains/common:toolchain_utils.bzl", "create_tool_info_provider", "create_toolchain_rule") # Create the provider and toolchain rule using common utilities TmuxInfo = create_tool_info_provider("tmux") tmux_toolchain = create_toolchain_rule("tmux", TmuxInfo) -# Create the repository rule for local tmux discovery -local_tmux_binary = create_local_tool_repository_rule("tmux") +def _tmux_repository_impl(repository_ctx): + """Repository rule that provides all tmux variants under @tmux.""" + + # Try to find local tmux first + local_tmux_path = repository_ctx.which("tmux") + + if local_tmux_path: + # Get version from local tmux binary + result = repository_ctx.execute([local_tmux_path, "-V"]) + if result.return_code != 0: + local_version = "unknown" + else: + version_output = result.stdout.strip() + parts = version_output.split(" ") + local_version = parts[1] if len(parts) > 1 else "unknown" + else: + local_tmux_path = None + local_version = "unknown" + + # Determine platform constraints + os = repository_ctx.os.name + exec_constraints = [] + target_constraints = [] + + if os.startswith("mac"): + exec_constraints = ["@platforms//os:macos"] + target_constraints = ["@platforms//os:macos"] + elif os.startswith("linux"): + exec_constraints = ["@platforms//os:linux"] + target_constraints = ["@platforms//os:linux"] + + # Format constraints for BUILD file + exec_constraints_formatted = "" + for c in exec_constraints: + exec_constraints_formatted += ' "{}",\n'.format(c) + + target_constraints_formatted = "" + for c in target_constraints: + target_constraints_formatted += ' "{}",\n'.format(c) + + # Create BUILD file content + build_content = ''' +package(default_visibility = ["//visibility:public"]) + +load("@dotfiles//toolchains/tmux:toolchain.bzl", "tmux_toolchain") +''' + + # Add local tmux toolchain if available + if local_tmux_path: + build_content += ''' +# Local system tmux (highest priority) +tmux_toolchain( + name = "local_impl", + tmux_path = "{local_path}", + tmux_version = "{local_version}", +) + +toolchain( + name = "local", + exec_compatible_with = [ +{exec_constraints} ], + target_compatible_with = [ +{target_constraints} ], + toolchain = ":local_impl", + toolchain_type = "@dotfiles//toolchains/tmux:toolchain_type", +) +'''.format( + local_path = local_tmux_path, + local_version = local_version, + exec_constraints = exec_constraints_formatted, + target_constraints = target_constraints_formatted, + ) + + # Add downloaded static binary toolchains + build_content += ''' +# Downloaded static binaries (fallback) +tmux_toolchain( + name = "linux_amd64_impl", + tmux_path = "@tmux_linux_amd64//:tmux", + tmux_version = "3.5a", +) + +toolchain( + name = "linux_amd64", + exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + target_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + toolchain = ":linux_amd64_impl", + toolchain_type = "@dotfiles//toolchains/tmux:toolchain_type", +) + +tmux_toolchain( + name = "linux_arm64_impl", + tmux_path = "@tmux_linux_arm64//:tmux", + tmux_version = "3.5a", +) + +toolchain( + name = "linux_arm64", + exec_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:arm64", + ], + target_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:arm64", + ], + toolchain = ":linux_arm64_impl", + toolchain_type = "@dotfiles//toolchains/tmux:toolchain_type", +) +''' + + repository_ctx.file("BUILD.bazel", build_content) + +# Create the repository rule for tmux variants +tmux_repository = repository_rule( + implementation = _tmux_repository_impl, + doc = "Creates a repository that provides all tmux toolchain variants", +)