#!/usr/bin/make -f

# Debian rules for opencode.
#
# opencode is a Bun-native TypeScript monorepo. There is no Node.js-runnable
# build: the CLI is produced by `bun build --compile` as a self-contained
# binary that embeds the Bun runtime and all JS dependencies. Bun is not
# packaged in Debian, so this build:
#
#   1. downloads the Bun toolchain (pinned to upstream's packageManager
#      version) from the network,
#   2. installs the npm dependencies with `bun install` (pinned by the
#      committed bun.lock), and
#   3. builds the CLI for the build host's architecture only with
#      `bun run script/build.ts --single --skip-install` in packages/cli
#      (upstream 2.x monorepo layout; the web UI is embedded by default).
#
# The Bun toolchain, the npm dependencies and the models.dev provider catalog
# are fetched from the network at build time: this package REQUIRES a
# network-enabled build worker and will not build on an offline buildd.
#
# Upstream 2.x ships the models.dev catalog as a committed snapshot
# (packages/core/src/models-dev/snapshot.txt) that may lag behind the live
# catalog by weeks. The build refreshes it best-effort with upstream's own
# update-models-snapshot.ts script (OPENCODE_MODELS_URL can override the
# source) so offline/first-run users start from a current model list.
# The binary also refreshes the catalog live at runtime, so a failed
# refresh only means an older floor, never a stale user experience.
#
# Build and embed the web UI (amd64 only) so the desktop launcher can
# start `opencode serve` (the `web` command was replaced by `serve` in
# upstream 2.x). The terminal TUI and all regular CLI commands remain
# available through /usr/bin/opencode on every architecture.
#
# The web UI build (a large vite build, then a synchronous brotli-11
# compression of the archive) and Bun's bytecode compilation are silent for
# long stretches, which looks like a hung build to inactivity watchdogs on
# slow (arm64) workers. override_dh_auto_build therefore runs
# debian/heartbeat.sh in the background: it prints load, available memory,
# the kernel oom_kill delta and a snapshot of the build processes (state,
# %cpu, command) every 10 minutes, which keeps the log alive for the
# watchdog. An arm64 build previously sat with load 0.00 and all build
# processes idle for ~50 minutes until the watchdog killed it, so after two
# consecutive all-idle samples the heartbeat also dumps each build
# process's wait site (kernel wait channel, syscall, RSS) to the log,
# turning a silent hang into a diagnosable one. The heartbeat lives in a
# plain shell script rather than in this recipe: inline "$( ... )"
# substitutions inside recipe echoes are quoting hazards on the buildd
# shell (mawk rejected a backslash-escaped printf once already), while a
# script file can be tested directly with /bin/sh.

export DH_VERBOSE = 1
# sbuild sets HOME=/sbuild-nonexistent (unwritable); bun needs a writable HOME
# (install cache, tmp). Redirect HOME into the build tree.
export HOME := $(CURDIR)/debian/fakehome

# Upstream version and the Bun version upstream requires. Both are derived
# (from the Debian changelog and the root package.json "packageManager"
# field) so a new upstream release no longer needs manual rules edits.
#
# OPENCODE_VERSION must stay set: upstream's build scripts fall back to
# `git branch --show-current` without it, which fails in a build tree
# unpacked from the orig tarball (no .git).
UPSTREAM_VERSION := $(shell dpkg-parsechangelog -SVersion | sed -n 's/-[^-]*$$//p')
BUN_VERSION := $(shell sed -n 's/.*"packageManager": *"bun@\([^"]*\)".*/\1/p' package.json)
ifeq ($(strip $(BUN_VERSION)),)
$(error opencode: cannot read the required Bun version from package.json "packageManager")
endif

# Debian arch -> Bun build target name.
ifeq ($(DEB_HOST_ARCH),amd64)
BUN_TARGET_ARCH := x64
BUN_DL_ARCH := x64
else ifeq ($(DEB_HOST_ARCH),arm64)
BUN_TARGET_ARCH := arm64
BUN_DL_ARCH := aarch64
else
$(error opencode: unsupported DEB_HOST_ARCH $(DEB_HOST_ARCH))
endif

# Embedded web UI: the UI is produced by a large vite build whose arm64
# builds have repeatedly stalled silently on the build workers (all build
# processes idle for ~50 minutes until the inactivity watchdog killed the
# build). Skip it on arm64 (--skip-web-ui) and do not install the OpenCode
# Web desktop launcher there: the binary then embeds an empty app archive,
# while the terminal UI, the CLI and the headless `serve` API server remain
# fully functional. Revisit once the heartbeat stall detector has identified
# the arm64 vite stall.
ifeq ($(DEB_HOST_ARCH),arm64)
WEBUI_BUILD_FLAGS := --skip-web-ui
WEBUI_MODE := skipped
WEBUI_DESKTOP :=
else
WEBUI_BUILD_FLAGS :=
WEBUI_MODE := embedded
WEBUI_DESKTOP := debian/opencode-web.desktop
endif

