From 8beeab514e39bbafc4b1e4281bfff7dd1cc24be9 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 26 Jan 2026 16:08:45 -0800 Subject: [PATCH 01/15] dont install brew or colima-related stuff on linux --- devenv/bootstrap.py | 12 ++++++++---- devenv/constants.py | 1 + devenv/update.py | 13 +++++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/devenv/bootstrap.py b/devenv/bootstrap.py index 0dcbfeb..f552524 100644 --- a/devenv/bootstrap.py +++ b/devenv/bootstrap.py @@ -103,11 +103,15 @@ def main(context: Context, argv: Sequence[str] | None = None) -> ExitCode: """ ) os.makedirs(f"{constants.root}/bin", exist_ok=True) - brew.install() - docker.install_global() + + if not constants.LINUX: + # we don't install brew or colima-related stuff on linux + brew.install() + docker.install_global() + colima.install_global() + limactl.install_global() + direnv.install() - colima.install_global() - limactl.install_global() os.makedirs(context["code_root"], exist_ok=True) diff --git a/devenv/constants.py b/devenv/constants.py index 2f6d247..e13e9b2 100644 --- a/devenv/constants.py +++ b/devenv/constants.py @@ -27,6 +27,7 @@ SYSTEM = platform.system().lower() MACHINE = platform.machine() DARWIN = SYSTEM == "darwin" +LINUX = SYSTEM == "linux" INTEL_MAC = DARWIN and (MACHINE == "x86_64") SHELL_UNSET = "(SHELL unset)" DEBUG = os.getenv("SNTY_DEVENV_DEBUG", os.getenv("DEBUG", "")) diff --git a/devenv/update.py b/devenv/update.py index 556ba01..bf12b66 100644 --- a/devenv/update.py +++ b/devenv/update.py @@ -36,11 +36,16 @@ def main(context: Context, argv: Sequence[str] | None = None) -> int: """ ) os.makedirs(f"{constants.root}/bin", exist_ok=True) - brew.install() - docker.install_global() + + if not constants.LINUX: + # we don't install brew or colima-related stuff on linux + brew.install() + docker.install_global() + colima.install_global() + limactl.install_global() + direnv.install() - colima.install_global() - limactl.install_global() + return 0 is_global_devenv = sys.executable.startswith( From 50cdaaeb170801afa066e9dd1075d4bc27c865f6 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 26 Jan 2026 16:26:21 -0800 Subject: [PATCH 02/15] ref: do not load checks tagged with colima if not on darwin --- devenv/checks/colimaSsh.py | 2 +- devenv/checks/dockerConfig.py | 4 ++-- devenv/checks/dockerDesktop.py | 6 +++--- devenv/checks/limaDns.py | 2 +- devenv/doctor.py | 29 ++++++++++++++++++++++++----- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/devenv/checks/colimaSsh.py b/devenv/checks/colimaSsh.py index 8a8ea05..e3c9bef 100644 --- a/devenv/checks/colimaSsh.py +++ b/devenv/checks/colimaSsh.py @@ -8,7 +8,7 @@ from devenv.lib_check.types import checker from devenv.lib_check.types import fixer -tags: set[str] = {"builtin"} +tags: set[str] = {"builtin", "colima"} name = "colima ssh credentials should only be owner rw" diff --git a/devenv/checks/dockerConfig.py b/devenv/checks/dockerConfig.py index e9cdd13..5c66318 100644 --- a/devenv/checks/dockerConfig.py +++ b/devenv/checks/dockerConfig.py @@ -7,8 +7,8 @@ from devenv.lib_check.types import checker from devenv.lib_check.types import fixer -tags: set[str] = {"builtin"} -name = "correct docker configuration" +tags: set[str] = {"builtin", "colima"} +name = "correct docker configuration for colima" @checker diff --git a/devenv/checks/dockerDesktop.py b/devenv/checks/dockerDesktop.py index 868e8b4..c144895 100644 --- a/devenv/checks/dockerDesktop.py +++ b/devenv/checks/dockerDesktop.py @@ -6,8 +6,8 @@ from devenv.lib_check.types import checker from devenv.lib_check.types import fixer -tags: set[str] = {"builtin"} -name = "docker desktop shouldn't be running" +tags: set[str] = {"builtin", "colima"} +name = "docker desktop shouldn't be running if you're trying to use colima" def docker_desktop_is_running() -> bool: @@ -20,7 +20,7 @@ def check() -> tuple[bool, str]: if docker_desktop_is_running(): return ( False, - "Docker Desktop is running. We don't support it, and it conflicts with colima.", + "Docker Desktop is running. It cannot be running at the same time as colima.", ) return True, "" diff --git a/devenv/checks/limaDns.py b/devenv/checks/limaDns.py index b2b6dfc..190fe91 100644 --- a/devenv/checks/limaDns.py +++ b/devenv/checks/limaDns.py @@ -7,7 +7,7 @@ from devenv.lib_check.types import checker from devenv.lib_check.types import fixer -tags: set[str] = {"builtin"} +tags: set[str] = {"builtin", "colima"} name = "colima's DNS isn't working" diff --git a/devenv/doctor.py b/devenv/doctor.py index ab7f101..4f2c9e0 100644 --- a/devenv/doctor.py +++ b/devenv/doctor.py @@ -14,6 +14,7 @@ from typing import List import devenv.checks +from devenv import constants from devenv.lib.context import Context from devenv.lib.modules import DevModuleInfo from devenv.lib.repository import Repository @@ -78,7 +79,9 @@ def __init__(self, module: ModuleType): super().__init__() -def load_checks(repo: Repository, match_tags: set[str]) -> List[Check]: +def load_checks( + repo: Repository, match_tags: set[str], exclude_tags: set[str] +) -> List[Check]: """ Load all checks from the checks directory. Optionally filter by tags. @@ -104,11 +107,15 @@ def load_checks(repo: Repository, match_tags: set[str]) -> List[Check]: continue if match_tags and not check.tags.issuperset(match_tags): continue + if check.tags & exclude_tags: + continue checks.append(check) return checks -def load_builtin_checks(match_tags: set[str]) -> List[Check]: +def load_builtin_checks( + match_tags: set[str], exclude_tags: set[str] +) -> List[Check]: """ Loads builtin checks. Optionally filter by tags. @@ -126,6 +133,8 @@ def load_builtin_checks(match_tags: set[str]) -> List[Check]: continue if match_tags and not check.tags.issuperset(match_tags): continue + if check.tags & exclude_tags: + continue checks.append(check) return checks @@ -193,23 +202,33 @@ def main(context: Context, argv: Sequence[str] | None = None) -> int: action="append", help="Used to match a subset of checks.", ) + parser.add_argument( + "--exclude-tag", + type=str, + action="append", + help="Used to unmatch checks.", + ) parser.add_argument( "--check-only", action="store_true", help="Do not run fixers." ) args = parser.parse_args(argv) match_tags: set[str] = set(args.tag if args.tag else ()) + exclude_tags: set[str] = set(args.exclude_tag if args.exclude_tag else ()) + + if not constants.DARWIN: + exclude_tags.add("colima") # First, we load builtin checks. These are not repo specific. - checks = load_builtin_checks(match_tags) + checks = load_builtin_checks(match_tags, exclude_tags) # Then we load any repo specific checks if any. repo = context.get("repo") if repo is not None: - checks.extend(load_checks(repo, match_tags)) + checks.extend(load_checks(repo, match_tags, exclude_tags)) if not checks: - print(f"No checks found for tags: {args.tag}") + print("No checks found.") return 1 # We run every check on a separate thread, aggregate the results, From aab06d02feafa65232f9372c93777d422d48bd11 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 26 Jan 2026 16:31:24 -0800 Subject: [PATCH 03/15] generic wording --- devenv/checks/diskfree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devenv/checks/diskfree.py b/devenv/checks/diskfree.py index 51e2b70..3d3b3b3 100644 --- a/devenv/checks/diskfree.py +++ b/devenv/checks/diskfree.py @@ -18,7 +18,7 @@ def check() -> tuple[bool, str]: return ( False, f"You have less than 10 GiB disk free ({disk_gib_free} GiB free). " - "You might start to encounter various problems when using colima.", + "You might start to encounter various problems when using docker.", ) return True, "" From f047afec9939455b36e1f01aab5042b9a45867a4 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 26 Jan 2026 16:31:56 -0800 Subject: [PATCH 04/15] fix --- devenv/checks/diskfree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devenv/checks/diskfree.py b/devenv/checks/diskfree.py index 3d3b3b3..8293ad4 100644 --- a/devenv/checks/diskfree.py +++ b/devenv/checks/diskfree.py @@ -14,7 +14,7 @@ def check() -> tuple[bool, str]: disk_total, disk_used, disk_free = shutil.disk_usage("/") disk_gib_free = disk_free / (1024**3) - if disk_gib_free < 10000: + if disk_gib_free < 10: return ( False, f"You have less than 10 GiB disk free ({disk_gib_free} GiB free). " From 9582edb9588f63f4a52dab18f7c2f7f6efa7f0a7 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 2 Feb 2026 11:41:15 -0800 Subject: [PATCH 05/15] better logic --- devenv/bootstrap.py | 4 ++-- devenv/lib/colima.py | 2 -- devenv/update.py | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/devenv/bootstrap.py b/devenv/bootstrap.py index f552524..3765497 100644 --- a/devenv/bootstrap.py +++ b/devenv/bootstrap.py @@ -104,8 +104,8 @@ def main(context: Context, argv: Sequence[str] | None = None) -> ExitCode: ) os.makedirs(f"{constants.root}/bin", exist_ok=True) - if not constants.LINUX: - # we don't install brew or colima-related stuff on linux + if constants.DARWIN: + # we only install brew and colima-related stuff on macos brew.install() docker.install_global() colima.install_global() diff --git a/devenv/lib/colima.py b/devenv/lib/colima.py index 2f7715d..1e448a1 100644 --- a/devenv/lib/colima.py +++ b/devenv/lib/colima.py @@ -63,8 +63,6 @@ def install_global() -> None: "darwin_x86_64_sha256": "791330c62c60389f70e5e1c33a56c35502a9e36e544a418daea0273e539acbf4", "darwin_arm64": f"https://github.com/abiosoft/colima/releases/download/{version}/colima-Darwin-arm64", "darwin_arm64_sha256": "c266fcb272b39221ef6152d2093bb02a1ebadc26042233ad359e1ae52d5d5922", - "linux_x86_64": f"https://github.com/abiosoft/colima/releases/download/{version}/colima-Linux-x86_64", - "linux_x86_64_sha256": "f2d6664a79ff3aa35f0718aac2ba9f6b531772e1464f3b096c1ac2aab404943e", } binroot = f"{root}/bin" diff --git a/devenv/update.py b/devenv/update.py index bf12b66..09a922f 100644 --- a/devenv/update.py +++ b/devenv/update.py @@ -37,8 +37,8 @@ def main(context: Context, argv: Sequence[str] | None = None) -> int: ) os.makedirs(f"{constants.root}/bin", exist_ok=True) - if not constants.LINUX: - # we don't install brew or colima-related stuff on linux + if constants.DARWIN: + # we only install brew and colima-related stuff on macos brew.install() docker.install_global() colima.install_global() From eebe40a8556b23c55ce243957017824a6a402718 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 2 Feb 2026 12:11:56 -0800 Subject: [PATCH 06/15] [skip ci] guide to install apt packages on linux --- devenv/constants.py | 4 +++- devenv/fetch.py | 28 ++++++++++++++++++---------- devenv/lib/apt.py | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 devenv/lib/apt.py diff --git a/devenv/constants.py b/devenv/constants.py index e13e9b2..fd5e6d4 100644 --- a/devenv/constants.py +++ b/devenv/constants.py @@ -4,6 +4,7 @@ import os import platform import pwd +import shutil import typing troubleshooting_help = """\ @@ -27,7 +28,8 @@ SYSTEM = platform.system().lower() MACHINE = platform.machine() DARWIN = SYSTEM == "darwin" -LINUX = SYSTEM == "linux" +# we only support apt-based linuxes +LINUX = shutil.which("dpkg") is not None INTEL_MAC = DARWIN and (MACHINE == "x86_64") SHELL_UNSET = "(SHELL unset)" DEBUG = os.getenv("SNTY_DEVENV_DEBUG", os.getenv("DEBUG", "")) diff --git a/devenv/fetch.py b/devenv/fetch.py index 0cebe34..2309588 100644 --- a/devenv/fetch.py +++ b/devenv/fetch.py @@ -6,10 +6,8 @@ import sys from collections.abc import Sequence -from devenv.constants import CI -from devenv.constants import DARWIN -from devenv.constants import EXTERNAL_CONTRIBUTOR -from devenv.constants import homebrew_bin +from devenv import constants +from devenv.lib import apt from devenv.lib import proc from devenv.lib.context import Context from devenv.lib.modules import DevModuleInfo @@ -35,11 +33,13 @@ def main(context: Context, argv: Sequence[str] | None = None) -> ExitCode: "getsentry/sentry", "getsentry/getsentry", ]: - fetch(code_root, "getsentry/sentry", auth=CI is None, sync=False) + fetch( + code_root, "getsentry/sentry", auth=constants.CI is None, sync=False + ) - if DARWIN: + if constants.DARWIN: print("Installing sentry's brew dependencies...") - if CI: + if constants.CI: # Installing everything from brew takes too much time, # and chromedriver cask flakes occasionally. Really all we need to # set up the devenv is colima and docker-cli. @@ -48,12 +48,20 @@ def main(context: Context, argv: Sequence[str] | None = None) -> ExitCode: proc.run(("brew", "install", "docker", "qemu")) else: proc.run( - (f"{homebrew_bin}/brew", "bundle"), + (f"{constants.homebrew_bin}/brew", "bundle"), cwd=f"{code_root}/sentry", ) + elif constants.LINUX: + if not constants.CI: + required_pkgs = ["watchman", "chromium-chromedriver"] + not_installed = apt.dpkgs_not_installed(required_pkgs) + if not_installed: + raise SystemExit( + f"Please install the following apt packages: {' '.join(required_pkgs)}" + ) else: print( - "Not on MacOS; assuming you have a docker cli and runtime installed." + "Unsupported platform; assuming you have a docker cli and runtime installed." ) proc.run( @@ -61,7 +69,7 @@ def main(context: Context, argv: Sequence[str] | None = None) -> ExitCode: cwd=f"{code_root}/sentry", ) - if not CI and not EXTERNAL_CONTRIBUTOR: + if not constants.CI and not constants.EXTERNAL_CONTRIBUTOR: fetch(code_root, "getsentry/getsentry") print( diff --git a/devenv/lib/apt.py b/devenv/lib/apt.py new file mode 100644 index 0000000..9e20bd2 --- /dev/null +++ b/devenv/lib/apt.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +import subprocess + + +def dpkg_is_installed(pkg: str) -> bool: + try: + out = subprocess.check_output( + ["dpkg-query", "-W", "-f=${Status}", pkg], + stderr=subprocess.DEVNULL, + text=True, + ).strip() + except subprocess.CalledProcessError: + return False + + # + return out == "install ok installed" + + +def dpkgs_not_installed(pkgs: list[str]) -> list[str]: + return [pkg for pkg in pkgs if not dpkg_is_installed(pkg)] From 99d37225d57b8f5e692fcdd42f5a8ea04539c0ad Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 2 Feb 2026 12:46:59 -0800 Subject: [PATCH 07/15] add test apt --- tests/lib/test_apt.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/lib/test_apt.py diff --git a/tests/lib/test_apt.py b/tests/lib/test_apt.py new file mode 100644 index 0000000..03be6be --- /dev/null +++ b/tests/lib/test_apt.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +import pytest + +from devenv import constants +from devenv.lib.apt import dpkg_is_installed + + +pytestmark = pytest.mark.skipif( + not constants.LINUX, reason="apt tests only run on Linux" +) + + +def test_dpkg_is_installed() -> None: + assert dpkg_is_installed("bash") is True + assert dpkg_is_installed("nonexistent-package-xyz-123") is False From 6048a42eaae7fb648f05a2a55de3904d581a7161 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 2 Feb 2026 12:50:06 -0800 Subject: [PATCH 08/15] ubuntu 24.04 --- .github/workflows/bootstrap.yml | 2 +- .github/workflows/integration.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bootstrap.yml b/.github/workflows/bootstrap.yml index 79f65c3..48532dc 100644 --- a/.github/workflows/bootstrap.yml +++ b/.github/workflows/bootstrap.yml @@ -19,7 +19,7 @@ env: jobs: bootstrap: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 30 env: DEVENV_FETCH_BRANCH: master diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index f8c7970..9f8ba8c 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -22,7 +22,7 @@ env: jobs: bootstrap: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 20 env: SENTRY_BRANCH: master From 56a709e67544ca7fe4676fc18e7c21c34039533b Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 2 Feb 2026 12:56:50 -0800 Subject: [PATCH 09/15] fix and add tests with exclude_tag --- tests/doctor/test_load_checks.py | 43 +++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/tests/doctor/test_load_checks.py b/tests/doctor/test_load_checks.py index 60b5017..d01fa12 100644 --- a/tests/doctor/test_load_checks.py +++ b/tests/doctor/test_load_checks.py @@ -9,12 +9,15 @@ def test_load_checks_no_checks() -> None: - assert doctor.load_checks(Repository("not a real repository"), set()) == [] + assert ( + doctor.load_checks(Repository("not a real repository"), set(), set()) + == [] + ) def test_load_checks_test_checks(capsys: pytest.CaptureFixture[str]) -> None: loaded_checks = doctor.load_checks( - Repository(os.path.join(os.path.dirname(__file__))), set() + Repository(os.path.join(os.path.dirname(__file__))), set(), set() ) loaded_check_names = [check.name for check in loaded_checks] assert len(loaded_check_names) == 5 @@ -37,7 +40,7 @@ def test_load_checks_test_checks(capsys: pytest.CaptureFixture[str]) -> None: def test_load_checks_only_passing_tag() -> None: loaded_checks = doctor.load_checks( - Repository(os.path.join(os.path.dirname(__file__))), {"pass"} + Repository(os.path.join(os.path.dirname(__file__))), {"pass"}, set() ) loaded_check_names = [check.name for check in loaded_checks] assert len(loaded_check_names) == 1 @@ -46,7 +49,7 @@ def test_load_checks_only_passing_tag() -> None: def test_load_checks_only_failing_tag() -> None: loaded_checks = doctor.load_checks( - Repository(os.path.join(os.path.dirname(__file__))), {"fail"} + Repository(os.path.join(os.path.dirname(__file__))), {"fail"}, set() ) loaded_check_names = [check.name for check in loaded_checks] assert len(loaded_check_names) == 2 @@ -56,7 +59,9 @@ def test_load_checks_only_failing_tag() -> None: def test_load_checks_passing_and_failing_tag() -> None: loaded_checks = doctor.load_checks( - Repository(os.path.join(os.path.dirname(__file__))), {"pass", "fail"} + Repository(os.path.join(os.path.dirname(__file__))), + {"pass", "fail"}, + set(), ) loaded_check_names = [check.name for check in loaded_checks] assert len(loaded_check_names) == 0 @@ -64,7 +69,7 @@ def test_load_checks_passing_and_failing_tag() -> None: def test_load_checks_test_tag() -> None: loaded_checks = doctor.load_checks( - Repository(os.path.join(os.path.dirname(__file__))), {"test"} + Repository(os.path.join(os.path.dirname(__file__))), {"test"}, set() ) loaded_check_names = [check.name for check in loaded_checks] assert len(loaded_check_names) == 5 @@ -73,3 +78,29 @@ def test_load_checks_test_tag() -> None: assert "failing check with msg" in loaded_check_names assert "broken check" in loaded_check_names assert "broken fix" in loaded_check_names + + +def test_load_checks_exclude_passing_tag() -> None: + loaded_checks = doctor.load_checks( + Repository(os.path.join(os.path.dirname(__file__))), + {"test"}, + {"pass"}, + ) + loaded_check_names = [check.name for check in loaded_checks] + assert len(loaded_check_names) == 4 + assert "passing check" not in loaded_check_names + assert "failing check" in loaded_check_names + assert "failing check with msg" in loaded_check_names + + +def test_load_checks_exclude_failing_tag() -> None: + loaded_checks = doctor.load_checks( + Repository(os.path.join(os.path.dirname(__file__))), + {"test"}, + {"fail"}, + ) + loaded_check_names = [check.name for check in loaded_checks] + assert len(loaded_check_names) == 3 + assert "passing check" in loaded_check_names + assert "failing check" not in loaded_check_names + assert "failing check with msg" not in loaded_check_names From b359ec93faa69546c3473717500724b17ccb8250 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 2 Feb 2026 14:26:04 -0800 Subject: [PATCH 10/15] remove unneeded linux shas --- devenv/lib/docker.py | 4 ---- devenv/lib/limactl.py | 9 +-------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/devenv/lib/docker.py b/devenv/lib/docker.py index 8f72e4b..f2d4b48 100644 --- a/devenv/lib/docker.py +++ b/devenv/lib/docker.py @@ -113,8 +113,6 @@ def install_global() -> None: "darwin_x86_64_sha256": "1b621d4c9a57ff361811cf29754aafb0c28bc113c70011927af8d73c2c162186", "darwin_arm64": f"https://download.docker.com/mac/static/stable/aarch64/docker-{version}.tgz", "darwin_arm64_sha256": "9dae125282116146b06eb777c2125ddda6c0468c0b9ad6c72a82edbc6783a77b", - "linux_x86_64": f"https://download.docker.com/linux/static/stable/x86_64/docker-{version}.tgz", - "linux_x86_64_sha256": "9b4f6fe406e50f9085ee474c451e2bb5adb119a03591f467922d3b4e2ddf31d3", } version_buildx = "v0.22.0" @@ -123,8 +121,6 @@ def install_global() -> None: "darwin_x86_64_sha256": "5221ad6b8acd2283f8fbbeebc79ae4b657e83519ca1c1e4cfbb9405230b3d933", "darwin_arm64": f"https://github.com/docker/buildx/releases/download/{version_buildx}/buildx-{version_buildx}.darwin-arm64", "darwin_arm64_sha256": "5898c338abb1f673107bc087997dc3cb63b4ea66d304ce4223472f57bd8d616e", - "linux_x86_64": f"https://github.com/docker/buildx/releases/download/{version_buildx}/buildx-{version_buildx}.linux-amd64", - "linux_x86_64_sha256": "805195386fba0cea5a1487cf0d47da82a145ea0a792bd3fb477583e2dbcdcc2f", } binroot = f"{root}/bin" diff --git a/devenv/lib/limactl.py b/devenv/lib/limactl.py index a0d9edd..dba5476 100644 --- a/devenv/lib/limactl.py +++ b/devenv/lib/limactl.py @@ -37,10 +37,7 @@ def _install(url: str, sha256: str, into: str) -> None: archive_file = archive.download(url, sha256, dest=f"{tmpd}/download") # the archive from homebrew has a lima/version prefix - if url.startswith("https://ghcr.io/v2/homebrew"): - archive.unpack_strip_n(archive_file, tmpd, n=2) - else: - archive.unpack(archive_file, tmpd) + archive.unpack_strip_n(archive_file, tmpd, n=2) # the archive was atomically placed into tmpd so # these are on the same fs and can be atomically moved too @@ -75,10 +72,6 @@ def install_global() -> None: # arm64_sonoma "darwin_arm64": "https://ghcr.io/v2/homebrew/core/lima/blobs/sha256:8aeb0a3b7295f0c3e0c2a7a92a798a44397936e5bb732db825aee6da5e762d7a", "darwin_arm64_sha256": "8aeb0a3b7295f0c3e0c2a7a92a798a44397936e5bb732db825aee6da5e762d7a", - # on linux we use github releases since most people are probably not using - # linuxbrew and the go binary in homebrew links to linuxbrew's ld.so - "linux_x86_64": f"https://github.com/lima-vm/lima/releases/download/v{version}/lima-{version}-Linux-x86_64.tar.gz", - "linux_x86_64_sha256": "b109cac29569a4aacab01c588f922ea6c7e2ef06ce9260bbc4c382e475bc3b98", } binroot = f"{root}/bin" From d1c28e7877f9bbb8242f67d3ed134386e21f770c Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 2 Feb 2026 14:48:14 -0800 Subject: [PATCH 11/15] moved to sentry post_fetch --- README.md | 2 +- devenv/fetch.py | 28 ---------------------------- devenv/lib/apt.py | 21 --------------------- tests/lib/test_apt.py | 16 ---------------- 4 files changed, 1 insertion(+), 66 deletions(-) delete mode 100644 devenv/lib/apt.py delete mode 100644 tests/lib/test_apt.py diff --git a/README.md b/README.md index 886c39d..dce72a6 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ Everything devenv needs is in `~/.local/share/sentry-devenv`. - `direnv` - we currently rely on direnv and a minimal [`[reporoot]/.envrc`](#direnv) to add `[reporoot]/.devenv/bin` to PATH - see [examples](#examples) for .envrc suggestions - - global tools: `docker` (cli), `colima` + - global tools (macos only; you are otherwise expected to install docker yourself): `docker` (cli), `colima` ### runtime diff --git a/devenv/fetch.py b/devenv/fetch.py index 2309588..35ea1d1 100644 --- a/devenv/fetch.py +++ b/devenv/fetch.py @@ -7,7 +7,6 @@ from collections.abc import Sequence from devenv import constants -from devenv.lib import apt from devenv.lib import proc from devenv.lib.context import Context from devenv.lib.modules import DevModuleInfo @@ -37,33 +36,6 @@ def main(context: Context, argv: Sequence[str] | None = None) -> ExitCode: code_root, "getsentry/sentry", auth=constants.CI is None, sync=False ) - if constants.DARWIN: - print("Installing sentry's brew dependencies...") - if constants.CI: - # Installing everything from brew takes too much time, - # and chromedriver cask flakes occasionally. Really all we need to - # set up the devenv is colima and docker-cli. - # This is also required for arm64 macOS GHA runners. - # We manage colima, so just need to install docker + qemu here. - proc.run(("brew", "install", "docker", "qemu")) - else: - proc.run( - (f"{constants.homebrew_bin}/brew", "bundle"), - cwd=f"{code_root}/sentry", - ) - elif constants.LINUX: - if not constants.CI: - required_pkgs = ["watchman", "chromium-chromedriver"] - not_installed = apt.dpkgs_not_installed(required_pkgs) - if not_installed: - raise SystemExit( - f"Please install the following apt packages: {' '.join(required_pkgs)}" - ) - else: - print( - "Unsupported platform; assuming you have a docker cli and runtime installed." - ) - proc.run( (sys.executable, "-P", "-m", "devenv", "sync"), cwd=f"{code_root}/sentry", diff --git a/devenv/lib/apt.py b/devenv/lib/apt.py deleted file mode 100644 index 9e20bd2..0000000 --- a/devenv/lib/apt.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -import subprocess - - -def dpkg_is_installed(pkg: str) -> bool: - try: - out = subprocess.check_output( - ["dpkg-query", "-W", "-f=${Status}", pkg], - stderr=subprocess.DEVNULL, - text=True, - ).strip() - except subprocess.CalledProcessError: - return False - - # - return out == "install ok installed" - - -def dpkgs_not_installed(pkgs: list[str]) -> list[str]: - return [pkg for pkg in pkgs if not dpkg_is_installed(pkg)] diff --git a/tests/lib/test_apt.py b/tests/lib/test_apt.py deleted file mode 100644 index 03be6be..0000000 --- a/tests/lib/test_apt.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import annotations - -import pytest - -from devenv import constants -from devenv.lib.apt import dpkg_is_installed - - -pytestmark = pytest.mark.skipif( - not constants.LINUX, reason="apt tests only run on Linux" -) - - -def test_dpkg_is_installed() -> None: - assert dpkg_is_installed("bash") is True - assert dpkg_is_installed("nonexistent-package-xyz-123") is False From 5d5da55f2a8f1d50dbc4a37b1b3ff63ec0e7695f Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 2 Feb 2026 15:00:52 -0800 Subject: [PATCH 12/15] test --- .github/workflows/bootstrap.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bootstrap.yml b/.github/workflows/bootstrap.yml index 48532dc..1b22214 100644 --- a/.github/workflows/bootstrap.yml +++ b/.github/workflows/bootstrap.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 30 env: - DEVENV_FETCH_BRANCH: master + DEVENV_FETCH_BRANCH: devenv-post-fetch-linux-apt-packages SNTY_DEVENV_BRANCH: "${{ github.event.pull_request && github.head_ref || github.ref_name }}" steps: From 42d9629b086be4532a0212d95388cc957f5f5f08 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 2 Feb 2026 15:49:29 -0800 Subject: [PATCH 13/15] always run post fetch --- devenv/fetch.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/devenv/fetch.py b/devenv/fetch.py index 35ea1d1..bad76a0 100644 --- a/devenv/fetch.py +++ b/devenv/fetch.py @@ -68,25 +68,24 @@ def fetch( if os.path.exists(reporoot): print(f"{reporoot} already exists") - return - - print(f"fetching {repo} into {reporoot}") - - additional_args = ( - # git@ clones forces the use of cloning through SSH which is what we want, - # though CI must clone open source repos via https (no git authentication) - (f"git@github.com:{repo}",) - if auth - else ( - "--depth", - "1", - "--single-branch", - f"--branch={os.environ['DEVENV_FETCH_BRANCH']}", - f"https://github.com/{repo}", + else: + print(f"fetching {repo} into {reporoot}") + + additional_args = ( + # git@ clones forces the use of cloning through SSH which is what we want, + # though CI must clone open source repos via https (no git authentication) + (f"git@github.com:{repo}",) + if auth + else ( + "--depth", + "1", + "--single-branch", + f"--branch={os.environ['DEVENV_FETCH_BRANCH']}", + f"https://github.com/{repo}", + ) ) - ) - proc.run(("git", "-C", coderoot, "clone", *additional_args), exit=True) + proc.run(("git", "-C", coderoot, "clone", *additional_args), exit=True) context_post_fetch = { "reporoot": reporoot, @@ -97,6 +96,7 @@ def fetch( # optional post-fetch, meant for recommended but not required defaults fp = f"{reporoot}/devenv/post_fetch.py" if os.path.exists(fp): + print(f"running {fp}") spec = importlib.util.spec_from_file_location("post_fetch", fp) module = importlib.util.module_from_spec(spec) # type: ignore @@ -107,6 +107,7 @@ def fetch( print(f"warning! failed running {fp} (code {rc})") if sync: + print("running devenv sync") proc.run((sys.executable, "-P", "-m", "devenv", "sync"), cwd=reporoot) From 6da9810644fb64c16deb134f5fe88dab7b8c7151 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 2 Feb 2026 16:17:41 -0800 Subject: [PATCH 14/15] docker on linux is a prereq --- devenv/bootstrap.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/devenv/bootstrap.py b/devenv/bootstrap.py index 3765497..b3f146a 100644 --- a/devenv/bootstrap.py +++ b/devenv/bootstrap.py @@ -54,9 +54,15 @@ def main(context: Context, argv: Sequence[str] | None = None) -> ExitCode: except RuntimeError: return "Failed to find git. Run xcode-select --install, then re-run bootstrap when done." + if constants.LINUX and not ( + shutil.which("docker") and shutil.which("dockerd") + ): + raise SystemExit("docker engine not installed; required on linux") + # even though this is called before colima starts, # better to try and potentially (although unlikely) fail earlier rather than later - rosetta.ensure() + if constants.DARWIN: + rosetta.ensure() github.add_to_known_hosts() From 1c6ab9b89652e665b01309316e09f8fae93a8dc2 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 2 Feb 2026 16:17:55 -0800 Subject: [PATCH 15/15] pc --- tests/doctor/test_load_checks.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/doctor/test_load_checks.py b/tests/doctor/test_load_checks.py index d01fa12..33af123 100644 --- a/tests/doctor/test_load_checks.py +++ b/tests/doctor/test_load_checks.py @@ -82,9 +82,7 @@ def test_load_checks_test_tag() -> None: def test_load_checks_exclude_passing_tag() -> None: loaded_checks = doctor.load_checks( - Repository(os.path.join(os.path.dirname(__file__))), - {"test"}, - {"pass"}, + Repository(os.path.join(os.path.dirname(__file__))), {"test"}, {"pass"} ) loaded_check_names = [check.name for check in loaded_checks] assert len(loaded_check_names) == 4 @@ -95,9 +93,7 @@ def test_load_checks_exclude_passing_tag() -> None: def test_load_checks_exclude_failing_tag() -> None: loaded_checks = doctor.load_checks( - Repository(os.path.join(os.path.dirname(__file__))), - {"test"}, - {"fail"}, + Repository(os.path.join(os.path.dirname(__file__))), {"test"}, {"fail"} ) loaded_check_names = [check.name for check in loaded_checks] assert len(loaded_check_names) == 3