From e9b92adedebe5b579ae52232a1f4b7f0a5a56480 Mon Sep 17 00:00:00 2001 From: Alexander Lanin Date: Fri, 24 Apr 2026 00:38:52 +0200 Subject: [PATCH 01/12] feat: provide tools via bazel in addition to devcontainer --- .gitignore | 3 + MODULE.bazel | 31 + MODULE.bazel.lock | 606 ++++++++++++++++++ REUSE.toml | 4 +- .../.devcontainer/Dockerfile | 2 + .../.devcontainer/bazel-feature/install.sh | 44 +- .../bazel-feature/tests/test_default.sh | 10 +- .../.devcontainer/bazel-feature/versions.yaml | 34 - .../.devcontainer/devcontainer.json | 2 +- .../.devcontainer/s-core-local/install.sh | 93 +-- .../s-core-local/tests/test_default.sh | 20 +- .../.devcontainer/s-core-local/versions.yaml | 30 - .../.devcontainer/with-proxy-vars.Dockerfile | 3 +- tools/BUILD.bazel | 61 ++ tools/README.md | 322 ++++++++++ tools/arch.png | Bin 0 -> 93325 bytes tools/lockfiles/actionlint.lock.json | 35 + tools/lockfiles/bazelisk.lock.json | 29 + tools/lockfiles/buildifier.lock.json | 29 + tools/lockfiles/ruff.lock.json | 35 + tools/lockfiles/shellcheck.lock.json | 35 + tools/lockfiles/starpls.lock.json | 29 + tools/lockfiles/uv.lock.json | 67 ++ tools/lockfiles/yamlfmt.lock.json | 35 + tools/tool_lockfile_helpers.sh | 144 +++++ tools/tool_lockfile_query.py | 160 +++++ 26 files changed, 1672 insertions(+), 191 deletions(-) create mode 100644 MODULE.bazel create mode 100644 MODULE.bazel.lock create mode 100644 tools/BUILD.bazel create mode 100644 tools/README.md create mode 100644 tools/arch.png create mode 100644 tools/lockfiles/actionlint.lock.json create mode 100644 tools/lockfiles/bazelisk.lock.json create mode 100644 tools/lockfiles/buildifier.lock.json create mode 100644 tools/lockfiles/ruff.lock.json create mode 100644 tools/lockfiles/shellcheck.lock.json create mode 100644 tools/lockfiles/starpls.lock.json create mode 100644 tools/lockfiles/uv.lock.json create mode 100644 tools/lockfiles/yamlfmt.lock.json create mode 100644 tools/tool_lockfile_helpers.sh create mode 100644 tools/tool_lockfile_query.py diff --git a/.gitignore b/.gitignore index 0c08d08..3dd1be0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ # Exported image files shall never be committed. /export.img build/ + +# bazel files +/bazel-* diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..913a6ae --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,31 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +module(name = "score_devcontainer") + +bazel_dep(name = "rules_multitool", version = "1.11.1") + +multitool = use_extension("@rules_multitool//multitool:extension.bzl", "multitool") + +multitool.hub(lockfile = "//tools:lockfiles/actionlint.lock.json") +multitool.hub(lockfile = "//tools:lockfiles/ruff.lock.json") +multitool.hub(lockfile = "//tools:lockfiles/shellcheck.lock.json") +multitool.hub(lockfile = "//tools:lockfiles/yamlfmt.lock.json") +multitool.hub(lockfile = "//tools:lockfiles/uv.lock.json") +multitool.hub(lockfile = "//tools:lockfiles/buildifier.lock.json") +multitool.hub(lockfile = "//tools:lockfiles/starpls.lock.json") +multitool.hub(lockfile = "//tools:lockfiles/bazelisk.lock.json") + +use_repo(multitool, "multitool") + +register_toolchains("@multitool//toolchains:all") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 0000000..028cfea --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,606 @@ +{ + "lockFileVersion": 26, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", + "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", + "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", + "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", + "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", + "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e", + "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1", + "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", + "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d", + "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", + "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", + "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", + "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", + "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a", + "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", + "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", + "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", + "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", + "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309", + "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", + "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0-rc.0/MODULE.bazel": "d6e00979a98ac14ada5e31c8794708b41434d461e7e7ca39b59b765e6d233b18", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0-rc.0/source.json": "7051768079aa19302df2b9446ad0889839fd08b7d59851eba2c99234d665c9ba", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", + "https://bcr.bazel.build/modules/buildifier_prebuilt/7.3.1/MODULE.bazel": "537faf0ad9f5892910074b8e43b4c91c96f1d5d86b6ed04bdbe40cf68aa48b68", + "https://bcr.bazel.build/modules/buildifier_prebuilt/7.3.1/source.json": "55153a5e6ca9c8a7e266c4b46b951e8a010d25ec6062bc35d5d4f89925796bad", + "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f", + "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd", + "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", + "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", + "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", + "https://bcr.bazel.build/modules/googletest/1.17.0/source.json": "38e4454b25fc30f15439c0378e57909ab1fd0a443158aa35aec685da727cd713", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000", + "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9", + "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", + "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", + "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", + "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", + "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", + "https://bcr.bazel.build/modules/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42", + "https://bcr.bazel.build/modules/protobuf/33.4/source.json": "555f8686b4c7d6b5ba731fbea13bf656b4bfd9a7ff629c1d9d3f6e1d6155de79", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680", + "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/source.json": "2ff292be6ef3340325ce8a045ecc326e92cbfab47c7cbab4bd85d28971b97ac4", + "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa", + "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/MODULE.bazel": "76e10fd4a48038d3fc7c5dc6e63b7063bbf5304a2e3bd42edda6ec660eebea68", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/source.json": "8ee81e1708756f81b343a5eb2b2f0b953f1d25c4ab3d4a68dc02754872e80715", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", + "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", + "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", + "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", + "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", + "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", + "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", + "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", + "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", + "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", + "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", + "https://bcr.bazel.build/modules/rules_java/9.1.0/MODULE.bazel": "ee63f27e36a3fada80342869361182f120a9819c74320e8e65b1e04ba0cd7a9d", + "https://bcr.bazel.build/modules/rules_java/9.1.0/source.json": "da589573c1dee2c9ac4a568b301269a2e8191110ff0345c1a959fa7ea6c4dfd6", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", + "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", + "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_multitool/1.11.1/MODULE.bazel": "f826d2d394e8d964e44ebb4a75ebcfe4e9cd4eb150e2ddcd60398ffeb939696a", + "https://bcr.bazel.build/modules/rules_multitool/1.11.1/source.json": "201f43de1d35bd17f25a4fed3ba5a2ec500ef5e08b7d4b341bb9fd39cef0cbc6", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", + "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", + "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", + "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", + "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", + "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", + "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", + "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", + "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", + "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", + "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", + "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", + "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", + "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", + "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", + "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@buildifier_prebuilt+//:defs.bzl%buildifier_prebuilt_deps_extension": { + "general": { + "bzlTransitiveDigest": "3SXpv/oaemUJfW9RAoTqenEgHJE2pkdbrrkPif6Vk00=", + "usagesDigest": "eWMDBEn8E8CrwAPXrlrjIap2pseSMhxDyDdrntHBOOE=", + "recordedInputs": [ + "REPO_MAPPING:buildifier_prebuilt+,bazel_skylib bazel_skylib+", + "REPO_MAPPING:buildifier_prebuilt+,bazel_tools bazel_tools" + ], + "generatedRepoSpecs": { + "buildifier_darwin_amd64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "urls": [ + "https://github.com/bazelbuild/buildtools/releases/download/v7.3.1/buildifier-darwin-amd64" + ], + "downloaded_file_path": "buildifier", + "executable": true, + "sha256": "375f823103d01620aaec20a0c29c6cbca99f4fd0725ae30b93655c6704f44d71" + } + }, + "buildifier_darwin_arm64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "urls": [ + "https://github.com/bazelbuild/buildtools/releases/download/v7.3.1/buildifier-darwin-arm64" + ], + "downloaded_file_path": "buildifier", + "executable": true, + "sha256": "5a6afc6ac7a09f5455ba0b89bd99d5ae23b4174dc5dc9d6c0ed5ce8caac3f813" + } + }, + "buildifier_linux_amd64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "urls": [ + "https://github.com/bazelbuild/buildtools/releases/download/v7.3.1/buildifier-linux-amd64" + ], + "downloaded_file_path": "buildifier", + "executable": true, + "sha256": "5474cc5128a74e806783d54081f581662c4be8ae65022f557e9281ed5dc88009" + } + }, + "buildifier_linux_arm64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "urls": [ + "https://github.com/bazelbuild/buildtools/releases/download/v7.3.1/buildifier-linux-arm64" + ], + "downloaded_file_path": "buildifier", + "executable": true, + "sha256": "0bf86c4bfffaf4f08eed77bde5b2082e4ae5039a11e2e8b03984c173c34a561c" + } + }, + "buildifier_windows_amd64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "urls": [ + "https://github.com/bazelbuild/buildtools/releases/download/v7.3.1/buildifier-windows-amd64.exe" + ], + "downloaded_file_path": "buildifier.exe", + "executable": true, + "sha256": "370cd576075ad29930a82f5de132f1a1de4084c784a82514bd4da80c85acf4a8" + } + }, + "buildozer_darwin_amd64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "urls": [ + "https://github.com/bazelbuild/buildtools/releases/download/v7.3.1/buildozer-darwin-amd64" + ], + "downloaded_file_path": "buildozer", + "executable": true, + "sha256": "854c9583efc166602276802658cef3f224d60898cfaa60630b33d328db3b0de2" + } + }, + "buildozer_darwin_arm64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "urls": [ + "https://github.com/bazelbuild/buildtools/releases/download/v7.3.1/buildozer-darwin-arm64" + ], + "downloaded_file_path": "buildozer", + "executable": true, + "sha256": "31b1bfe20d7d5444be217af78f94c5c43799cdf847c6ce69794b7bf3319c5364" + } + }, + "buildozer_linux_amd64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "urls": [ + "https://github.com/bazelbuild/buildtools/releases/download/v7.3.1/buildozer-linux-amd64" + ], + "downloaded_file_path": "buildozer", + "executable": true, + "sha256": "3305e287b3fcc68b9a35fd8515ee617452cd4e018f9e6886b6c7cdbcba8710d4" + } + }, + "buildozer_linux_arm64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "urls": [ + "https://github.com/bazelbuild/buildtools/releases/download/v7.3.1/buildozer-linux-arm64" + ], + "downloaded_file_path": "buildozer", + "executable": true, + "sha256": "0b5a2a717ac4fc911e1fec8d92af71dbb4fe95b10e5213da0cc3d56cea64a328" + } + }, + "buildozer_windows_amd64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "urls": [ + "https://github.com/bazelbuild/buildtools/releases/download/v7.3.1/buildozer-windows-amd64.exe" + ], + "downloaded_file_path": "buildozer.exe", + "executable": true, + "sha256": "58d41ce53257c5594c9bc86d769f580909269f68de114297f46284fbb9023dcf" + } + }, + "buildifier_prebuilt_toolchains": { + "repoRuleId": "@@buildifier_prebuilt+//:defs.bzl%_buildifier_toolchain_setup", + "attributes": { + "assets_json": "[{\"arch\":\"amd64\",\"name\":\"buildifier\",\"platform\":\"darwin\",\"sha256\":\"375f823103d01620aaec20a0c29c6cbca99f4fd0725ae30b93655c6704f44d71\",\"version\":\"v7.3.1\"},{\"arch\":\"arm64\",\"name\":\"buildifier\",\"platform\":\"darwin\",\"sha256\":\"5a6afc6ac7a09f5455ba0b89bd99d5ae23b4174dc5dc9d6c0ed5ce8caac3f813\",\"version\":\"v7.3.1\"},{\"arch\":\"amd64\",\"name\":\"buildifier\",\"platform\":\"linux\",\"sha256\":\"5474cc5128a74e806783d54081f581662c4be8ae65022f557e9281ed5dc88009\",\"version\":\"v7.3.1\"},{\"arch\":\"arm64\",\"name\":\"buildifier\",\"platform\":\"linux\",\"sha256\":\"0bf86c4bfffaf4f08eed77bde5b2082e4ae5039a11e2e8b03984c173c34a561c\",\"version\":\"v7.3.1\"},{\"arch\":\"amd64\",\"name\":\"buildifier\",\"platform\":\"windows\",\"sha256\":\"370cd576075ad29930a82f5de132f1a1de4084c784a82514bd4da80c85acf4a8\",\"version\":\"v7.3.1\"},{\"arch\":\"amd64\",\"name\":\"buildozer\",\"platform\":\"darwin\",\"sha256\":\"854c9583efc166602276802658cef3f224d60898cfaa60630b33d328db3b0de2\",\"version\":\"v7.3.1\"},{\"arch\":\"arm64\",\"name\":\"buildozer\",\"platform\":\"darwin\",\"sha256\":\"31b1bfe20d7d5444be217af78f94c5c43799cdf847c6ce69794b7bf3319c5364\",\"version\":\"v7.3.1\"},{\"arch\":\"amd64\",\"name\":\"buildozer\",\"platform\":\"linux\",\"sha256\":\"3305e287b3fcc68b9a35fd8515ee617452cd4e018f9e6886b6c7cdbcba8710d4\",\"version\":\"v7.3.1\"},{\"arch\":\"arm64\",\"name\":\"buildozer\",\"platform\":\"linux\",\"sha256\":\"0b5a2a717ac4fc911e1fec8d92af71dbb4fe95b10e5213da0cc3d56cea64a328\",\"version\":\"v7.3.1\"},{\"arch\":\"amd64\",\"name\":\"buildozer\",\"platform\":\"windows\",\"sha256\":\"58d41ce53257c5594c9bc86d769f580909269f68de114297f46284fbb9023dcf\",\"version\":\"v7.3.1\"}]" + } + } + } + } + }, + "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": { + "general": { + "bzlTransitiveDigest": "b+RP7Sgl8KN0VHamrgTqzGLuYPcQ/Mo4ptNkkHUIIlA=", + "usagesDigest": "D1r3lfzMuUBFxgG8V6o0bQTLMk3GkaGOaPzw53wrwyw=", + "recordedInputs": [ + "REPO_MAPPING:pybind11_bazel+,bazel_tools bazel_tools", + "FILE:@@pybind11_bazel+//MODULE.bazel e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34" + ], + "generatedRepoSpecs": { + "pybind11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "build_file": "@@pybind11_bazel+//:pybind11-BUILD.bazel", + "strip_prefix": "pybind11-2.12.0", + "urls": [ + "https://github.com/pybind/pybind11/archive/v2.12.0.zip" + ] + } + } + } + } + }, + "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { + "general": { + "bzlTransitiveDigest": "Ga4z8lQy1YQ5rAMy+dOl0dqcCEBnYNCXku8x3YQmDZI=", + "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", + "recordedInputs": [ + "REPO_MAPPING:rules_kotlin+,bazel_tools bazel_tools" + ], + "generatedRepoSpecs": { + "com_github_jetbrains_kotlin_git": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", + "attributes": { + "urls": [ + "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" + ], + "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" + } + }, + "com_github_jetbrains_kotlin": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", + "attributes": { + "git_repository_name": "com_github_jetbrains_kotlin_git", + "compiler_version": "1.9.23" + } + }, + "com_github_google_ksp": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", + "attributes": { + "urls": [ + "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" + ], + "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", + "strip_version": "1.9.23-1.0.20" + } + }, + "com_github_pinterest_ktlint": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", + "urls": [ + "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" + ], + "executable": true + } + }, + "rules_android": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + "strip_prefix": "rules_android-0.1.1", + "urls": [ + "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" + ] + } + } + } + } + }, + "@@rules_python+//python/extensions:config.bzl%config": { + "general": { + "bzlTransitiveDigest": "iibnRYgg8LpcfmH7EAnVwYePC3jsVaJ6Id8XxUjSZps=", + "usagesDigest": "ZVSXMAGpD+xzVNPuvF1IoLBkty7TROO0+akMapt1pAg=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,pypi__build rules_python++config+pypi__build", + "REPO_MAPPING:rules_python+,pypi__click rules_python++config+pypi__click", + "REPO_MAPPING:rules_python+,pypi__colorama rules_python++config+pypi__colorama", + "REPO_MAPPING:rules_python+,pypi__importlib_metadata rules_python++config+pypi__importlib_metadata", + "REPO_MAPPING:rules_python+,pypi__installer rules_python++config+pypi__installer", + "REPO_MAPPING:rules_python+,pypi__more_itertools rules_python++config+pypi__more_itertools", + "REPO_MAPPING:rules_python+,pypi__packaging rules_python++config+pypi__packaging", + "REPO_MAPPING:rules_python+,pypi__pep517 rules_python++config+pypi__pep517", + "REPO_MAPPING:rules_python+,pypi__pip rules_python++config+pypi__pip", + "REPO_MAPPING:rules_python+,pypi__pip_tools rules_python++config+pypi__pip_tools", + "REPO_MAPPING:rules_python+,pypi__pyproject_hooks rules_python++config+pypi__pyproject_hooks", + "REPO_MAPPING:rules_python+,pypi__setuptools rules_python++config+pypi__setuptools", + "REPO_MAPPING:rules_python+,pypi__tomli rules_python++config+pypi__tomli", + "REPO_MAPPING:rules_python+,pypi__wheel rules_python++config+pypi__wheel", + "REPO_MAPPING:rules_python+,pypi__zipp rules_python++config+pypi__zipp" + ], + "generatedRepoSpecs": { + "rules_python_internal": { + "repoRuleId": "@@rules_python+//python/private:internal_config_repo.bzl%internal_config_repo", + "attributes": { + "transition_setting_generators": {}, + "transition_settings": [] + } + }, + "pypi__build": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl", + "sha256": "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__click": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", + "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__colorama": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__importlib_metadata": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl", + "sha256": "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__installer": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", + "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__more_itertools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl", + "sha256": "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__packaging": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", + "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pep517": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl", + "sha256": "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", + "sha256": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip_tools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl", + "sha256": "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pyproject_hooks": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl", + "sha256": "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__setuptools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl", + "sha256": "c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__tomli": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", + "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__wheel": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", + "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__zipp": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl", + "sha256": "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + } + } + } + }, + "@@rules_python+//python/uv:uv.bzl%uv": { + "general": { + "bzlTransitiveDigest": "ijW9KS7qsIY+yBVvJ+Nr1mzwQox09j13DnE3iIwaeTM=", + "usagesDigest": "H8dQoNZcoqP+Mu0tHZTi4KHATzvNkM5ePuEqoQdklIU=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,platforms platforms" + ], + "generatedRepoSpecs": { + "uv": { + "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", + "attributes": { + "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'", + "toolchain_names": [ + "none" + ], + "toolchain_implementations": { + "none": "'@@rules_python+//python:none'" + }, + "toolchain_compatible_with": { + "none": [ + "@platforms//:incompatible" + ] + }, + "toolchain_target_settings": {} + } + } + } + } + } + }, + "facts": {} +} diff --git a/REUSE.toml b/REUSE.toml index d6231a3..1e86a8d 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -25,7 +25,9 @@ SPDX-License-Identifier = "Apache-2.0" [[annotations]] path = ["resources/reopen_in_container.png", - "resources/devcontainer_success.png" + "resources/devcontainer_success.png", + "tools/arch.png", + "tools/lockfiles/*.lock.json", ] SPDX-FileCopyrightText = "Copyright (c) 2026 Contributors to the Eclipse Foundation" SPDX-License-Identifier = "Apache-2.0" diff --git a/src/s-core-devcontainer/.devcontainer/Dockerfile b/src/s-core-devcontainer/.devcontainer/Dockerfile index 502dbcd..2ba2494 100644 --- a/src/s-core-devcontainer/.devcontainer/Dockerfile +++ b/src/s-core-devcontainer/.devcontainer/Dockerfile @@ -16,4 +16,6 @@ FROM buildpack-deps:noble-curl LABEL dev.containers.features="common" +COPY tools /usr/local/share/score-tools + RUN userdel -f -r ubuntu diff --git a/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh b/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh index 3bcdc72..1d32d93 100755 --- a/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh +++ b/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh @@ -33,26 +33,17 @@ DEBIAN_FRONTEND=noninteractive ARCHITECTURE=$(dpkg --print-architecture) +source /usr/local/share/score-tools/tool_lockfile_helpers.sh + apt-get update # INSTALL CONTAINER BUILD DEPENDENCIES # Container build dependencies are not pinned, since they are removed anyway after container creation. apt-get install apt-transport-https -y -# Bazelisk, directly from GitHub -# Using the existing devcontainer feature is not optimal: -# - it does not check the SHA256 checksum of the downloaded file -# - it cannot pre-install a specific version of Bazel, or prepare bash completion -BAZELISK_VARIANT="amd64" -SHA256SUM="${bazelisk_amd64_sha256}" -if [ "${ARCHITECTURE}" = "arm64" ]; then - BAZELISK_VARIANT="arm64" - SHA256SUM="${bazelisk_arm64_sha256}" -fi -curl -L "https://github.com/bazelbuild/bazelisk/releases/download/v${bazelisk_version}/bazelisk-${BAZELISK_VARIANT}.deb" -o /tmp/bazelisk.deb -echo "${SHA256SUM} /tmp/bazelisk.deb" | sha256sum -c - || exit 1 -apt-get install -y --no-install-recommends --fix-broken /tmp/bazelisk.deb -rm /tmp/bazelisk.deb +# Bazelisk + Bazel +score_install_tool_from_lockfile bazelisk +ln -sf /usr/local/bin/bazelisk /usr/local/bin/bazel # Pre-install a fixed Bazel version, setup the bash command completion export USE_BAZEL_VERSION=${bazel_version} @@ -67,28 +58,11 @@ sh -c "echo 'INSTALLED_BAZEL_VERSION=${bazel_version}' >> /devcontainer/features # This is required for corporate environments with custom CA certificates echo 'startup --host_jvm_args=-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts --host_jvm_args=-Djavax.net.ssl.trustStorePassword=changeit' >> /etc/bazel.bazelrc -# Buildifier, directly from GitHub (apparently no APT repository available) -# The version is pinned to a specific release, and the SHA256 checksum is provided by the devcontainer-features.json file. -BUILDIFIER_VARIANT="amd64" -SHA256SUM="${buildifier_amd64_sha256}" -if [ "${ARCHITECTURE}" = "arm64" ]; then - BUILDIFIER_VARIANT="arm64" - SHA256SUM="${buildifier_arm64_sha256}" -fi -curl -L "https://github.com/bazelbuild/buildtools/releases/download/v${buildifier_version}/buildifier-linux-${BUILDIFIER_VARIANT}" -o /usr/local/bin/buildifier -echo "${SHA256SUM} /usr/local/bin/buildifier" | sha256sum -c - || exit 1 -chmod +x /usr/local/bin/buildifier +# Buildifier +score_install_tool_from_lockfile buildifier -# Starlark Language Server, directly from GitHub (apparently no APT repository available) -STARPLS_VARIANT="amd64" -SHA256SUM="${starpls_amd64_sha256}" -if [ "${ARCHITECTURE}" = "arm64" ]; then - STARPLS_VARIANT="aarch64" - SHA256SUM="${starpls_arm64_sha256}" -fi -curl -L "https://github.com/withered-magic/starpls/releases/download/v${starpls_version}/starpls-linux-${STARPLS_VARIANT}" -o /usr/local/bin/starpls -echo "${SHA256SUM} /usr/local/bin/starpls" | sha256sum -c - || exit 1 -chmod +x /usr/local/bin/starpls +# Starlark Language Server +score_install_tool_from_lockfile starpls # Code completion for C++ code of Bazel projects # (see https://github.com/kiron1/bazel-compile-commands) diff --git a/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh b/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh index b47c48e..5d99e19 100755 --- a/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh +++ b/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh @@ -17,16 +17,20 @@ set -euo pipefail # Read tool versions + metadata into environment variables . /devcontainer/features/s-core-local/versions.sh /devcontainer/features/bazel/versions.yaml +source /usr/local/share/score-tools/tool_lockfile_helpers.sh +bazelisk_lockfile_version="$(score_tool_version bazelisk)" +buildifier_lockfile_version="$(score_tool_version buildifier)" +starpls_lockfile_version="$(score_tool_version starpls)" # Bazel-related tools ## This is the bazel version preinstalled in the devcontainer. ## A solid test would disable the network interface first to prevent a different version from being downloaded, ## but that requires CAP_NET_ADMIN, which is not yet added. export USE_BAZEL_VERSION=${bazel_version} -check "validate bazelisk is working and has the correct version" bash -c "bazelisk version | grep '${bazelisk_version}'" +check "validate bazelisk is working and has the correct version" bash -c "bazelisk version | grep '${bazelisk_lockfile_version}'" check "validate bazel is working and has the correct version" bash -c "bazel version | grep '${bazel_version}'" unset USE_BAZEL_VERSION -check "validate buildifier is working and has the correct version" bash -c "buildifier --version | grep '${buildifier_version}'" -check "validate starpls is working and has the correct version" bash -c "starpls version | grep '${starpls_version}'" +check "validate buildifier is working and has the correct version" bash -c "buildifier --version | grep '${buildifier_lockfile_version}'" +check "validate starpls is working and has the correct version" bash -c "starpls version | grep '${starpls_lockfile_version}'" check "validate bazel-compile-commands is working and has the correct version" bash -c "bazel-compile-commands --version 2>&1 | grep '${bazel_compile_commands_version}'" diff --git a/src/s-core-devcontainer/.devcontainer/bazel-feature/versions.yaml b/src/s-core-devcontainer/.devcontainer/bazel-feature/versions.yaml index b207ead..a3fd803 100644 --- a/src/s-core-devcontainer/.devcontainer/bazel-feature/versions.yaml +++ b/src/s-core-devcontainer/.devcontainer/bazel-feature/versions.yaml @@ -14,40 +14,6 @@ bazel: # https://github.com/bazelbuild/bazel/releases -- latest version as of 2025-09-24 version: 8.4.1 # no need to define sha256 here, as bazel is installed via bazelisk -buildifier: - version: 8.2.1 - amd64: - # The following sha256sum is for the binary buildifier-linux-amd64 - # from the GitHub release page of buildtools - # It is generated by running 'sha256sum buildifier-linux-amd64' - sha256: 6ceb7b0ab7cf66fceccc56a027d21d9cc557a7f34af37d2101edb56b92fcfa1a - arm64: - # The following sha256sum is for the binary buildifier-linux-arm64 - # from the GitHub release page of buildtools - # It is generated by running 'sha256sum buildifier-linux-arm64' - sha256: 3baa1cf7eb41d51f462fdd1fff3a6a4d81d757275d05b2dd5f48671284e9a1a5 -bazelisk: - version: 1.27.0 - amd64: - # The following sha256sums are for the deb package bazelisk__amd64.deb - # It is generated by running 'sha256sum bazelisk__amd64.deb' - sha256: d8b00ea975c823e15263c80200ac42979e17368547fbff4ab177af035badfa83 - arm64: - # The following sha256sums are for the deb package bazelisk__arm64.deb - # It is generated by running 'sha256sum bazelisk__arm64.deb' - sha256: 173c5b367b485a30ce58c1d0d560b39d257a2d7a3c859c45d7d05eb61605a2a1 -starpls: - version: 0.1.22 - amd64: - # The following sha256sum is for the binary starpls-linux-amd64 - # from the GitHub release page of starpls - # It is generated by running 'sha256sum starpls-linux-amd64' - sha256: 7c661cdde0d1c026665086d07523d825671e29056276681616bb32d0273c5eab - arm64: - # The following sha256sum is for the binary starpls-linux-arm64 - # from the GitHub release page of starpls - # It is generated by running 'sha256sum starpls-linux-arm64' - sha256: 55877ec4c3ff03e1d90d59c76f69a3a144b6c29688747c8ac4d77993e2eef1ad bazel_compile_commands: version: 0.18.0 amd64: diff --git a/src/s-core-devcontainer/.devcontainer/devcontainer.json b/src/s-core-devcontainer/.devcontainer/devcontainer.json index 33fdeb0..f1394b0 100644 --- a/src/s-core-devcontainer/.devcontainer/devcontainer.json +++ b/src/s-core-devcontainer/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ "build": { // Installs latest version from the Distribution "dockerfile": "./${localEnv:DEVCONTAINER_DOCKERFILE_NAME:Dockerfile}", - "context": ".", + "context": "../../../", "args": { "HTTP_PROXY": "${localEnv:HTTP_PROXY}", "HTTPS_PROXY": "${localEnv:HTTPS_PROXY}", diff --git a/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh b/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh index 94b39ac..02286aa 100755 --- a/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh +++ b/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh @@ -36,52 +36,7 @@ DEBIAN_FRONTEND=noninteractive ARCHITECTURE=$(dpkg --print-architecture) KERNEL=$(uname -s) -# Downloads and extracts a tool from GitHub releases, based on the provided URL pattern, version and architecture-specific checksums. -# The URL pattern can include placeholders for version and architecture variant -download_and_extract_from_github() { - local url_pattern="$1" - local tool_name="$2" - local amd64_name="$3" - local arm64_name="$4" - local extract_names="$5" - local strip_components="${6:-0}" - local temp_file="/tmp/${tool_name}" - - local version_name="${tool_name}_version" - export version="${!version_name}" - variant="${amd64_name}" - local sha256sum_name="${tool_name}_amd64_sha256" - if [ "${ARCHITECTURE}" = "arm64" ]; then - variant="${arm64_name}" - sha256sum_name="${tool_name}_arm64_sha256" - fi - sha256sum="${!sha256sum_name}" - export variant - - local url - url="$(eval "echo ${url_pattern}")" - - curl -L "${url}" -o "${temp_file}" - echo "${sha256sum} ${temp_file}" | sha256sum -c - || exit 1 - - local tar_options="" - if [[ "${url}" == *.tar.gz ]]; then - tar_options="-xzf" - elif [[ "${url}" == *.tar.xz ]]; then - tar_options="-xf" - elif [[ "${url}" == *.tar.zst ]]; then - tar_options="-I zstd -xf" - fi - - local extract_names_expanded - extract_names_expanded="$(eval "echo ${extract_names}")" - - # shellcheck disable=SC2086 - # tar_options and extract_names_expanded are expected to be word-split - tar ${tar_options} "${temp_file}" -C "/usr/local/bin" --strip-components="${strip_components}" ${extract_names_expanded} - - rm "${temp_file}" -} +source /usr/local/share/score-tools/tool_lockfile_helpers.sh # always add PIPX_BIN_DIR to path PIPX_BIN_DIR_EXPORT="$(grep "export PIPX_BIN_DIR" /etc/bash.bashrc)" @@ -98,13 +53,14 @@ apt-get install -y man-db manpages manpages-dev manpages-posix manpages-posix-de # Container build dependencies are not pinned, since they are removed anyway after container creation. apt-get install apt-transport-https -y +# Python, via APT +apt-get install -y "python${python_version}" python3-pip python3-venv +# The following packages correspond to the list of packages installed by the +# devcontainer feature "python" (cf. https://github.com/devcontainers/features/tree/main/src/python ) +apt-get install -y flake8 python3-autopep8 black python3-yapf mypy pydocstyle pycodestyle bandit pipenv virtualenv pylint + # static code analysis for shell scripts -download_and_extract_from_github \ - 'https://github.com/koalaman/shellcheck/releases/download/v${version}/shellcheck-v${version}.linux.${variant}.tar.xz' \ - "shellcheck" \ - "x86_64" "aarch64" \ - 'shellcheck-v${version}/shellcheck' \ - 1 +score_install_tool_from_lockfile shellcheck # GraphViz # The Ubuntu Noble package of GraphViz @@ -118,12 +74,6 @@ apt-get install -y git apt-get install -y git-lfs apt-get install -y gh -# Python, via APT -apt-get install -y "python${python_version}" python3-pip python3-venv -# The following packages correspond to the list of packages installed by the -# devcontainer feature "python" (cf. https://github.com/devcontainers/features/tree/main/src/python ) -apt-get install -y flake8 python3-autopep8 black python3-yapf mypy pydocstyle pycodestyle bandit pipenv virtualenv pylint - # OpenJDK 21, via APT # Set JAVA_HOME environment variable system-wide, since some tools rely on it (e.g., Bazel's rules_java) apt-get install -y ca-certificates-java openjdk-21-jdk-headless="${openjdk_21_version}*" @@ -135,34 +85,17 @@ echo -e "JAVA_HOME=${JAVA_HOME}\nexport JAVA_HOME" > /etc/profile.d/java_home.sh apt-get install -y --no-install-recommends --fix-broken qemu-system-arm="${qemu_system_arm_version}*" # ruff -download_and_extract_from_github \ - 'https://github.com/astral-sh/ruff/releases/download/${version}/ruff-${variant}-unknown-linux-gnu.tar.gz' \ - "ruff" \ - "x86_64" "aarch64" \ - 'ruff-${variant}-unknown-linux-gnu/ruff' \ - 1 +score_install_tool_from_lockfile ruff # actionlint -download_and_extract_from_github \ - 'https://github.com/rhysd/actionlint/releases/download/v${version}/actionlint_${version}_linux_${variant}.tar.gz' \ - "actionlint" \ - "amd64" "arm64" \ - 'actionlint' +score_install_tool_from_lockfile actionlint # yamlfmt -download_and_extract_from_github \ - 'https://github.com/google/yamlfmt/releases/download/v${version}/yamlfmt_${version}_Linux_${variant}.tar.gz' \ - "yamlfmt" \ - "x86_64" "arm64" \ - 'yamlfmt' +score_install_tool_from_lockfile yamlfmt # uv -download_and_extract_from_github \ - 'https://github.com/astral-sh/uv/releases/download/${version}/uv-${variant}-unknown-linux-gnu.tar.gz' \ - "uv" \ - "x86_64" "aarch64" \ - 'uv-${variant}-unknown-linux-gnu/uv uv-${variant}-unknown-linux-gnu/uvx' \ - 1 +score_install_tool_from_lockfile uv +score_install_tool_from_lockfile uvx uv # basedpyright su $(ls /home) -c "uv tool install basedpyright@\"${basedpyright_version}\"" diff --git a/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh b/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh index b2e6731..909e1cc 100755 --- a/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh +++ b/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh @@ -20,12 +20,20 @@ KERNEL=$(uname -s) # Read tool versions + metadata into environment variables . /devcontainer/features/s-core-local/versions.sh /devcontainer/features/s-core-local/versions.yaml +source /usr/local/share/score-tools/tool_lockfile_helpers.sh + +shellcheck_lockfile_version="$(score_tool_version shellcheck)" +ruff_lockfile_version="$(score_tool_version ruff)" +actionlint_lockfile_version="$(score_tool_version actionlint)" +yamlfmt_lockfile_version="$(score_tool_version yamlfmt)" +uv_lockfile_version="$(score_tool_version uv)" +uvx_lockfile_version="$(score_tool_version uvx uv)" # pre-commit, it is available via $PATH in login shells, but not in non-login shells check "validate pre-commit is working and has the correct version" bash -c "pre-commit --version | grep '4.5.1'" # Common tooling -check "validate shellcheck is working and has the correct version" bash -c "shellcheck --version | grep '${shellcheck_version}'" +check "validate shellcheck is working and has the correct version" bash -c "shellcheck --version | grep '${shellcheck_lockfile_version}'" # For an unknown reason, dot -V reports on Ubuntu Noble a version 2.43.0, while the package has a different version. # Hence, we have to work around that. @@ -56,17 +64,17 @@ check "validate java is working and has the correct version" bash -c "java -vers check "validate JAVA_HOME is set correctly" bash -c "echo ${JAVA_HOME} | xargs readlink -f | grep \"java-21-openjdk\"" # ruff -check "validate ruff is working and has the correct version" bash -c "ruff --version | grep '${ruff_version}'" +check "validate ruff is working and has the correct version" bash -c "ruff --version | grep '${ruff_lockfile_version}'" # actionlint -check "validate actionlint is working and has the correct version" bash -c "actionlint --version | grep '${actionlint_version}'" +check "validate actionlint is working and has the correct version" bash -c "actionlint --version | grep '${actionlint_lockfile_version}'" # yamlfmt -check "validate yamlfmt is working and has the correct version" bash -c "yamlfmt --version | grep '${yamlfmt_version}'" +check "validate yamlfmt is working and has the correct version" bash -c "yamlfmt --version | grep '${yamlfmt_lockfile_version}'" # uv -check "validate uv is working and has the correct version" bash -c "uv --version | grep '${uv_version}'" -check "validate uvx is working and has the correct version" bash -c "uvx --version | grep '${uv_version}'" +check "validate uv is working and has the correct version" bash -c "uv --version | grep '${uv_lockfile_version}'" +check "validate uvx is working and has the correct version" bash -c "uvx --version | grep '${uvx_lockfile_version}'" # additional developer tools check "validate gdb is working and has the correct version" bash -c "gdb --version | grep '${gdb_version}'" diff --git a/src/s-core-devcontainer/.devcontainer/s-core-local/versions.yaml b/src/s-core-devcontainer/.devcontainer/s-core-local/versions.yaml index fcb8a81..37961b4 100644 --- a/src/s-core-devcontainer/.devcontainer/s-core-local/versions.yaml +++ b/src/s-core-devcontainer/.devcontainer/s-core-local/versions.yaml @@ -10,12 +10,6 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -actionlint: - version: 1.7.7 - amd64: - sha256: 023070a287cd8cccd71515fedc843f1985bf96c436b7effaecce67290e7e0757 - arm64: - sha256: 401942f9c24ed71e4fe71b76c7d638f66d8633575c4016efd2977ce7c28317d0 basedpyright: version: 1.35.0 graphviz: @@ -40,24 +34,6 @@ gh: version: 2.45.0 openjdk_21: version: 21.0.10 -ruff: - version: 0.11.13 - amd64: - sha256: 01aa32d29d00876b8d1429c617ed63a00b1fc81abfa4183bb05c9cb647fbc3d0 - arm64: - sha256: 551af2ebc439d8268dcaf871ea60ad035f688728d30943dcbb2bf775e105213e -shellcheck: - version: 0.10.0 - amd64: - sha256: 6c881ab0698e4e6ea235245f22832860544f17ba386442fe7e9d629f8cbedf87 - arm64: - sha256: 324a7e89de8fa2aed0d0c28f3dab59cf84c6d74264022c00c22af665ed1a09bb -uv: - version: 0.10.4 - amd64: - sha256: 6b52a47358deea1c5e173278bf46b2b489747a59ae31f2a4362ed5c6c1c269f7 - arm64: - sha256: c84a6e6405715caa6e2f5ef8e5f29a5d0bc558a954e9f1b5c082b9d4708c222e codeql: # the coding_standards_version below dictates the codeql version version: 2.21.4 @@ -71,9 +47,3 @@ codeql_coding_standards: version: 2.54.0 valgrind: version: 3.22.0 -yamlfmt: - version: 0.17.0 - amd64: - sha256: e91dd8722001596b8e4777a29d4a526a10ff276c4ff8a5ae39ff59be5a033054 - arm64: - sha256: ebe78a5547dac68f05a01c9a2845901b3c658095432b107bef3084dfe0b2629d diff --git a/src/s-core-devcontainer/.devcontainer/with-proxy-vars.Dockerfile b/src/s-core-devcontainer/.devcontainer/with-proxy-vars.Dockerfile index 372c4a2..ab546e9 100644 --- a/src/s-core-devcontainer/.devcontainer/with-proxy-vars.Dockerfile +++ b/src/s-core-devcontainer/.devcontainer/with-proxy-vars.Dockerfile @@ -33,6 +33,7 @@ ENV no_proxy=${no_proxy} LABEL dev.containers.features="common" # Unset proxy variables for all login shells -COPY unset-proxy.sh /etc/bash_completion.d/unset-proxy.sh +COPY src/s-core-devcontainer/.devcontainer/unset-proxy.sh /etc/bash_completion.d/unset-proxy.sh +COPY tools /usr/local/share/score-tools RUN userdel -f -r ubuntu diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel new file mode 100644 index 0000000..62192f4 --- /dev/null +++ b/tools/BUILD.bazel @@ -0,0 +1,61 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +package(default_visibility = ["//visibility:public"]) + +exports_files(glob(["lockfiles/*.lock.json"])) + +alias( + name = "actionlint", + actual = "@multitool//tools/actionlint:cwd", +) + +alias( + name = "bazelisk", + actual = "@multitool//tools/bazelisk:cwd", +) + +alias( + name = "buildifier", + actual = "@multitool//tools/buildifier:cwd", +) + +alias( + name = "ruff", + actual = "@multitool//tools/ruff:cwd", +) + +alias( + name = "shellcheck", + actual = "@multitool//tools/shellcheck:cwd", +) + +alias( + name = "starpls", + actual = "@multitool//tools/starpls:cwd", +) + +alias( + name = "uv", + actual = "@multitool//tools/uv:cwd", +) + +alias( + name = "uvx", + actual = "@multitool//tools/uvx:cwd", +) + +alias( + name = "yamlfmt", + actual = "@multitool//tools/yamlfmt:cwd", +) diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000..fe8f2ce --- /dev/null +++ b/tools/README.md @@ -0,0 +1,322 @@ +# Tooling Strategy: Reproducible CLI Tools Across Development Environments + +> This document complements the general infrastructure direction defined in +> [DR-001 Infrastructure Design Decision](https://eclipse-score.github.io/score/main/design_decisions/DR-001-infra.html) +> and specifies how CLI tooling is provided across environments. + +## Purpose + +We provide selected CLI tools such as `actionlint` and `shellcheck` in a reproducible way across supported development environments. + +The goal is simple: + +- same tool versions +- same behavior +- same results + +independent of how developers choose to work. + +--- + +## Strategy + +We support two ways to access the same tooling: + +- **DevContainer** +- **Bazel via `rules_multitool`** + +Both are supported intentionally. + +Not all developers work the same way. Some prefer a fully managed environment, others prefer to stay on their host system. Both workflows exist in practice, and both need to produce identical results. + +![Tooling architecture](arch.png) + +--- + +## Design Principle + +> Reproducibility is required. +> The execution path is a developer choice. + +This means: + +- no reliance on system-installed tools +- no hidden dependencies +- no environment-specific behavior + +--- + +## DevContainer + +The DevContainer provides: + +- a ready-to-use environment +- minimal setup effort +- predictable tooling + +For many developers, this is the most straightforward option. + +--- + +## Bazel-Based Tool Access + +We additionally expose tools via Bazel using [`rules_multitool`](https://github.com/bazel-contrib/rules_multitool). + +Example usage: +- `bazel run //tools:actionlint` +- `bazel run //tools:shellcheck` + +This exists primarily to support workflows outside the DevContainer. + +It allows: + +- reproducible tool execution on the host +- consistent versions across platforms +- alignment with CI execution + +At the same time, invoking standalone tools through a build system is not always the most ergonomic experience. The setup therefore focuses on making this path reliable rather than minimal. + +--- + +## Why We Support Both + +In practice: + +- some developers use the DevContainer +- some developers do not +- some switch between both depending on the task + +Relying on only one of these paths would either: + +- reduce adoption (DevContainer-only), or +- introduce inconsistencies (native-only) + +Supporting both allows flexibility without sacrificing consistency. + +--- + +## Why `rules_multitool` + +We use [`rules_multitool`](https://github.com/bazel-contrib/rules_multitool) to provide: + +- pinned tool versions +- checksum verification +- platform-specific binaries (Linux x64, macOS arm64) +- a uniform way to expose CLI tools via Bazel + +This is particularly useful for standalone tools such as: + +- `actionlint` +- `shellcheck` + +The alternative would be to manually maintain platform mappings, download logic, and wrappers for each tool. At scale, that quickly turns into a parallel infrastructure effort. + +--- + +## Why This Approach + +This setup reflects the actual constraints: + +- large number of users +- multiple host platforms +- mixed development workflows +- need for consistent results across local and CI + +A single enforced workflow would simplify the model, but would not match how the system is used in reality. + +--- + +## Alternatives Considered + +### DevContainer only + +Conceptually simple, but assumes universal adoption. In practice, that assumption does not hold, leading to gaps in reproducibility. + +--- + +### Bazel toolchains + +Technically correct and very powerful, but introduce significantly more complexity than needed for standalone CLI tools. + +--- + +## Why Use a Niche Solution + +`rules_multitool` is not widely used, and that is expected. + +Most teams: + +- operate on a single platform (usually Linux) +- rely on CI-only validation +- accept minor inconsistencies in local setups + +Under those conditions, simpler approaches are sufficient. + +Our setup differs: + +- cross-platform development (Linux + macOS ARM) +- large team size +- frequent local execution of tools +- low tolerance for inconsistencies + +In this context, reproducibility becomes more important than minimizing tooling layers. + +--- + +## Source of Truth + +For tools downloaded directly from upstream release artifacts that participate +in the shared lockfile-based setup, the authoritative metadata lives in the +`tools/lockfiles/*.lock.json` files. + +These lockfiles define: + +- supported platforms +- download URLs +- checksums +- archive or package layout + +Both Bazel via `rules_multitool` and the DevContainer installation scripts +consume the same lockfiles. + +Feature installation scripts must not duplicate version, URL, or checksum data +for these tools. + +Tools that are currently still managed directly inside a feature script, or via +the distribution package manager, remain managed elsewhere. + +--- + +## Using From Another Repository + +There are two supported Bazel usage patterns for consumers outside this +repository. + +### Option 1: Reuse the exported tool targets directly + +If another repository wants to use the exact targets defined here, it can depend +on this module and run the tools through external labels. + +Because this module is not published to a Bazel registry yet, consumers +currently need an override such as `local_path_override(...)`. + +Consumer `MODULE.bazel`: + +```starlark +module(name = "consumer") + +bazel_dep(name = "score_devcontainer", version = "0.0.0") + +local_path_override( + module_name = "score_devcontainer", + path = "/absolute/path/to/score_devcontainer", +) +``` + +Then run the tools through the exported targets from this repository: + +- `bazel run @score_devcontainer//tools:actionlint -- --version` +- `bazel run @score_devcontainer//tools:shellcheck -- --version` +- `bazel run @score_devcontainer//tools:ruff -- --version` + +If the consumer wants local target names, it can keep option 1 and add local +aliases on top: + +Consumer `BUILD.bazel`: + +```starlark +alias( + name = "shellcheck", + actual = "@score_devcontainer//tools:shellcheck", +) + +alias( + name = "actionlint", + actual = "@score_devcontainer//tools:actionlint", +) +``` + +Then run: + +- `bazel run //:shellcheck -- --version` +- `bazel run //:actionlint -- --version` + +This is the simplest option if the consumer wants the targets defined here, but +prefers local labels in its own repository. + +### Option 2: Reuse the lockfiles, but define local targets in the consumer + +If another repository wants to keep its own target names, it can import the +lockfiles exported by this repository and create its own `rules_multitool` hub. + +The lockfiles are exported as files from the top-level `tools` package, so the +external labels look like this: + +- `@score_devcontainer//tools:lockfiles/actionlint.lock.json` +- `@score_devcontainer//tools:lockfiles/shellcheck.lock.json` + +Consumer `MODULE.bazel`: + +```starlark +module(name = "consumer") + +bazel_dep(name = "rules_multitool", version = "1.11.1") +bazel_dep(name = "score_devcontainer", version = "0.0.0") + +local_path_override( + module_name = "score_devcontainer", + path = "/absolute/path/to/score_devcontainer", +) + +multitool = use_extension("@rules_multitool//multitool:extension.bzl", "multitool") + +multitool.hub(lockfile = "@score_devcontainer//tools:lockfiles/shellcheck.lock.json") +multitool.hub(lockfile = "@score_devcontainer//tools:lockfiles/actionlint.lock.json") + +use_repo(multitool, "multitool") +register_toolchains("@multitool//toolchains:all") +``` + +Consumer `BUILD.bazel`: + +```starlark +alias( + name = "shellcheck", + actual = "@multitool//tools/shellcheck:cwd", +) + +alias( + name = "actionlint", + actual = "@multitool//tools/actionlint:cwd", +) +``` + +Then run: + +- `bazel run //:shellcheck -- --version` +- `bazel run //:actionlint -- --version` + +This option is useful if the consumer wants to share the pinned tool metadata +but expose its own wrapper targets. + +### Notes + +- The lockfile labels above are intended as the cross-repository API for Bazel + consumers. +- The shell helpers in this directory are internal support code for the + DevContainer image build. They are not intended to be sourced from another + repository. +- Once this repository is published in a registry or consumed through a pinned + Git/archive override, the `local_path_override(...)` can be replaced with the + appropriate distribution mechanism. + +--- + +## Summary + +We provide: + +- a **DevContainer** for convenience and quick setup +- **Bazel-based tooling** for reproducible execution outside the container + +This combination allows developers to choose their workflow while ensuring consistent and predictable results across the project. diff --git a/tools/arch.png b/tools/arch.png new file mode 100644 index 0000000000000000000000000000000000000000..55ee37d9750001eca6a9782766c063693ffa6175 GIT binary patch literal 93325 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 [lockfile] +# - score_install_tool_from_lockfile [lockfile] [destination] +# +# Example: +# source /usr/local/share/score-tools/tool_lockfile_helpers.sh +# score_tool_version shellcheck +# score_install_tool_from_lockfile buildifier + +# Resolve the helper location once when the file is sourced. This keeps the +# script self-contained: as long as the `.sh`, `.py`, and `lockfiles/` +# directory stay together, callers do not need to pass any path configuration. +readonly SCORE_TOOL_HELPERS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCORE_TOOL_LOCKFILE_QUERY_PY="${SCORE_TOOL_HELPERS_DIR}/tool_lockfile_query.py" + +_score_run_tool_lockfile_query() { + # Use the Python helper for all JSON parsing and platform selection. + python3 "${SCORE_TOOL_LOCKFILE_QUERY_PY}" "$@" +} + +score_tool_version() { + # Print the declared version for one tool entry. + local tool_name="$1" + local lockfile_name="${2:-$1}" + + _score_run_tool_lockfile_query \ + version \ + --lockfile "${lockfile_name}" \ + --tool "${tool_name}" +} + +score_install_tool_from_lockfile() { + # Download, verify, extract if needed, and install one lockfile-defined + # tool. The lockfile tells us which URL, checksum, archive type, and + # in-archive path belong to the current platform. + local tool_name="$1" + local lockfile_name="${2:-$1}" + local destination="${3:-/usr/local/bin/${tool_name}}" + + local kind="" + local url="" + local sha256="" + local file="" + local archive_type="" + + # Convert the Python helper's `key=value` output into normal shell + # variables. We keep the mapping explicit so it is obvious which pieces of + # metadata the installer consumes. + while IFS='=' read -r key value; do + case "${key}" in + kind) kind="${value}" ;; + url) url="${value}" ;; + sha256) sha256="${value}" ;; + file) file="${value}" ;; + type) archive_type="${value}" ;; + esac + done < <( + _score_run_tool_lockfile_query \ + env \ + --lockfile "${lockfile_name}" \ + --tool "${tool_name}" + ) + + if [[ -z "${kind}" || -z "${url}" || -z "${sha256}" ]]; then + echo "Incomplete lockfile metadata for ${tool_name}" >&2 + return 1 + fi + + # Work in a temporary directory so partially downloaded archives do not + # pollute the filesystem and cleanup is automatic on return. + local temp_dir + temp_dir="$(mktemp -d)" + trap 'rm -rf "${temp_dir}"; trap - RETURN' RETURN + + local download_path="${temp_dir}/download" + + # Always verify the checksum before we execute or unpack anything. + curl -fsSL "${url}" -o "${download_path}" + echo "${sha256} ${download_path}" | sha256sum -c - >/dev/null + + case "${kind}" in + file) + # Simple case: the downloaded file is the final executable. + install -D -m 0755 "${download_path}" "${destination}" + ;; + archive) + case "${archive_type}" in + tar.gz|tgz) + # Extract only the requested member from the tarball. + tar -xzf "${download_path}" -C "${temp_dir}" "${file}" + install -D -m 0755 "${temp_dir}/${file}" "${destination}" + ;; + tar.xz|txz) + # Same as above, but for xz-compressed tarballs. + tar -xJf "${download_path}" -C "${temp_dir}" "${file}" + install -D -m 0755 "${temp_dir}/${file}" "${destination}" + ;; + zip) + # Use Python's standard-library zip support. + # This avoids an additional `unzip` package dependency. + python3 -m zipfile -e "${download_path}" "${temp_dir}" >/dev/null + install -D -m 0755 "${temp_dir}/${file}" "${destination}" + ;; + deb|.deb) + # Debian packages already describe their installation + # layout, so we let `apt-get` handle unpacking and file + # placement. + apt-get install -y --no-install-recommends --fix-broken "${download_path}" + ;; + *) + echo "Unsupported archive type '${archive_type}' for ${tool_name}" >&2 + return 1 + ;; + esac + ;; + *) + echo "Unsupported lockfile kind '${kind}' for ${tool_name}" >&2 + return 1 + ;; + esac +} diff --git a/tools/tool_lockfile_query.py b/tools/tool_lockfile_query.py new file mode 100644 index 0000000..5727565 --- /dev/null +++ b/tools/tool_lockfile_query.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +"""Query tool metadata from the shared `tools/lockfiles/*.lock.json` catalog. + +This script is intentionally small and dependency-free so shell-based +devcontainer feature installers and tests can ask two focused questions without +re-implementing JSON parsing or platform selection logic: + +1. "What is the declared version for tool X?" +2. "Which binary metadata applies to tool X on this OS/CPU?" + +The shell helper `tool_lockfile_helpers.sh` wraps this script for everyday use. +""" +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +from __future__ import annotations + +import argparse +import json +import platform +import sys +from pathlib import Path + + +LOCKFILE_ROOT = Path(__file__).resolve().parent / "lockfiles" + + +def _detect_os() -> str: + """Map Python's platform string to the lockfile schema's OS names.""" + system = platform.system() + if system == "Linux": + return "linux" + if system == "Darwin": + return "macos" + raise SystemExit(f"Unsupported OS: {system}") + + +def _detect_cpu() -> str: + """Map Python's machine string to the lockfile schema's CPU names.""" + machine = platform.machine().lower() + if machine in {"x86_64", "amd64"}: + return "x86_64" + if machine in {"arm64", "aarch64"}: + return "arm64" + raise SystemExit(f"Unsupported CPU architecture: {machine}") + + +def _lockfile_path(lockfile: str) -> Path: + """Resolve a lockfile basename like `ruff` to `ruff.lock.json`.""" + return LOCKFILE_ROOT / f"{lockfile}.lock.json" + + +def _load_tool(lockfile: str, tool: str) -> dict: + """Load one tool entry from a lockfile and fail with a clear message.""" + with _lockfile_path(lockfile).open(encoding="utf-8") as handle: + data = json.load(handle) + + try: + return data[tool] + except KeyError as exc: + raise SystemExit( + f"Tool '{tool}' not found in lockfile '{lockfile}.lock.json'", + ) from exc + + +def _select_binary(tool_data: dict, os_name: str, cpu: str) -> dict: + """Pick the binary entry matching the requested platform.""" + for binary in tool_data["binaries"]: + if binary["os"] == os_name and binary["cpu"] == cpu: + return binary + + raise SystemExit( + f"No binary defined for os={os_name!r}, cpu={cpu!r}", + ) + + +def _cmd_version(args: argparse.Namespace) -> int: + """Print the declared version for one tool.""" + tool_data = _load_tool(args.lockfile, args.tool) + version = tool_data.get("version") + if version is None: + raise SystemExit( + f"Tool '{args.tool}' in '{args.lockfile}.lock.json' does not define a version", + ) + print(version) + return 0 + + +def _cmd_env(args: argparse.Namespace) -> int: + """Print selected binary metadata as `key=value` lines for shell callers.""" + tool_data = _load_tool(args.lockfile, args.tool) + binary = _select_binary(tool_data, args.os, args.cpu) + + output = {} + if "version" in tool_data: + output["version"] = tool_data["version"] + for key in ("kind", "url", "sha256", "file", "type", "os", "cpu"): + if key in binary: + output[key] = binary[key] + + for key, value in output.items(): + print(f"{key}={value}") + return 0 + + +def _build_parser() -> argparse.ArgumentParser: + """Define a tiny CLI for version and platform-specific metadata lookups.""" + parser = argparse.ArgumentParser( + description="Read tool metadata from multitool-compatible lockfiles.", + ) + subparsers = parser.add_subparsers(dest="command", required=True) + + version_parser = subparsers.add_parser( + "version", + help="Print the version for a tool from a lockfile.", + ) + version_parser.add_argument("--tool", required=True) + version_parser.add_argument( + "--lockfile", + help="Lockfile basename without .lock.json, defaults to the tool name", + ) + version_parser.set_defaults(func=_cmd_version) + + env_parser = subparsers.add_parser( + "env", + help="Print selected binary metadata as shell-style key=value lines.", + ) + env_parser.add_argument("--tool", required=True) + env_parser.add_argument( + "--lockfile", + help="Lockfile basename without .lock.json, defaults to the tool name", + ) + env_parser.add_argument("--os", default=_detect_os()) + env_parser.add_argument("--cpu", default=_detect_cpu()) + env_parser.set_defaults(func=_cmd_env) + + return parser + + +def main(argv: list[str] | None = None) -> int: + """Parse arguments, fill in default lockfile names, and dispatch.""" + parser = _build_parser() + args = parser.parse_args(argv) + if args.lockfile is None: + args.lockfile = args.tool + return args.func(args) + + +if __name__ == "__main__": + sys.exit(main()) From 29db3339219df8a3e2446af463ecb72f8d001752 Mon Sep 17 00:00:00 2001 From: Alexander Lanin Date: Mon, 27 Apr 2026 16:57:28 +0200 Subject: [PATCH 02/12] move versions.sh --- .gitignore | 3 +++ .../.devcontainer/bazel-feature/devcontainer-feature.json | 3 --- src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh | 2 +- .../.devcontainer/bazel-feature/tests/test_default.sh | 2 +- src/s-core-devcontainer/.devcontainer/s-core-local/install.sh | 2 +- .../.devcontainer/s-core-local/tests/test_default.sh | 2 +- .../.devcontainer/s-core-local => tools}/versions.sh | 0 7 files changed, 7 insertions(+), 7 deletions(-) rename {src/s-core-devcontainer/.devcontainer/s-core-local => tools}/versions.sh (100%) diff --git a/.gitignore b/.gitignore index 3dd1be0..764b4f2 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ build/ # bazel files /bazel-* + +# AI +/.codex diff --git a/src/s-core-devcontainer/.devcontainer/bazel-feature/devcontainer-feature.json b/src/s-core-devcontainer/.devcontainer/bazel-feature/devcontainer-feature.json index d60e94f..b15d32c 100644 --- a/src/s-core-devcontainer/.devcontainer/bazel-feature/devcontainer-feature.json +++ b/src/s-core-devcontainer/.devcontainer/bazel-feature/devcontainer-feature.json @@ -3,9 +3,6 @@ "id": "bazel", "version": "1.0.0", "description": "Bazel and supplimentary tools for working with Bazel-based projects.", - "dependsOn": { - "./s-core-local": {} // needed for extracting versions (versions.sh) - }, "onCreateCommand": "/devcontainer/features/bazel/on_create_command.sh", "postCreateCommand": { // The repos in S-CORE may use different Bazel versions. This ensures that the required version is installed. diff --git a/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh b/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh index 1d32d93..599c0dd 100755 --- a/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh +++ b/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh @@ -29,7 +29,7 @@ rm -f "${COPY_TARGET}/devcontainer-features.env" "${COPY_TARGET}/devcontainer-fe DEBIAN_FRONTEND=noninteractive # Read tool versions + metadata into environment variables -. /devcontainer/features/s-core-local/versions.sh /devcontainer/features/bazel/versions.yaml +. /usr/local/share/score-tools/versions.sh /devcontainer/features/bazel/versions.yaml ARCHITECTURE=$(dpkg --print-architecture) diff --git a/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh b/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh index 5d99e19..4dec9a0 100755 --- a/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh +++ b/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh @@ -16,7 +16,7 @@ set -euo pipefail # Read tool versions + metadata into environment variables -. /devcontainer/features/s-core-local/versions.sh /devcontainer/features/bazel/versions.yaml +. /usr/local/share/score-tools/versions.sh /devcontainer/features/bazel/versions.yaml source /usr/local/share/score-tools/tool_lockfile_helpers.sh bazelisk_lockfile_version="$(score_tool_version bazelisk)" diff --git a/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh b/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh index 02286aa..f970b02 100755 --- a/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh +++ b/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh @@ -31,7 +31,7 @@ rm -f "${COPY_TARGET}/devcontainer-features.env" "${COPY_TARGET}/devcontainer-fe DEBIAN_FRONTEND=noninteractive # Read tool versions + metadata into environment variables -. /devcontainer/features/s-core-local/versions.sh /devcontainer/features/s-core-local/versions.yaml +. /usr/local/share/score-tools/versions.sh /devcontainer/features/s-core-local/versions.yaml ARCHITECTURE=$(dpkg --print-architecture) KERNEL=$(uname -s) diff --git a/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh b/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh index 909e1cc..5fe9cbd 100755 --- a/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh +++ b/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh @@ -19,7 +19,7 @@ ARCHITECTURE=$(dpkg --print-architecture) KERNEL=$(uname -s) # Read tool versions + metadata into environment variables -. /devcontainer/features/s-core-local/versions.sh /devcontainer/features/s-core-local/versions.yaml +. /usr/local/share/score-tools/versions.sh /devcontainer/features/s-core-local/versions.yaml source /usr/local/share/score-tools/tool_lockfile_helpers.sh shellcheck_lockfile_version="$(score_tool_version shellcheck)" diff --git a/src/s-core-devcontainer/.devcontainer/s-core-local/versions.sh b/tools/versions.sh similarity index 100% rename from src/s-core-devcontainer/.devcontainer/s-core-local/versions.sh rename to tools/versions.sh From 0d1960e798543bfa6ba9a65c5033aec5a30405af Mon Sep 17 00:00:00 2001 From: Alexander Lanin Date: Mon, 27 Apr 2026 17:15:16 +0200 Subject: [PATCH 03/12] auto format --- tools/README.md | 76 ++++++++++++++++++------------------ tools/tool_lockfile_query.py | 22 +++++------ 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/tools/README.md b/tools/README.md index fe8f2ce..b7b5d87 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,7 +1,7 @@ # Tooling Strategy: Reproducible CLI Tools Across Development Environments -> This document complements the general infrastructure direction defined in -> [DR-001 Infrastructure Design Decision](https://eclipse-score.github.io/score/main/design_decisions/DR-001-infra.html) +> This document complements the general infrastructure direction defined in +> [DR-001 Infrastructure Design Decision](https://eclipse-score.github.io/score/main/design_decisions/DR-001-infra.html) > and specifies how CLI tooling is provided across environments. ## Purpose @@ -10,9 +10,9 @@ We provide selected CLI tools such as `actionlint` and `shellcheck` in a reprodu The goal is simple: -- same tool versions -- same behavior -- same results +- same tool versions +- same behavior +- same results independent of how developers choose to work. @@ -35,14 +35,14 @@ Not all developers work the same way. Some prefer a fully managed environment, o ## Design Principle -> Reproducibility is required. +> Reproducibility is required. > The execution path is a developer choice. This means: -- no reliance on system-installed tools -- no hidden dependencies -- no environment-specific behavior +- no reliance on system-installed tools +- no hidden dependencies +- no environment-specific behavior --- @@ -50,9 +50,9 @@ This means: The DevContainer provides: -- a ready-to-use environment -- minimal setup effort -- predictable tooling +- a ready-to-use environment +- minimal setup effort +- predictable tooling For many developers, this is the most straightforward option. @@ -70,9 +70,9 @@ This exists primarily to support workflows outside the DevContainer. It allows: -- reproducible tool execution on the host -- consistent versions across platforms -- alignment with CI execution +- reproducible tool execution on the host +- consistent versions across platforms +- alignment with CI execution At the same time, invoking standalone tools through a build system is not always the most ergonomic experience. The setup therefore focuses on making this path reliable rather than minimal. @@ -82,13 +82,13 @@ At the same time, invoking standalone tools through a build system is not always In practice: -- some developers use the DevContainer -- some developers do not -- some switch between both depending on the task +- some developers use the DevContainer +- some developers do not +- some switch between both depending on the task Relying on only one of these paths would either: -- reduce adoption (DevContainer-only), or +- reduce adoption (DevContainer-only), or - introduce inconsistencies (native-only) Supporting both allows flexibility without sacrificing consistency. @@ -99,15 +99,15 @@ Supporting both allows flexibility without sacrificing consistency. We use [`rules_multitool`](https://github.com/bazel-contrib/rules_multitool) to provide: -- pinned tool versions -- checksum verification -- platform-specific binaries (Linux x64, macOS arm64) -- a uniform way to expose CLI tools via Bazel +- pinned tool versions +- checksum verification +- platform-specific binaries (Linux x64, macOS arm64) +- a uniform way to expose CLI tools via Bazel This is particularly useful for standalone tools such as: -- `actionlint` -- `shellcheck` +- `actionlint` +- `shellcheck` The alternative would be to manually maintain platform mappings, download logic, and wrappers for each tool. At scale, that quickly turns into a parallel infrastructure effort. @@ -117,10 +117,10 @@ The alternative would be to manually maintain platform mappings, download logic, This setup reflects the actual constraints: -- large number of users -- multiple host platforms -- mixed development workflows -- need for consistent results across local and CI +- large number of users +- multiple host platforms +- mixed development workflows +- need for consistent results across local and CI A single enforced workflow would simplify the model, but would not match how the system is used in reality. @@ -146,18 +146,18 @@ Technically correct and very powerful, but introduce significantly more complexi Most teams: -- operate on a single platform (usually Linux) -- rely on CI-only validation -- accept minor inconsistencies in local setups +- operate on a single platform (usually Linux) +- rely on CI-only validation +- accept minor inconsistencies in local setups Under those conditions, simpler approaches are sufficient. Our setup differs: -- cross-platform development (Linux + macOS ARM) -- large team size -- frequent local execution of tools -- low tolerance for inconsistencies +- cross-platform development (Linux + macOS ARM) +- large team size +- frequent local execution of tools +- low tolerance for inconsistencies In this context, reproducibility becomes more important than minimizing tooling layers. @@ -316,7 +316,7 @@ but expose its own wrapper targets. We provide: -- a **DevContainer** for convenience and quick setup -- **Bazel-based tooling** for reproducible execution outside the container +- a **DevContainer** for convenience and quick setup +- **Bazel-based tooling** for reproducible execution outside the container This combination allows developers to choose their workflow while ensuring consistent and predictable results across the project. diff --git a/tools/tool_lockfile_query.py b/tools/tool_lockfile_query.py index 5727565..0fd3828 100644 --- a/tools/tool_lockfile_query.py +++ b/tools/tool_lockfile_query.py @@ -1,15 +1,4 @@ #!/usr/bin/env python3 -"""Query tool metadata from the shared `tools/lockfiles/*.lock.json` catalog. - -This script is intentionally small and dependency-free so shell-based -devcontainer feature installers and tests can ask two focused questions without -re-implementing JSON parsing or platform selection logic: - -1. "What is the declared version for tool X?" -2. "Which binary metadata applies to tool X on this OS/CPU?" - -The shell helper `tool_lockfile_helpers.sh` wraps this script for everyday use. -""" # ******************************************************************************* # Copyright (c) 2026 Contributors to the Eclipse Foundation # @@ -22,6 +11,17 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +"""Query tool metadata from the shared `tools/lockfiles/*.lock.json` catalog. + +This script is intentionally small and dependency-free so shell-based +devcontainer feature installers and tests can ask two focused questions without +re-implementing JSON parsing or platform selection logic: + +1. "What is the declared version for tool X?" +2. "Which binary metadata applies to tool X on this OS/CPU?" + +The shell helper `tool_lockfile_helpers.sh` wraps this script for everyday use. +""" from __future__ import annotations From 00de55d7dec4ecd3f32458c7328ce424b6c557fa Mon Sep 17 00:00:00 2001 From: Alexander Lanin Date: Mon, 27 Apr 2026 17:25:39 +0200 Subject: [PATCH 04/12] auto fix --- .pre-commit-config.yaml | 1 + REUSE.toml | 2 +- tools/README.md | 18 +++++++- tools/arch.png | Bin 93325 -> 0 bytes tools/arch.svg | 75 +++++++++++++++++++++++++++++++++ tools/tool_lockfile_helpers.sh | 7 ++- tools/tool_lockfile_query.py | 1 - 7 files changed, 99 insertions(+), 5 deletions(-) delete mode 100644 tools/arch.png create mode 100644 tools/arch.svg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3fecbb9..d3e8e6e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,7 @@ repos: - id: check-executables-have-shebangs - id: check-added-large-files args: [--maxkb=50, --enforce-all] # increase or add git lfs if too strict + exclude: ^MODULE\.bazel\.lock$ - repo: https://github.com/google/yamlfmt rev: 21ca5323a9c87ee37a434e0ca908efc0a89daa07 # v0.21.0 hooks: diff --git a/REUSE.toml b/REUSE.toml index 1e86a8d..6904c6c 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -26,8 +26,8 @@ SPDX-License-Identifier = "Apache-2.0" [[annotations]] path = ["resources/reopen_in_container.png", "resources/devcontainer_success.png", - "tools/arch.png", "tools/lockfiles/*.lock.json", + "MODULE.bazel.lock", ] SPDX-FileCopyrightText = "Copyright (c) 2026 Contributors to the Eclipse Foundation" SPDX-License-Identifier = "Apache-2.0" diff --git a/tools/README.md b/tools/README.md index b7b5d87..f66ef9d 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,3 +1,19 @@ + + # Tooling Strategy: Reproducible CLI Tools Across Development Environments > This document complements the general infrastructure direction defined in @@ -29,7 +45,7 @@ Both are supported intentionally. Not all developers work the same way. Some prefer a fully managed environment, others prefer to stay on their host system. Both workflows exist in practice, and both need to produce identical results. -![Tooling architecture](arch.png) +![Tooling architecture](arch.svg) --- diff --git a/tools/arch.png b/tools/arch.png deleted file mode 100644 index 55ee37d9750001eca6a9782766c063693ffa6175..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93325 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 + + + Tooling architecture + A shared lockfile catalog feeds DevContainer installation scripts and Bazel rules_multitool targets, giving both execution paths the same tool versions and checksums. + + + + + + + + + + + Shared tool lockfiles + versions, URLs, checksums, platforms + + + DevContainer + feature installers download tools + + + Bazel + rules_multitool exposes targets + + + + + same metadata + same metadata + diff --git a/tools/tool_lockfile_helpers.sh b/tools/tool_lockfile_helpers.sh index 699a8bb..2bdecb0 100644 --- a/tools/tool_lockfile_helpers.sh +++ b/tools/tool_lockfile_helpers.sh @@ -1,4 +1,3 @@ -#!/usr/bin/env bash # ******************************************************************************* # Copyright (c) 2026 Contributors to the Eclipse Foundation # @@ -12,6 +11,8 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +# shellcheck shell=bash + set -euo pipefail # Shared shell helpers around the `tools/lockfiles/*.lock.json` catalog. @@ -33,7 +34,8 @@ set -euo pipefail # Resolve the helper location once when the file is sourced. This keeps the # script self-contained: as long as the `.sh`, `.py`, and `lockfiles/` # directory stay together, callers do not need to pass any path configuration. -readonly SCORE_TOOL_HELPERS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +SCORE_TOOL_HELPERS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +readonly SCORE_TOOL_HELPERS_DIR readonly SCORE_TOOL_LOCKFILE_QUERY_PY="${SCORE_TOOL_HELPERS_DIR}/tool_lockfile_query.py" _score_run_tool_lockfile_query() { @@ -76,6 +78,7 @@ score_install_tool_from_lockfile() { sha256) sha256="${value}" ;; file) file="${value}" ;; type) archive_type="${value}" ;; + *) ;; esac done < <( _score_run_tool_lockfile_query \ diff --git a/tools/tool_lockfile_query.py b/tools/tool_lockfile_query.py index 0fd3828..8fa7cac 100644 --- a/tools/tool_lockfile_query.py +++ b/tools/tool_lockfile_query.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # ******************************************************************************* # Copyright (c) 2026 Contributors to the Eclipse Foundation # From 45dc467a1fa5b851666937e0f81fd9253967ff02 Mon Sep 17 00:00:00 2001 From: Alexander Lanin Date: Mon, 27 Apr 2026 17:55:26 +0200 Subject: [PATCH 05/12] fix local .devcontainer --- .devcontainer/post_create_command.sh | 8 ++++-- .pre-commit-config.yaml | 15 +++++++---- tools/run_precommit_tool.sh | 40 ++++++++++++++++++++++++++++ tools/tool_lockfile_helpers.sh | 32 ++++++++++++++++++++++ 4 files changed, 88 insertions(+), 7 deletions(-) create mode 100755 tools/run_precommit_tool.sh diff --git a/.devcontainer/post_create_command.sh b/.devcontainer/post_create_command.sh index 3fa2982..d337c0a 100755 --- a/.devcontainer/post_create_command.sh +++ b/.devcontainer/post_create_command.sh @@ -14,8 +14,12 @@ # ******************************************************************************* npm install -g @devcontainers/cli + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +REPOSITORY_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd -P)" + +sudo bash "${REPOSITORY_ROOT}/tools/tool_lockfile_helpers.sh" install shellcheck yamlfmt + pre-commit install scripts/create_builder.sh - -sudo apt-get update && sudo apt-get install -y shellcheck diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d3e8e6e..fcf6716 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,14 +23,19 @@ repos: - id: check-added-large-files args: [--maxkb=50, --enforce-all] # increase or add git lfs if too strict exclude: ^MODULE\.bazel\.lock$ - - repo: https://github.com/google/yamlfmt - rev: 21ca5323a9c87ee37a434e0ca908efc0a89daa07 # v0.21.0 + - repo: local hooks: - id: yamlfmt - - repo: https://github.com/jumanjihouse/pre-commit-hooks - rev: 38980559e3a605691d6579f96222c30778e5a69e # 3.0.0 - hooks: + name: yamlfmt + entry: tools/run_precommit_tool.sh yamlfmt + language: system + types: [yaml] - id: shellcheck + name: shellcheck + entry: tools/run_precommit_tool.sh shellcheck + language: system + args: [-e, SC1091] + types: [shell] - repo: https://github.com/eclipse-score/tooling rev: 31ff8eee214e4e97ef8f5cb46e443273515b63ec hooks: diff --git a/tools/run_precommit_tool.sh b/tools/run_precommit_tool.sh new file mode 100755 index 0000000..dccd31b --- /dev/null +++ b/tools/run_precommit_tool.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +set -euo pipefail + +if [[ "$#" -lt 1 ]]; then + echo "Usage: $0 [args...]" >&2 + exit 2 +fi + +tool_name="$1" +shift + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +repository_root="$(cd "${script_dir}/.." && pwd -P)" + +if { [[ -f /.dockerenv ]] || [[ -f /run/.containerenv ]] || [[ -d /devcontainer ]]; } && + command -v "${tool_name}" >/dev/null 2>&1; then + exec "${tool_name}" "$@" +fi + +if command -v bazel >/dev/null 2>&1; then +cd "${repository_root}" +exec bazel run "//tools:${tool_name}" -- "$@" +fi + +echo "Could not run '${tool_name}': not available on PATH in a container, and bazel was not found." >&2 +exit 127 diff --git a/tools/tool_lockfile_helpers.sh b/tools/tool_lockfile_helpers.sh index 2bdecb0..7bdd075 100644 --- a/tools/tool_lockfile_helpers.sh +++ b/tools/tool_lockfile_helpers.sh @@ -30,6 +30,10 @@ set -euo pipefail # source /usr/local/share/score-tools/tool_lockfile_helpers.sh # score_tool_version shellcheck # score_install_tool_from_lockfile buildifier +# +# Direct usage: +# bash ./tool_lockfile_helpers.sh install shellcheck yamlfmt +# bash ./tool_lockfile_helpers.sh version shellcheck # Resolve the helper location once when the file is sourced. This keeps the # script self-contained: as long as the `.sh`, `.py`, and `lockfiles/` @@ -145,3 +149,31 @@ score_install_tool_from_lockfile() { ;; esac } + +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + command="${1:-}" + shift || true + + case "${command}" in + install) + if [[ "$#" -lt 1 ]]; then + echo "Usage: $0 install [tool...]" >&2 + exit 2 + fi + for tool_name in "$@"; do + score_install_tool_from_lockfile "${tool_name}" + done + ;; + version) + if [[ "$#" -lt 1 || "$#" -gt 2 ]]; then + echo "Usage: $0 version [lockfile]" >&2 + exit 2 + fi + score_tool_version "$@" + ;; + *) + echo "Usage: $0 [tool...]" >&2 + exit 2 + ;; + esac +fi From f6b56c272a405653e5824979d69eda04a7bff3ab Mon Sep 17 00:00:00 2001 From: Alexander Lanin Date: Mon, 27 Apr 2026 18:12:57 +0200 Subject: [PATCH 06/12] wip --- .../.devcontainer/bazel-feature/install.sh | 12 ++--- .../bazel-feature/tests/test_default.sh | 7 ++- .../.devcontainer/s-core-local/install.sh | 19 +------- .../s-core-local/tests/test_default.sh | 15 +++---- tools/README.md | 6 +-- tools/tool_lockfile_helpers.sh | 45 ++++++++++--------- tools/tool_lockfile_query.py | 29 +++++++++++- 7 files changed, 69 insertions(+), 64 deletions(-) diff --git a/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh b/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh index 599c0dd..a020d63 100755 --- a/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh +++ b/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh @@ -33,16 +33,16 @@ DEBIAN_FRONTEND=noninteractive ARCHITECTURE=$(dpkg --print-architecture) -source /usr/local/share/score-tools/tool_lockfile_helpers.sh - apt-get update # INSTALL CONTAINER BUILD DEPENDENCIES # Container build dependencies are not pinned, since they are removed anyway after container creation. apt-get install apt-transport-https -y +# Lockfile-managed Bazel tooling +bash /usr/local/share/score-tools/tool_lockfile_helpers.sh install bazelisk buildifier starpls + # Bazelisk + Bazel -score_install_tool_from_lockfile bazelisk ln -sf /usr/local/bin/bazelisk /usr/local/bin/bazel # Pre-install a fixed Bazel version, setup the bash command completion @@ -58,12 +58,6 @@ sh -c "echo 'INSTALLED_BAZEL_VERSION=${bazel_version}' >> /devcontainer/features # This is required for corporate environments with custom CA certificates echo 'startup --host_jvm_args=-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts --host_jvm_args=-Djavax.net.ssl.trustStorePassword=changeit' >> /etc/bazel.bazelrc -# Buildifier -score_install_tool_from_lockfile buildifier - -# Starlark Language Server -score_install_tool_from_lockfile starpls - # Code completion for C++ code of Bazel projects # (see https://github.com/kiron1/bazel-compile-commands) source /etc/lsb-release diff --git a/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh b/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh index 4dec9a0..ad9dea5 100755 --- a/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh +++ b/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh @@ -17,11 +17,10 @@ set -euo pipefail # Read tool versions + metadata into environment variables . /usr/local/share/score-tools/versions.sh /devcontainer/features/bazel/versions.yaml -source /usr/local/share/score-tools/tool_lockfile_helpers.sh -bazelisk_lockfile_version="$(score_tool_version bazelisk)" -buildifier_lockfile_version="$(score_tool_version buildifier)" -starpls_lockfile_version="$(score_tool_version starpls)" +bazelisk_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version bazelisk)" +buildifier_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version buildifier)" +starpls_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version starpls)" # Bazel-related tools ## This is the bazel version preinstalled in the devcontainer. ## A solid test would disable the network interface first to prevent a different version from being downloaded, diff --git a/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh b/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh index f970b02..a431507 100755 --- a/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh +++ b/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh @@ -36,8 +36,6 @@ DEBIAN_FRONTEND=noninteractive ARCHITECTURE=$(dpkg --print-architecture) KERNEL=$(uname -s) -source /usr/local/share/score-tools/tool_lockfile_helpers.sh - # always add PIPX_BIN_DIR to path PIPX_BIN_DIR_EXPORT="$(grep "export PIPX_BIN_DIR" /etc/bash.bashrc)" eval "${PIPX_BIN_DIR_EXPORT}" @@ -59,8 +57,8 @@ apt-get install -y "python${python_version}" python3-pip python3-venv # devcontainer feature "python" (cf. https://github.com/devcontainers/features/tree/main/src/python ) apt-get install -y flake8 python3-autopep8 black python3-yapf mypy pydocstyle pycodestyle bandit pipenv virtualenv pylint -# static code analysis for shell scripts -score_install_tool_from_lockfile shellcheck +# Lockfile-managed local developer tools +bash /usr/local/share/score-tools/tool_lockfile_helpers.sh install shellcheck ruff actionlint yamlfmt uv uvx # GraphViz # The Ubuntu Noble package of GraphViz @@ -84,19 +82,6 @@ echo -e "JAVA_HOME=${JAVA_HOME}\nexport JAVA_HOME" > /etc/profile.d/java_home.sh # qemu-system-arm apt-get install -y --no-install-recommends --fix-broken qemu-system-arm="${qemu_system_arm_version}*" -# ruff -score_install_tool_from_lockfile ruff - -# actionlint -score_install_tool_from_lockfile actionlint - -# yamlfmt -score_install_tool_from_lockfile yamlfmt - -# uv -score_install_tool_from_lockfile uv -score_install_tool_from_lockfile uvx uv - # basedpyright su $(ls /home) -c "uv tool install basedpyright@\"${basedpyright_version}\"" diff --git a/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh b/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh index 5fe9cbd..29526ec 100755 --- a/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh +++ b/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh @@ -20,14 +20,13 @@ KERNEL=$(uname -s) # Read tool versions + metadata into environment variables . /usr/local/share/score-tools/versions.sh /devcontainer/features/s-core-local/versions.yaml -source /usr/local/share/score-tools/tool_lockfile_helpers.sh - -shellcheck_lockfile_version="$(score_tool_version shellcheck)" -ruff_lockfile_version="$(score_tool_version ruff)" -actionlint_lockfile_version="$(score_tool_version actionlint)" -yamlfmt_lockfile_version="$(score_tool_version yamlfmt)" -uv_lockfile_version="$(score_tool_version uv)" -uvx_lockfile_version="$(score_tool_version uvx uv)" + +shellcheck_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version shellcheck)" +ruff_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version ruff)" +actionlint_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version actionlint)" +yamlfmt_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version yamlfmt)" +uv_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version uv)" +uvx_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version uvx)" # pre-commit, it is available via $PATH in login shells, but not in non-login shells check "validate pre-commit is working and has the correct version" bash -c "pre-commit --version | grep '4.5.1'" diff --git a/tools/README.md b/tools/README.md index f66ef9d..43e21f0 100644 --- a/tools/README.md +++ b/tools/README.md @@ -319,9 +319,9 @@ but expose its own wrapper targets. - The lockfile labels above are intended as the cross-repository API for Bazel consumers. -- The shell helpers in this directory are internal support code for the - DevContainer image build. They are not intended to be sourced from another - repository. +- The lockfile shell installer in this directory is internal support code for + the DevContainer image build. It is not intended as a stable cross-repository + API. - Once this repository is published in a registry or consumed through a pinned Git/archive override, the `local_path_override(...)` can be replaced with the appropriate distribution mechanism. diff --git a/tools/tool_lockfile_helpers.sh b/tools/tool_lockfile_helpers.sh index 7bdd075..18e5d52 100644 --- a/tools/tool_lockfile_helpers.sh +++ b/tools/tool_lockfile_helpers.sh @@ -15,27 +15,15 @@ set -euo pipefail -# Shared shell helpers around the `tools/lockfiles/*.lock.json` catalog. +# CLI installer around the `tools/lockfiles/*.lock.json` catalog. # -# This script is meant to be sourced by devcontainer feature installers and -# tests. It delegates JSON parsing and platform selection to -# `tool_lockfile_query.py`, then adds the shell ergonomics needed for -# installation. -# -# Provided functions: -# - score_tool_version [lockfile] -# - score_install_tool_from_lockfile [lockfile] [destination] -# -# Example: -# source /usr/local/share/score-tools/tool_lockfile_helpers.sh -# score_tool_version shellcheck -# score_install_tool_from_lockfile buildifier -# -# Direct usage: +# This script delegates JSON parsing and platform selection to +# `tool_lockfile_query.py`, then adds the shell logic needed for installation. +# Public usage is through the CLI: # bash ./tool_lockfile_helpers.sh install shellcheck yamlfmt # bash ./tool_lockfile_helpers.sh version shellcheck -# Resolve the helper location once when the file is sourced. This keeps the +# Resolve the helper location once when the script starts. This keeps the # script self-contained: as long as the `.sh`, `.py`, and `lockfiles/` # directory stay together, callers do not need to pass any path configuration. SCORE_TOOL_HELPERS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" @@ -47,10 +35,21 @@ _score_run_tool_lockfile_query() { python3 "${SCORE_TOOL_LOCKFILE_QUERY_PY}" "$@" } +_score_tool_lockfile_name() { + # Print the lockfile basename that declares the requested tool. + local tool_name="$1" + + _score_run_tool_lockfile_query lockfile --tool "${tool_name}" +} + score_tool_version() { # Print the declared version for one tool entry. local tool_name="$1" - local lockfile_name="${2:-$1}" + local lockfile_name="${2:-}" + + if [[ -z "${lockfile_name}" ]]; then + lockfile_name="$(_score_tool_lockfile_name "${tool_name}")" + fi _score_run_tool_lockfile_query \ version \ @@ -63,9 +62,13 @@ score_install_tool_from_lockfile() { # tool. The lockfile tells us which URL, checksum, archive type, and # in-archive path belong to the current platform. local tool_name="$1" - local lockfile_name="${2:-$1}" + local lockfile_name="${2:-}" local destination="${3:-/usr/local/bin/${tool_name}}" + if [[ -z "${lockfile_name}" ]]; then + lockfile_name="$(_score_tool_lockfile_name "${tool_name}")" + fi + local kind="" local url="" local sha256="" @@ -156,7 +159,7 @@ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then case "${command}" in install) - if [[ "$#" -lt 1 ]]; then + if [[ "$#" -eq 0 ]]; then echo "Usage: $0 install [tool...]" >&2 exit 2 fi @@ -172,7 +175,7 @@ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then score_tool_version "$@" ;; *) - echo "Usage: $0 [tool...]" >&2 + echo "Usage: $0 install [tool...] | version [lockfile]" >&2 exit 2 ;; esac diff --git a/tools/tool_lockfile_query.py b/tools/tool_lockfile_query.py index 8fa7cac..ccb9d01 100644 --- a/tools/tool_lockfile_query.py +++ b/tools/tool_lockfile_query.py @@ -13,11 +13,12 @@ """Query tool metadata from the shared `tools/lockfiles/*.lock.json` catalog. This script is intentionally small and dependency-free so shell-based -devcontainer feature installers and tests can ask two focused questions without +devcontainer feature installers and tests can ask three focused questions without re-implementing JSON parsing or platform selection logic: 1. "What is the declared version for tool X?" 2. "Which binary metadata applies to tool X on this OS/CPU?" +3. "Which lockfile declares tool X?" The shell helper `tool_lockfile_helpers.sh` wraps this script for everyday use. """ @@ -72,6 +73,17 @@ def _load_tool(lockfile: str, tool: str) -> dict: ) from exc +def _find_lockfile(tool: str) -> str: + """Find the lockfile basename that declares a tool.""" + for path in sorted(LOCKFILE_ROOT.glob("*.lock.json")): + with path.open(encoding="utf-8") as handle: + data = json.load(handle) + if tool in data: + return path.name.removesuffix(".lock.json") + + raise SystemExit(f"Tool '{tool}' not found in lockfile catalog") + + def _select_binary(tool_data: dict, os_name: str, cpu: str) -> dict: """Pick the binary entry matching the requested platform.""" for binary in tool_data["binaries"]: @@ -95,6 +107,12 @@ def _cmd_version(args: argparse.Namespace) -> int: return 0 +def _cmd_lockfile(args: argparse.Namespace) -> int: + """Print the lockfile basename that declares one tool.""" + print(_find_lockfile(args.tool)) + return 0 + + def _cmd_env(args: argparse.Namespace) -> int: """Print selected binary metadata as `key=value` lines for shell callers.""" tool_data = _load_tool(args.lockfile, args.tool) @@ -119,6 +137,13 @@ def _build_parser() -> argparse.ArgumentParser: ) subparsers = parser.add_subparsers(dest="command", required=True) + lockfile_parser = subparsers.add_parser( + "lockfile", + help="Print the lockfile basename declaring a tool.", + ) + lockfile_parser.add_argument("--tool", required=True) + lockfile_parser.set_defaults(func=_cmd_lockfile) + version_parser = subparsers.add_parser( "version", help="Print the version for a tool from a lockfile.", @@ -150,7 +175,7 @@ def main(argv: list[str] | None = None) -> int: """Parse arguments, fill in default lockfile names, and dispatch.""" parser = _build_parser() args = parser.parse_args(argv) - if args.lockfile is None: + if hasattr(args, "lockfile") and args.lockfile is None: args.lockfile = args.tool return args.func(args) From b7b00994f9d2cb07b575da642b9c1c4ab923b027 Mon Sep 17 00:00:00 2001 From: Alexander Lanin Date: Mon, 27 Apr 2026 18:26:47 +0200 Subject: [PATCH 07/12] rename --- .pre-commit-config.yaml | 4 ++-- tools/{run_precommit_tool.sh => run_tool.sh} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename tools/{run_precommit_tool.sh => run_tool.sh} (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fcf6716..196cadb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,12 +27,12 @@ repos: hooks: - id: yamlfmt name: yamlfmt - entry: tools/run_precommit_tool.sh yamlfmt + entry: tools/run_tool.sh yamlfmt language: system types: [yaml] - id: shellcheck name: shellcheck - entry: tools/run_precommit_tool.sh shellcheck + entry: tools/run_tool.sh shellcheck language: system args: [-e, SC1091] types: [shell] diff --git a/tools/run_precommit_tool.sh b/tools/run_tool.sh similarity index 100% rename from tools/run_precommit_tool.sh rename to tools/run_tool.sh From 232d775769e47525f697a773f9a8a9e9b60faffc Mon Sep 17 00:00:00 2001 From: Alexander Lanin Date: Mon, 27 Apr 2026 21:33:54 +0200 Subject: [PATCH 08/12] docs: assume score_devcontainer module is in registry --- tools/README.md | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/tools/README.md b/tools/README.md index 43e21f0..df64898 100644 --- a/tools/README.md +++ b/tools/README.md @@ -213,20 +213,12 @@ repository. If another repository wants to use the exact targets defined here, it can depend on this module and run the tools through external labels. -Because this module is not published to a Bazel registry yet, consumers -currently need an override such as `local_path_override(...)`. - Consumer `MODULE.bazel`: ```starlark module(name = "consumer") -bazel_dep(name = "score_devcontainer", version = "0.0.0") - -local_path_override( - module_name = "score_devcontainer", - path = "/absolute/path/to/score_devcontainer", -) +bazel_dep(name = "score_devcontainer", version = "1.4.1") ``` Then run the tools through the exported targets from this repository: @@ -277,12 +269,7 @@ Consumer `MODULE.bazel`: module(name = "consumer") bazel_dep(name = "rules_multitool", version = "1.11.1") -bazel_dep(name = "score_devcontainer", version = "0.0.0") - -local_path_override( - module_name = "score_devcontainer", - path = "/absolute/path/to/score_devcontainer", -) +bazel_dep(name = "score_devcontainer", version = "1.4.1") multitool = use_extension("@rules_multitool//multitool:extension.bzl", "multitool") @@ -315,6 +302,12 @@ Then run: This option is useful if the consumer wants to share the pinned tool metadata but expose its own wrapper targets. +### Version alignment + +The `score_devcontainer` Bazel module version corresponds to the DevContainer +image version. Repositories that use both the DevContainer and the Bazel module +must pin the same version to ensure identical tool versions in both paths. + ### Notes - The lockfile labels above are intended as the cross-repository API for Bazel @@ -322,9 +315,6 @@ but expose its own wrapper targets. - The lockfile shell installer in this directory is internal support code for the DevContainer image build. It is not intended as a stable cross-repository API. -- Once this repository is published in a registry or consumed through a pinned - Git/archive override, the `local_path_override(...)` can be replaced with the - appropriate distribution mechanism. --- From e38d2dc25b9bc7ad77cde9c732deb0e3f88120d3 Mon Sep 17 00:00:00 2001 From: Alexander Lanin Date: Mon, 27 Apr 2026 21:40:17 +0200 Subject: [PATCH 09/12] document run_tools --- tools/run_tool.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/run_tool.sh b/tools/run_tool.sh index dccd31b..504547d 100755 --- a/tools/run_tool.sh +++ b/tools/run_tool.sh @@ -13,6 +13,10 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* +# Unified entry point for running a CLI tool by name. +# Inside a container the tool is expected on PATH; outside, it is resolved via Bazel. +# See tools/README.md for the rationale behind supporting both paths. + set -euo pipefail if [[ "$#" -lt 1 ]]; then From c762db98517edd4e8090b289e8c31f15e602c7f1 Mon Sep 17 00:00:00 2001 From: Alexander Lanin Date: Mon, 27 Apr 2026 22:35:47 +0200 Subject: [PATCH 10/12] drop sh and use py Co-authored-by: Copilot --- .devcontainer/post_create_command.sh | 2 +- .gitignore | 3 + .../.devcontainer/bazel-feature/install.sh | 2 +- .../bazel-feature/tests/test_default.sh | 6 +- .../.devcontainer/s-core-local/install.sh | 2 +- .../s-core-local/tests/test_default.sh | 12 +- tools/tool_installer.py | 249 ++++++++++++++++++ tools/tool_lockfile_helpers.sh | 182 ------------- tools/tool_lockfile_query.py | 184 ------------- 9 files changed, 264 insertions(+), 378 deletions(-) create mode 100644 tools/tool_installer.py delete mode 100644 tools/tool_lockfile_helpers.sh delete mode 100644 tools/tool_lockfile_query.py diff --git a/.devcontainer/post_create_command.sh b/.devcontainer/post_create_command.sh index d337c0a..72ab6bd 100755 --- a/.devcontainer/post_create_command.sh +++ b/.devcontainer/post_create_command.sh @@ -18,7 +18,7 @@ npm install -g @devcontainers/cli SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" REPOSITORY_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd -P)" -sudo bash "${REPOSITORY_ROOT}/tools/tool_lockfile_helpers.sh" install shellcheck yamlfmt +sudo python3 "${REPOSITORY_ROOT}/tools/tool_installer.py" install shellcheck yamlfmt pre-commit install diff --git a/.gitignore b/.gitignore index 764b4f2..31f6757 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ build/ # AI /.codex + +# Python files +*.pyc diff --git a/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh b/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh index a020d63..c8e67a5 100755 --- a/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh +++ b/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh @@ -40,7 +40,7 @@ apt-get update apt-get install apt-transport-https -y # Lockfile-managed Bazel tooling -bash /usr/local/share/score-tools/tool_lockfile_helpers.sh install bazelisk buildifier starpls +python3 /usr/local/share/score-tools/tool_installer.py install bazelisk buildifier starpls # Bazelisk + Bazel ln -sf /usr/local/bin/bazelisk /usr/local/bin/bazel diff --git a/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh b/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh index ad9dea5..94db4a8 100755 --- a/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh +++ b/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh @@ -18,9 +18,9 @@ set -euo pipefail # Read tool versions + metadata into environment variables . /usr/local/share/score-tools/versions.sh /devcontainer/features/bazel/versions.yaml -bazelisk_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version bazelisk)" -buildifier_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version buildifier)" -starpls_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version starpls)" +bazelisk_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version bazelisk)" +buildifier_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version buildifier)" +starpls_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version starpls)" # Bazel-related tools ## This is the bazel version preinstalled in the devcontainer. ## A solid test would disable the network interface first to prevent a different version from being downloaded, diff --git a/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh b/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh index a431507..51bda1b 100755 --- a/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh +++ b/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh @@ -58,7 +58,7 @@ apt-get install -y "python${python_version}" python3-pip python3-venv apt-get install -y flake8 python3-autopep8 black python3-yapf mypy pydocstyle pycodestyle bandit pipenv virtualenv pylint # Lockfile-managed local developer tools -bash /usr/local/share/score-tools/tool_lockfile_helpers.sh install shellcheck ruff actionlint yamlfmt uv uvx +python3 /usr/local/share/score-tools/tool_installer.py install shellcheck ruff actionlint yamlfmt uv uvx # GraphViz # The Ubuntu Noble package of GraphViz diff --git a/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh b/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh index 29526ec..1a61d28 100755 --- a/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh +++ b/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh @@ -21,12 +21,12 @@ KERNEL=$(uname -s) # Read tool versions + metadata into environment variables . /usr/local/share/score-tools/versions.sh /devcontainer/features/s-core-local/versions.yaml -shellcheck_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version shellcheck)" -ruff_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version ruff)" -actionlint_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version actionlint)" -yamlfmt_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version yamlfmt)" -uv_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version uv)" -uvx_lockfile_version="$(bash /usr/local/share/score-tools/tool_lockfile_helpers.sh version uvx)" +shellcheck_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version shellcheck)" +ruff_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version ruff)" +actionlint_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version actionlint)" +yamlfmt_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version yamlfmt)" +uv_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version uv)" +uvx_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version uvx)" # pre-commit, it is available via $PATH in login shells, but not in non-login shells check "validate pre-commit is working and has the correct version" bash -c "pre-commit --version | grep '4.5.1'" diff --git a/tools/tool_installer.py b/tools/tool_installer.py new file mode 100644 index 0000000..a5f1846 --- /dev/null +++ b/tools/tool_installer.py @@ -0,0 +1,249 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +"""Install pinned tools from the `tools/lockfiles/*.lock.json` catalog. + +Dependency-free (stdlib only) so devcontainer feature installers can use it +without extra packages. + +Usage: + python3 tool_installer.py install shellcheck yamlfmt + python3 tool_installer.py version shellcheck +""" + +# pyright: reportAny=false, reportUnusedCallResult=false, reportExplicitAny=false + +from __future__ import annotations + +import argparse +import hashlib +import json +import platform +import shutil +import sys +import tarfile +import tempfile +import urllib.request +import zipfile +from pathlib import Path +from typing import NotRequired, TypedDict + + +class Binary(TypedDict): + """A single binary entry from a tool's lockfile definition.""" + + os: str + cpu: str + kind: str + url: str + sha256: str + type: NotRequired[str] + file: NotRequired[str] + + +class ToolData(TypedDict): + """Tool metadata from a lockfile entry.""" + + version: NotRequired[str] + binaries: list[Binary] + + +LOCKFILE_ROOT = Path(__file__).resolve().parent / "lockfiles" + + +def _detect_os() -> str: + """Map Python's platform string to the lockfile schema's OS names.""" + system = platform.system() + if system == "Linux": + return "linux" + if system == "Darwin": + return "macos" + raise SystemExit(f"Unsupported OS: {system}") + + +def _detect_cpu() -> str: + """Map Python's machine string to the lockfile schema's CPU names.""" + machine = platform.machine().lower() + if machine in {"x86_64", "amd64"}: + return "x86_64" + if machine in {"arm64", "aarch64"}: + return "arm64" + raise SystemExit(f"Unsupported CPU architecture: {machine}") + + +def _lockfile_path(lockfile: str) -> Path: + """Resolve a lockfile basename like `ruff` to `ruff.lock.json`.""" + return LOCKFILE_ROOT / f"{lockfile}.lock.json" + + +def _load_tool(lockfile: str, tool: str) -> ToolData: + """Load one tool entry from a lockfile and fail with a clear message.""" + with _lockfile_path(lockfile).open(encoding="utf-8") as handle: + data = json.load(handle) + + try: + return data[tool] + except KeyError as exc: + raise SystemExit( + f"Tool '{tool}' not found in lockfile '{lockfile}.lock.json'", + ) from exc + + +def _find_lockfile(tool: str) -> str: + """Find the lockfile basename that declares a tool.""" + for path in sorted(LOCKFILE_ROOT.glob("*.lock.json")): + with path.open(encoding="utf-8") as handle: + data = json.load(handle) + if tool in data: + return path.name.removesuffix(".lock.json") + + raise SystemExit(f"Tool '{tool}' not found in lockfile catalog") + + +def _resolve_lockfile(tool: str, lockfile: str | None = None) -> str: + """Return the lockfile basename for *tool*, auto-detecting when needed.""" + if lockfile is not None: + return lockfile + if _lockfile_path(tool).exists(): + return tool + return _find_lockfile(tool) + + +def _select_binary(tool_data: ToolData, os_name: str, cpu: str) -> Binary: + """Pick the binary entry matching the requested platform.""" + for binary in tool_data["binaries"]: + if binary["os"] == os_name and binary["cpu"] == cpu: + return binary + + raise SystemExit( + f"No binary defined for os={os_name!r}, cpu={cpu!r}", + ) + + +def _cmd_version(args: argparse.Namespace) -> int: + """Print the declared version for one tool.""" + args.lockfile = _resolve_lockfile(args.tool, args.lockfile) + tool_data = _load_tool(args.lockfile, args.tool) + version = tool_data.get("version") + if version is None: + raise SystemExit( + f"Tool '{args.tool}' in '{args.lockfile}.lock.json' does not define a version", + ) + print(version) + return 0 + + +def _place_binary(source: Path, destination: Path) -> None: + """Copy a file to its destination with executable permissions.""" + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(source, destination) + destination.chmod(0o755) + + +def _extract_member( + binary: Binary, archive_path: Path, out_path: Path, tool: str +) -> None: + """Extract one member from an archive and write it to *out_path*.""" + archive_type = binary.get("type", "") + member = binary.get("file") + if member is None: + raise SystemExit(f"Binary entry for {tool} does not define 'file' field") + + if archive_type in ("tar.gz", "tgz", "tar.xz", "txz"): + with tarfile.open(archive_path) as tf: + reader = tf.extractfile(member) + if reader is None: + raise SystemExit(f"Cannot extract '{member}' from archive for {tool}") + out_path.write_bytes(reader.read()) + elif archive_type == "zip": + with zipfile.ZipFile(archive_path) as zf: + out_path.write_bytes(zf.read(member)) + else: + raise SystemExit(f"Unsupported archive type '{archive_type}' for {tool}") + + +def _cmd_install(args: argparse.Namespace) -> int: + """Download, verify, and install tools from the lockfile catalog.""" + dest_dir = Path(args.destination) + + for tool in args.tools: + lockfile = _resolve_lockfile(tool, args.lockfile) + tool_data = _load_tool(lockfile, tool) + binary = _select_binary(tool_data, args.os, args.cpu) + + kind = binary["kind"] + url = binary["url"] + expected_sha256 = binary["sha256"] + destination = dest_dir / tool + + with tempfile.TemporaryDirectory() as tmp: + tmp = Path(tmp) + download = tmp / "download" + + urllib.request.urlretrieve(url, download) + + actual = hashlib.sha256(download.read_bytes()).hexdigest() + if actual != expected_sha256: + raise SystemExit( + f"Checksum mismatch for {tool}: " + + f"expected {expected_sha256}, got {actual}" + ) + + if kind == "file": + _place_binary(download, destination) + elif kind == "archive": + extracted = tmp / "extracted" + _extract_member(binary, download, extracted, tool) + if extracted.exists(): + _place_binary(extracted, destination) + else: + raise SystemExit(f"Unsupported kind '{kind}' for {tool}") + + return 0 + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Install pinned tools from multitool-compatible lockfiles.", + ) + subparsers = parser.add_subparsers(dest="command", required=True) + + install_parser = subparsers.add_parser( + "install", + help="Download, verify, and install tools.", + ) + install_parser.add_argument("tools", nargs="+") + install_parser.add_argument("--lockfile") + install_parser.add_argument("--destination", default="/usr/local/bin") + install_parser.add_argument("--os", default=_detect_os()) + install_parser.add_argument("--cpu", default=_detect_cpu()) + install_parser.set_defaults(func=_cmd_install) + + version_parser = subparsers.add_parser( + "version", + help="Print the declared version for a tool.", + ) + version_parser.add_argument("tool") + version_parser.add_argument("--lockfile") + version_parser.set_defaults(func=_cmd_version) + + return parser + + +def main(argv: list[str] | None = None) -> int: + parser = _build_parser() + args = parser.parse_args(argv) + return args.func(args) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/tool_lockfile_helpers.sh b/tools/tool_lockfile_helpers.sh deleted file mode 100644 index 18e5d52..0000000 --- a/tools/tool_lockfile_helpers.sh +++ /dev/null @@ -1,182 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -# shellcheck shell=bash - -set -euo pipefail - -# CLI installer around the `tools/lockfiles/*.lock.json` catalog. -# -# This script delegates JSON parsing and platform selection to -# `tool_lockfile_query.py`, then adds the shell logic needed for installation. -# Public usage is through the CLI: -# bash ./tool_lockfile_helpers.sh install shellcheck yamlfmt -# bash ./tool_lockfile_helpers.sh version shellcheck - -# Resolve the helper location once when the script starts. This keeps the -# script self-contained: as long as the `.sh`, `.py`, and `lockfiles/` -# directory stay together, callers do not need to pass any path configuration. -SCORE_TOOL_HELPERS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" -readonly SCORE_TOOL_HELPERS_DIR -readonly SCORE_TOOL_LOCKFILE_QUERY_PY="${SCORE_TOOL_HELPERS_DIR}/tool_lockfile_query.py" - -_score_run_tool_lockfile_query() { - # Use the Python helper for all JSON parsing and platform selection. - python3 "${SCORE_TOOL_LOCKFILE_QUERY_PY}" "$@" -} - -_score_tool_lockfile_name() { - # Print the lockfile basename that declares the requested tool. - local tool_name="$1" - - _score_run_tool_lockfile_query lockfile --tool "${tool_name}" -} - -score_tool_version() { - # Print the declared version for one tool entry. - local tool_name="$1" - local lockfile_name="${2:-}" - - if [[ -z "${lockfile_name}" ]]; then - lockfile_name="$(_score_tool_lockfile_name "${tool_name}")" - fi - - _score_run_tool_lockfile_query \ - version \ - --lockfile "${lockfile_name}" \ - --tool "${tool_name}" -} - -score_install_tool_from_lockfile() { - # Download, verify, extract if needed, and install one lockfile-defined - # tool. The lockfile tells us which URL, checksum, archive type, and - # in-archive path belong to the current platform. - local tool_name="$1" - local lockfile_name="${2:-}" - local destination="${3:-/usr/local/bin/${tool_name}}" - - if [[ -z "${lockfile_name}" ]]; then - lockfile_name="$(_score_tool_lockfile_name "${tool_name}")" - fi - - local kind="" - local url="" - local sha256="" - local file="" - local archive_type="" - - # Convert the Python helper's `key=value` output into normal shell - # variables. We keep the mapping explicit so it is obvious which pieces of - # metadata the installer consumes. - while IFS='=' read -r key value; do - case "${key}" in - kind) kind="${value}" ;; - url) url="${value}" ;; - sha256) sha256="${value}" ;; - file) file="${value}" ;; - type) archive_type="${value}" ;; - *) ;; - esac - done < <( - _score_run_tool_lockfile_query \ - env \ - --lockfile "${lockfile_name}" \ - --tool "${tool_name}" - ) - - if [[ -z "${kind}" || -z "${url}" || -z "${sha256}" ]]; then - echo "Incomplete lockfile metadata for ${tool_name}" >&2 - return 1 - fi - - # Work in a temporary directory so partially downloaded archives do not - # pollute the filesystem and cleanup is automatic on return. - local temp_dir - temp_dir="$(mktemp -d)" - trap 'rm -rf "${temp_dir}"; trap - RETURN' RETURN - - local download_path="${temp_dir}/download" - - # Always verify the checksum before we execute or unpack anything. - curl -fsSL "${url}" -o "${download_path}" - echo "${sha256} ${download_path}" | sha256sum -c - >/dev/null - - case "${kind}" in - file) - # Simple case: the downloaded file is the final executable. - install -D -m 0755 "${download_path}" "${destination}" - ;; - archive) - case "${archive_type}" in - tar.gz|tgz) - # Extract only the requested member from the tarball. - tar -xzf "${download_path}" -C "${temp_dir}" "${file}" - install -D -m 0755 "${temp_dir}/${file}" "${destination}" - ;; - tar.xz|txz) - # Same as above, but for xz-compressed tarballs. - tar -xJf "${download_path}" -C "${temp_dir}" "${file}" - install -D -m 0755 "${temp_dir}/${file}" "${destination}" - ;; - zip) - # Use Python's standard-library zip support. - # This avoids an additional `unzip` package dependency. - python3 -m zipfile -e "${download_path}" "${temp_dir}" >/dev/null - install -D -m 0755 "${temp_dir}/${file}" "${destination}" - ;; - deb|.deb) - # Debian packages already describe their installation - # layout, so we let `apt-get` handle unpacking and file - # placement. - apt-get install -y --no-install-recommends --fix-broken "${download_path}" - ;; - *) - echo "Unsupported archive type '${archive_type}' for ${tool_name}" >&2 - return 1 - ;; - esac - ;; - *) - echo "Unsupported lockfile kind '${kind}' for ${tool_name}" >&2 - return 1 - ;; - esac -} - -if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then - command="${1:-}" - shift || true - - case "${command}" in - install) - if [[ "$#" -eq 0 ]]; then - echo "Usage: $0 install [tool...]" >&2 - exit 2 - fi - for tool_name in "$@"; do - score_install_tool_from_lockfile "${tool_name}" - done - ;; - version) - if [[ "$#" -lt 1 || "$#" -gt 2 ]]; then - echo "Usage: $0 version [lockfile]" >&2 - exit 2 - fi - score_tool_version "$@" - ;; - *) - echo "Usage: $0 install [tool...] | version [lockfile]" >&2 - exit 2 - ;; - esac -fi diff --git a/tools/tool_lockfile_query.py b/tools/tool_lockfile_query.py deleted file mode 100644 index ccb9d01..0000000 --- a/tools/tool_lockfile_query.py +++ /dev/null @@ -1,184 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* -"""Query tool metadata from the shared `tools/lockfiles/*.lock.json` catalog. - -This script is intentionally small and dependency-free so shell-based -devcontainer feature installers and tests can ask three focused questions without -re-implementing JSON parsing or platform selection logic: - -1. "What is the declared version for tool X?" -2. "Which binary metadata applies to tool X on this OS/CPU?" -3. "Which lockfile declares tool X?" - -The shell helper `tool_lockfile_helpers.sh` wraps this script for everyday use. -""" - -from __future__ import annotations - -import argparse -import json -import platform -import sys -from pathlib import Path - - -LOCKFILE_ROOT = Path(__file__).resolve().parent / "lockfiles" - - -def _detect_os() -> str: - """Map Python's platform string to the lockfile schema's OS names.""" - system = platform.system() - if system == "Linux": - return "linux" - if system == "Darwin": - return "macos" - raise SystemExit(f"Unsupported OS: {system}") - - -def _detect_cpu() -> str: - """Map Python's machine string to the lockfile schema's CPU names.""" - machine = platform.machine().lower() - if machine in {"x86_64", "amd64"}: - return "x86_64" - if machine in {"arm64", "aarch64"}: - return "arm64" - raise SystemExit(f"Unsupported CPU architecture: {machine}") - - -def _lockfile_path(lockfile: str) -> Path: - """Resolve a lockfile basename like `ruff` to `ruff.lock.json`.""" - return LOCKFILE_ROOT / f"{lockfile}.lock.json" - - -def _load_tool(lockfile: str, tool: str) -> dict: - """Load one tool entry from a lockfile and fail with a clear message.""" - with _lockfile_path(lockfile).open(encoding="utf-8") as handle: - data = json.load(handle) - - try: - return data[tool] - except KeyError as exc: - raise SystemExit( - f"Tool '{tool}' not found in lockfile '{lockfile}.lock.json'", - ) from exc - - -def _find_lockfile(tool: str) -> str: - """Find the lockfile basename that declares a tool.""" - for path in sorted(LOCKFILE_ROOT.glob("*.lock.json")): - with path.open(encoding="utf-8") as handle: - data = json.load(handle) - if tool in data: - return path.name.removesuffix(".lock.json") - - raise SystemExit(f"Tool '{tool}' not found in lockfile catalog") - - -def _select_binary(tool_data: dict, os_name: str, cpu: str) -> dict: - """Pick the binary entry matching the requested platform.""" - for binary in tool_data["binaries"]: - if binary["os"] == os_name and binary["cpu"] == cpu: - return binary - - raise SystemExit( - f"No binary defined for os={os_name!r}, cpu={cpu!r}", - ) - - -def _cmd_version(args: argparse.Namespace) -> int: - """Print the declared version for one tool.""" - tool_data = _load_tool(args.lockfile, args.tool) - version = tool_data.get("version") - if version is None: - raise SystemExit( - f"Tool '{args.tool}' in '{args.lockfile}.lock.json' does not define a version", - ) - print(version) - return 0 - - -def _cmd_lockfile(args: argparse.Namespace) -> int: - """Print the lockfile basename that declares one tool.""" - print(_find_lockfile(args.tool)) - return 0 - - -def _cmd_env(args: argparse.Namespace) -> int: - """Print selected binary metadata as `key=value` lines for shell callers.""" - tool_data = _load_tool(args.lockfile, args.tool) - binary = _select_binary(tool_data, args.os, args.cpu) - - output = {} - if "version" in tool_data: - output["version"] = tool_data["version"] - for key in ("kind", "url", "sha256", "file", "type", "os", "cpu"): - if key in binary: - output[key] = binary[key] - - for key, value in output.items(): - print(f"{key}={value}") - return 0 - - -def _build_parser() -> argparse.ArgumentParser: - """Define a tiny CLI for version and platform-specific metadata lookups.""" - parser = argparse.ArgumentParser( - description="Read tool metadata from multitool-compatible lockfiles.", - ) - subparsers = parser.add_subparsers(dest="command", required=True) - - lockfile_parser = subparsers.add_parser( - "lockfile", - help="Print the lockfile basename declaring a tool.", - ) - lockfile_parser.add_argument("--tool", required=True) - lockfile_parser.set_defaults(func=_cmd_lockfile) - - version_parser = subparsers.add_parser( - "version", - help="Print the version for a tool from a lockfile.", - ) - version_parser.add_argument("--tool", required=True) - version_parser.add_argument( - "--lockfile", - help="Lockfile basename without .lock.json, defaults to the tool name", - ) - version_parser.set_defaults(func=_cmd_version) - - env_parser = subparsers.add_parser( - "env", - help="Print selected binary metadata as shell-style key=value lines.", - ) - env_parser.add_argument("--tool", required=True) - env_parser.add_argument( - "--lockfile", - help="Lockfile basename without .lock.json, defaults to the tool name", - ) - env_parser.add_argument("--os", default=_detect_os()) - env_parser.add_argument("--cpu", default=_detect_cpu()) - env_parser.set_defaults(func=_cmd_env) - - return parser - - -def main(argv: list[str] | None = None) -> int: - """Parse arguments, fill in default lockfile names, and dispatch.""" - parser = _build_parser() - args = parser.parse_args(argv) - if hasattr(args, "lockfile") and args.lockfile is None: - args.lockfile = args.tool - return args.func(args) - - -if __name__ == "__main__": - sys.exit(main()) From 4c9cb1e90f24aac8cb0c466010a679cfbdfa5e03 Mon Sep 17 00:00:00 2001 From: Alexander Lanin Date: Mon, 27 Apr 2026 22:40:08 +0200 Subject: [PATCH 11/12] move shellcheck config --- .pre-commit-config.yaml | 1 - .shellcheckrc | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 196cadb..9a33895 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,7 +34,6 @@ repos: name: shellcheck entry: tools/run_tool.sh shellcheck language: system - args: [-e, SC1091] types: [shell] - repo: https://github.com/eclipse-score/tooling rev: 31ff8eee214e4e97ef8f5cb46e443273515b63ec diff --git a/.shellcheckrc b/.shellcheckrc index 2d48e04..5424a52 100644 --- a/.shellcheckrc +++ b/.shellcheckrc @@ -24,3 +24,6 @@ disable=SC2046 # optional checks, fixes might not be easy and better not break the code disable=SC2292,SC2154,SC2312 + +# SC1091: Not following sourced files that are not specified as input +disable=SC1091 From ca83ed2354b6fffa3802a387d841d4b89cafa4a8 Mon Sep 17 00:00:00 2001 From: Alexander Lanin Date: Mon, 27 Apr 2026 22:52:10 +0200 Subject: [PATCH 12/12] make tool_installer executable --- .devcontainer/post_create_command.sh | 2 +- .../.devcontainer/bazel-feature/install.sh | 2 +- .../bazel-feature/tests/test_default.sh | 6 +++--- .../.devcontainer/s-core-local/install.sh | 2 +- .../.devcontainer/s-core-local/tests/test_default.sh | 12 ++++++------ tools/tool_installer.py | 5 +++-- 6 files changed, 15 insertions(+), 14 deletions(-) mode change 100644 => 100755 tools/tool_installer.py diff --git a/.devcontainer/post_create_command.sh b/.devcontainer/post_create_command.sh index 72ab6bd..6101a47 100755 --- a/.devcontainer/post_create_command.sh +++ b/.devcontainer/post_create_command.sh @@ -18,7 +18,7 @@ npm install -g @devcontainers/cli SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" REPOSITORY_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd -P)" -sudo python3 "${REPOSITORY_ROOT}/tools/tool_installer.py" install shellcheck yamlfmt +sudo "${REPOSITORY_ROOT}/tools/tool_installer.py" install shellcheck yamlfmt pre-commit install diff --git a/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh b/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh index c8e67a5..fca90c3 100755 --- a/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh +++ b/src/s-core-devcontainer/.devcontainer/bazel-feature/install.sh @@ -40,7 +40,7 @@ apt-get update apt-get install apt-transport-https -y # Lockfile-managed Bazel tooling -python3 /usr/local/share/score-tools/tool_installer.py install bazelisk buildifier starpls +/usr/local/share/score-tools/tool_installer.py install bazelisk buildifier starpls # Bazelisk + Bazel ln -sf /usr/local/bin/bazelisk /usr/local/bin/bazel diff --git a/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh b/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh index 94db4a8..bb85e25 100755 --- a/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh +++ b/src/s-core-devcontainer/.devcontainer/bazel-feature/tests/test_default.sh @@ -18,9 +18,9 @@ set -euo pipefail # Read tool versions + metadata into environment variables . /usr/local/share/score-tools/versions.sh /devcontainer/features/bazel/versions.yaml -bazelisk_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version bazelisk)" -buildifier_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version buildifier)" -starpls_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version starpls)" +bazelisk_lockfile_version="$(/usr/local/share/score-tools/tool_installer.py version bazelisk)" +buildifier_lockfile_version="$(/usr/local/share/score-tools/tool_installer.py version buildifier)" +starpls_lockfile_version="$(/usr/local/share/score-tools/tool_installer.py version starpls)" # Bazel-related tools ## This is the bazel version preinstalled in the devcontainer. ## A solid test would disable the network interface first to prevent a different version from being downloaded, diff --git a/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh b/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh index 51bda1b..ff41759 100755 --- a/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh +++ b/src/s-core-devcontainer/.devcontainer/s-core-local/install.sh @@ -58,7 +58,7 @@ apt-get install -y "python${python_version}" python3-pip python3-venv apt-get install -y flake8 python3-autopep8 black python3-yapf mypy pydocstyle pycodestyle bandit pipenv virtualenv pylint # Lockfile-managed local developer tools -python3 /usr/local/share/score-tools/tool_installer.py install shellcheck ruff actionlint yamlfmt uv uvx +/usr/local/share/score-tools/tool_installer.py install shellcheck ruff actionlint yamlfmt uv uvx # GraphViz # The Ubuntu Noble package of GraphViz diff --git a/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh b/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh index 1a61d28..1042838 100755 --- a/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh +++ b/src/s-core-devcontainer/.devcontainer/s-core-local/tests/test_default.sh @@ -21,12 +21,12 @@ KERNEL=$(uname -s) # Read tool versions + metadata into environment variables . /usr/local/share/score-tools/versions.sh /devcontainer/features/s-core-local/versions.yaml -shellcheck_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version shellcheck)" -ruff_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version ruff)" -actionlint_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version actionlint)" -yamlfmt_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version yamlfmt)" -uv_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version uv)" -uvx_lockfile_version="$(python3 /usr/local/share/score-tools/tool_installer.py version uvx)" +shellcheck_lockfile_version="$(/usr/local/share/score-tools/tool_installer.py version shellcheck)" +ruff_lockfile_version="$(/usr/local/share/score-tools/tool_installer.py version ruff)" +actionlint_lockfile_version="$(/usr/local/share/score-tools/tool_installer.py version actionlint)" +yamlfmt_lockfile_version="$(/usr/local/share/score-tools/tool_installer.py version yamlfmt)" +uv_lockfile_version="$(/usr/local/share/score-tools/tool_installer.py version uv)" +uvx_lockfile_version="$(/usr/local/share/score-tools/tool_installer.py version uvx)" # pre-commit, it is available via $PATH in login shells, but not in non-login shells check "validate pre-commit is working and has the correct version" bash -c "pre-commit --version | grep '4.5.1'" diff --git a/tools/tool_installer.py b/tools/tool_installer.py old mode 100644 new mode 100755 index a5f1846..f660f24 --- a/tools/tool_installer.py +++ b/tools/tool_installer.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # ******************************************************************************* # Copyright (c) 2026 Contributors to the Eclipse Foundation # @@ -16,8 +17,8 @@ without extra packages. Usage: - python3 tool_installer.py install shellcheck yamlfmt - python3 tool_installer.py version shellcheck + tool_installer.py install shellcheck yamlfmt + tool_installer.py version shellcheck """ # pyright: reportAny=false, reportUnusedCallResult=false, reportExplicitAny=false