BUN_HOME := $(CURDIR)/debian/.bun-toolchain
BUN := $(BUN_HOME)/bun/bun

# Where the built binary lands. Upstream 2.x builds from packages/cli and
# names the per-target output directory cli-linux-<arch>
# (was opencode-linux-<arch> in the packages/opencode 1.x layout).
DIST_NAME := cli-linux-$(BUN_TARGET_ARCH)
DIST_DIR := packages/cli/dist/$(DIST_NAME)

%:
	dh $@

# ----------------------------------------------------------------------------
# Configure: fetch the Bun toolchain (network).
# ----------------------------------------------------------------------------
override_dh_auto_configure:
	set -e; \
	echo "==> fetching bun $(BUN_VERSION) for linux-$(BUN_DL_ARCH)"; \
	mkdir -p "$(BUN_HOME)"; \
	curl -fSL --retry 3 --speed-limit 1024 --speed-time 60 \
		-o "$(BUN_HOME)/bun.zip" \
		"https://github.com/oven-sh/bun/releases/download/bun-v$(BUN_VERSION)/bun-linux-$(BUN_DL_ARCH).zip"; \
	unzip -q -o "$(BUN_HOME)/bun.zip" -d "$(BUN_HOME)"; \
	mv "$(BUN_HOME)/bun-linux-$(BUN_DL_ARCH)" "$(BUN_HOME)/bun"; \
	rm -f "$(BUN_HOME)/bun.zip"; \
	"$(BUN)" --version

# ----------------------------------------------------------------------------
# Build: install deps and compile the CLI for the build host's arch.
# ----------------------------------------------------------------------------
override_dh_auto_build:
	set -e; \
	mkdir -p $(CURDIR)/debian/fakehome; \
	export PATH="$(BUN_HOME)/bun:$$PATH"; \
	sh debian/heartbeat.sh & \
	HEARTBEAT_PID=$$!; \
	trap 'kill $$HEARTBEAT_PID 2>/dev/null || true' EXIT; \
	echo "==> refreshing the bundled models.dev catalog snapshot (best-effort, 300s timeout)"; \
	timeout 300 bun run --cwd packages/core script/update-models-snapshot.ts \
		|| echo "==> WARNING: models.dev refresh failed or timed out; keeping the upstream snapshot"; \
	echo "==> bun install --frozen-lockfile (deps pinned by bun.lock; fail"; \
	echo "    loudly if the lockfile ever drifts; lifecycle scripts skipped:"; \
	echo "    native modules ship prebuilt binaries and need no node-gyp)"; \
	bun install --frozen-lockfile --ignore-scripts; \
	echo "==> building $(DIST_NAME) (single target, web UI $(WEBUI_MODE))"; \
	cd packages/cli; \
	OPENCODE_VERSION=$(UPSTREAM_VERSION) \
		bun run script/build.ts --single --skip-install $(WEBUI_BUILD_FLAGS); \
	kill $$HEARTBEAT_PID 2>/dev/null || true

# ----------------------------------------------------------------------------
# Test: the staged executable is smoke-tested in the dh_strip sequence
# below. The full upstream test suite needs LLM provider credentials;
# skip it.
# ----------------------------------------------------------------------------
override_dh_auto_test:
	@echo "Skipping upstream test suite (requires LLM provider credentials)."
	@echo "The staged executable is smoke-tested in the dh_strip sequence."

# ----------------------------------------------------------------------------
# Install the compiled binary at a stable, architecture-independent path,
# provide the terminal command, and add separate Web and CLI desktop entries.
# ----------------------------------------------------------------------------
override_dh_auto_install:
	install -D -m 0755 "$(DIST_DIR)/bin/opencode" \
		debian/opencode/usr/lib/opencode/opencode
	install -D -m 0755 debian/opencode-wrapper \
		debian/opencode/usr/bin/opencode
ifneq ($(WEBUI_DESKTOP),)
	install -D -m 0755 debian/opencode-web \
		debian/opencode/usr/bin/opencode-web
	install -D -m 0644 debian/opencode-web.desktop \
		debian/opencode/usr/share/applications/opencode-web.desktop
endif
	install -D -m 0644 debian/opencode-cli.desktop \
		debian/opencode/usr/share/applications/opencode-cli.desktop
	install -D -m 0644 packages/desktop/icons/prod/icon.png \
		debian/opencode/usr/share/pixmaps/opencode.png

# Bun standalone executables append their compiled module graph to the ELF.
# strip/dwz remove or rewrite that payload, leaving a plain Bun executable.
override_dh_strip:
	@echo "Skipping dh_strip: it corrupts Bun standalone executables."
	env -u BUN_BE_BUN debian/opencode/usr/lib/opencode/opencode --version

override_dh_dwz:
	@echo "Skipping dh_dwz: it is unsafe for Bun standalone executables."

# ----------------------------------------------------------------------------
# Clean.
# ----------------------------------------------------------------------------
override_dh_auto_clean:
	rm -rf "$(BUN_HOME)" node_modules packages/cli/dist packages/app/dist debian/fakehome
	dh_auto_clean
