diff --git a/.devcontainer/post_create_command.sh b/.devcontainer/post_create_command.sh index 6101a47..8415894 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 "${REPOSITORY_ROOT}/tools/tool_installer.py" install shellcheck yamlfmt +sudo "${REPOSITORY_ROOT}/tools/tool_installer.py" install shellcheck yamlfmt apm pre-commit install diff --git a/MODULE.bazel b/MODULE.bazel index 913a6ae..f81ba10 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -25,6 +25,7 @@ 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") +multitool.hub(lockfile = "//tools:lockfiles/apm.lock.json") use_repo(multitool, "multitool") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index f040569..b30b3b3 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,5 +1,5 @@ { - "lockFileVersion": 24, + "lockFileVersion": 28, "registryFileHashes": { "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", @@ -147,7 +147,7 @@ "moduleExtensions": { "@@buildifier_prebuilt+//:defs.bzl%buildifier_prebuilt_deps_extension": { "general": { - "bzlTransitiveDigest": "UuPPKtgkayflJh3xnoDcElnjA0xyLYaim0cMSFV12v8=", + "bzlTransitiveDigest": "cakPvHT3/EvT2emakoEFNqXxi8AaaHBg/Tf0+4xhbno=", "usagesDigest": "eWMDBEn8E8CrwAPXrlrjIap2pseSMhxDyDdrntHBOOE=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -284,126 +284,9 @@ ] } }, - "@@pybind11_bazel+//:python_configure.bzl%extension": { - "general": { - "bzlTransitiveDigest": "D2/qWHU6yQFwRG7Bb+caqrYMha5avsASao2vERrxK24=", - "usagesDigest": "fycyB39YnXIJkfWCIXLUKJMZzANcuLy9ZE73hRucjFk=", - "recordedFileInputs": { - "@@pybind11_bazel+//MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e" - }, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "local_config_python": { - "repoRuleId": "@@pybind11_bazel+//:python_configure.bzl%python_configure", - "attributes": {} - }, - "pybind11": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file": "@@pybind11_bazel+//:pybind11.BUILD", - "strip_prefix": "pybind11-2.11.1", - "urls": [ - "https://github.com/pybind/pybind11/archive/v2.11.1.zip" - ] - } - } - }, - "recordedRepoMappingEntries": [ - [ - "pybind11_bazel+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, - "@@rules_fuzzing+//fuzzing/private:extensions.bzl%non_module_dependencies": { - "general": { - "bzlTransitiveDigest": "4LouzhF/yT117s7peGnNs9ROomiJXC6Zl5R0oI21jho=", - "usagesDigest": "wy6ISK6UOcBEjj/mvJ/S3WeXoO67X+1llb9yPyFtPgc=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "platforms": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz", - "https://github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz" - ], - "sha256": "8150406605389ececb6da07cbcb509d5637a3ab9a24bc69b1101531367d89d74" - } - }, - "rules_python": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "sha256": "d70cd72a7a4880f0000a6346253414825c19cdd40a28289bdf67b8e6480edff8", - "strip_prefix": "rules_python-0.28.0", - "url": "https://github.com/bazelbuild/rules_python/releases/download/0.28.0/rules_python-0.28.0.tar.gz" - } - }, - "bazel_skylib": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "sha256": "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94", - "urls": [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz", - "https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz" - ] - } - }, - "com_google_absl": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/abseil/abseil-cpp/archive/refs/tags/20240116.1.zip" - ], - "strip_prefix": "abseil-cpp-20240116.1", - "integrity": "sha256-7capMWOvWyoYbUaHF/b+I2U6XLMaHmky8KugWvfXYuk=" - } - }, - "rules_fuzzing_oss_fuzz": { - "repoRuleId": "@@rules_fuzzing+//fuzzing/private/oss_fuzz:repository.bzl%oss_fuzz_repository", - "attributes": {} - }, - "honggfuzz": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file": "@@rules_fuzzing+//:honggfuzz.BUILD", - "sha256": "6b18ba13bc1f36b7b950c72d80f19ea67fbadc0ac0bb297ec89ad91f2eaa423e", - "url": "https://github.com/google/honggfuzz/archive/2.5.zip", - "strip_prefix": "honggfuzz-2.5" - } - }, - "rules_fuzzing_jazzer": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", - "attributes": { - "sha256": "ee6feb569d88962d59cb59e8a31eb9d007c82683f3ebc64955fd5b96f277eec2", - "url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer/0.20.1/jazzer-0.20.1.jar" - } - }, - "rules_fuzzing_jazzer_api": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", - "attributes": { - "sha256": "f5a60242bc408f7fa20fccf10d6c5c5ea1fcb3c6f44642fec5af88373ae7aa1b", - "url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer-api/0.20.1/jazzer-api-0.20.1.jar" - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_fuzzing+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "nvW/NrBXlAmiQw99EMGKkLaD2KbNp2mQDlxdfpr+0Ls=", + "bzlTransitiveDigest": "+Kp6j204mBZ3mxlIDDR0gBoP45BZ4jYRhRAcB8sU0qc=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -467,18 +350,26 @@ }, "@@rules_python+//python/private/pypi:pip.bzl%pip_internal": { "general": { - "bzlTransitiveDigest": "PBWvQk1Ig1DQmk3kBkz1gxjJTZTLiYJhnBx9coVSz8w=", - "usagesDigest": "OLoIStnzNObNalKEMRq99FqenhPGLFZ5utVLV4sz7OI=", - "recordedFileInputs": { - "@@rules_python+//tools/publish/requirements_darwin.txt": "2994136eab7e57b083c3de76faf46f70fad130bc8e7360a7fed2b288b69e79dc", - "@@rules_python+//tools/publish/requirements_linux.txt": "8175b4c8df50ae2f22d1706961884beeb54e7da27bd2447018314a175981997d", - "@@rules_python+//tools/publish/requirements_windows.txt": "7673adc71dc1a81d3661b90924d7a7c0fc998cd508b3cb4174337cef3f2de556" - }, - "recordedDirentsInputs": {}, - "envVariables": { - "RULES_PYTHON_REPO_DEBUG": null, - "RULES_PYTHON_REPO_DEBUG_VERBOSITY": null - }, + "bzlTransitiveDigest": "dzD8Q2YmrP3fz8saWLHPmlwPLO91ImtTmP/c9JKTStM=", + "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_publish_deps_311_backports_tarfile_py3_none_any_77e284d7": { "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", @@ -2960,5 +2851,6 @@ } } }, - "facts": {} + "facts": {}, + "factsVersions": {} } 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 17aff46..7a38f21 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 -/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 apm # 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 2727f66..0c41974 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 @@ -27,6 +27,7 @@ actionlint_lockfile_version="$(/usr/local/share/score-tools/tool_installer.py ve 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)" +apm_lockfile_version="$(/usr/local/share/score-tools/tool_installer.py version apm)" # 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'" @@ -71,6 +72,9 @@ check "validate actionlint is working and has the correct version" bash -c "acti # yamlfmt check "validate yamlfmt is working and has the correct version" bash -c "yamlfmt --version | grep '${yamlfmt_lockfile_version}'" +# apm +check "validate apm is working and has the correct version" bash -c "apm --version | grep '${apm_lockfile_version}'" + # uv 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}'" 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 c9ab8ec..f60ade4 100644 --- a/src/s-core-devcontainer/.devcontainer/s-core-local/versions.yaml +++ b/src/s-core-devcontainer/.devcontainer/s-core-local/versions.yaml @@ -51,3 +51,5 @@ ripgrep: version: 14.1.0 lcov: version: 2.0 +apm: + version: 0.27.0 diff --git a/tools/lockfiles/apm.lock.json b/tools/lockfiles/apm.lock.json new file mode 100644 index 0000000..f105791 --- /dev/null +++ b/tools/lockfiles/apm.lock.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://raw.githubusercontent.com/bazel-contrib/rules_multitool/main/lockfile.schema.json", + "apm": { + "version": "0.27.0", + "binaries": [ + { + "kind": "archive-dir", + "dir": "apm-darwin-arm64", + "url": "https://github.com/microsoft/apm/releases/download/v0.27.0/apm-darwin-arm64.tar.gz", + "sha256": "4c68e5eaa3cfdb0b25734c316deb532835eaf3c3e2f7379a4c7c06918043a641", + "type": "tar.gz", + "os": "macos", + "cpu": "arm64" + }, + { + "kind": "archive-dir", + "dir": "apm-darwin-x86_64", + "url": "https://github.com/microsoft/apm/releases/download/v0.27.0/apm-darwin-x86_64.tar.gz", + "sha256": "846b30055d96cbc6fa0fcf451f50d13f632b540ffdff344873a025bba607e25a", + "type": "tar.gz", + "os": "macos", + "cpu": "x86_64" + }, + { + "kind": "archive-dir", + "dir": "apm-linux-x86_64", + "url": "https://github.com/microsoft/apm/releases/download/v0.27.0/apm-linux-x86_64.tar.gz", + "sha256": "be2d8a97ca8816636117ec26da85482d647ae3353213ea022fb1130c2dd3d3b0", + "type": "tar.gz", + "os": "linux", + "cpu": "x86_64" + }, + { + "kind": "archive-dir", + "dir": "apm-linux-arm64", + "url": "https://github.com/microsoft/apm/releases/download/v0.27.0/apm-linux-arm64.tar.gz", + "sha256": "7df6e64ca9540665367f07af0226077ba92820f6cc759c10a5ca37e038a500e4", + "type": "tar.gz", + "os": "linux", + "cpu": "arm64" + } + ] + } +} diff --git a/tools/tool_installer.py b/tools/tool_installer.py index f660f24..a3066ea 100755 --- a/tools/tool_installer.py +++ b/tools/tool_installer.py @@ -49,6 +49,7 @@ class Binary(TypedDict): sha256: str type: NotRequired[str] file: NotRequired[str] + dir: NotRequired[str] class ToolData(TypedDict): @@ -172,6 +173,38 @@ def _extract_member( raise SystemExit(f"Unsupported archive type '{archive_type}' for {tool}") +def _extract_dir( + binary: Binary, archive_path: Path, out_dir: Path, tool: str +) -> None: + """Extract a directory from a tar archive, stripping the top-level prefix.""" + archive_type = binary.get("type", "") + dir_prefix = binary.get("dir") + if dir_prefix is None: + raise SystemExit(f"Binary entry for {tool} does not define 'dir' field") + prefix = dir_prefix.rstrip("/") + "/" + + if archive_type not in ("tar.gz", "tgz", "tar.xz", "txz"): + raise SystemExit(f"archive-dir only supports tar archives, got '{archive_type}' for {tool}") + + with tarfile.open(archive_path) as tf: + for member in tf.getmembers(): + if not member.name.startswith(prefix): + continue + rel = member.name[len(prefix):] + if not rel: + continue + dest = out_dir / rel + if member.isdir(): + dest.mkdir(parents=True, exist_ok=True) + elif member.isfile(): + dest.parent.mkdir(parents=True, exist_ok=True) + reader = tf.extractfile(member) + if reader is not None: + dest.write_bytes(reader.read()) + if member.mode & 0o111: + dest.chmod(dest.stat().st_mode | 0o111) + + def _cmd_install(args: argparse.Namespace) -> int: """Download, verify, and install tools from the lockfile catalog.""" dest_dir = Path(args.destination) @@ -206,6 +239,12 @@ def _cmd_install(args: argparse.Namespace) -> int: _extract_member(binary, download, extracted, tool) if extracted.exists(): _place_binary(extracted, destination) + elif kind == "archive-dir": + extracted_dir = tmp / "extracted_dir" + extracted_dir.mkdir() + _extract_dir(binary, download, extracted_dir, tool) + shutil.copytree(str(extracted_dir), str(dest_dir), dirs_exist_ok=True) + (dest_dir / tool).chmod(0o755) else: raise SystemExit(f"Unsupported kind '{kind}' for {tool}")