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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/post_create_command.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also needs a test, that indeed the correct version is inside the container. Take shellcheck as example:


pre-commit install

Expand Down
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
158 changes: 25 additions & 133 deletions MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'"
Expand Down Expand Up @@ -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}'"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,5 @@ ripgrep:
version: 14.1.0
lcov:
version: 2.0
apm:
version: 0.27.0
44 changes: 44 additions & 0 deletions tools/lockfiles/apm.lock.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
}
39 changes: 39 additions & 0 deletions tools/tool_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Binary(TypedDict):
sha256: str
type: NotRequired[str]
file: NotRequired[str]
dir: NotRequired[str]


class ToolData(TypedDict):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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}")

Expand Down