#!/usr/bin/env bash # tools/lsp/build.sh — Build the El LSP server binary end-to-end. # # Pipeline: # 1. Compile el-lsp.el → C source (using elc) # 2. Compile C source → binary (using cc with el_runtime.c) # # Output: tools/lsp/dist/el-lsp # # Prerequisites: # - dist/platform/elc (the El self-hosted compiler) # - el-compiler/runtime/el_runtime.c + el_runtime.h # - cc (clang or gcc), libcurl, pthreads # # Usage: # cd # ./tools/lsp/build.sh # # Or from anywhere: # EL_HOME=/path/to/el-root ./tools/lsp/build.sh set -euo pipefail # ── Locate el root ───────────────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" EL_HOME="${EL_HOME:-$(cd "${SCRIPT_DIR}/../.." && pwd)}" ELC="${ELC:-${EL_HOME}/dist/platform/elc}" RUNTIME_DIR="${EL_HOME}/el-compiler/runtime" LSP_DIR="${SCRIPT_DIR}" OUT_DIR="${LSP_DIR}/dist" # ── Validate prerequisites ───────────────────────────────────────────────── echo "==> Checking prerequisites..." echo " EL_HOME = ${EL_HOME}" echo " elc = ${ELC}" echo " runtime = ${RUNTIME_DIR}" if [ ! -x "${ELC}" ]; then echo "error: elc not found at ${ELC}" >&2 echo " Build it first: cd && make (or compile elc-bootstrap.c)" >&2 exit 1 fi if [ ! -f "${RUNTIME_DIR}/el_runtime.c" ]; then echo "error: el_runtime.c not found at ${RUNTIME_DIR}" >&2 exit 1 fi if [ ! -f "${LSP_DIR}/el-lsp.el" ]; then echo "error: el-lsp.el not found at ${LSP_DIR}" >&2 exit 1 fi mkdir -p "${OUT_DIR}" # ── Compile El → C ───────────────────────────────────────────────────────── C_OUT="${OUT_DIR}/el-lsp.c" echo "==> Compiling El → C..." echo " ${LSP_DIR}/el-lsp.el → ${C_OUT}" # el-lsp.el uses only standard El builtins (no import statements needed). # The elc compiler emits #include "el_runtime.h" at the top of the output. "${ELC}" "${LSP_DIR}/el-lsp.el" > "${C_OUT}" echo " Done ($(wc -l < "${C_OUT}") lines of C)." # ── Compile C → binary ───────────────────────────────────────────────────── BIN="${OUT_DIR}/el-lsp" echo "==> Compiling C → binary..." echo " ${C_OUT} + el_runtime.c → ${BIN}" cc -std=c11 -O2 \ -I "${RUNTIME_DIR}" \ -o "${BIN}" \ "${C_OUT}" "${RUNTIME_DIR}/el_runtime.c" \ -lcurl -lpthread echo " Done." # ── Summary ──────────────────────────────────────────────────────────────── echo echo "==> Build complete." echo echo " Binary : ${BIN}" echo " Size : $(du -sh "${BIN}" | cut -f1)" echo echo " Install system-wide:" echo " sudo cp ${BIN} /usr/local/bin/el-lsp" echo echo " Quick smoke test (initialize + shutdown):" cat << 'SMOKETEST' python3 - << 'PY' import subprocess, json def frame(body): b = body.encode() return f"Content-Length: {len(b)}\r\n\r\n".encode() + b def send(proc, obj): proc.stdin.write(frame(json.dumps(obj))) proc.stdin.flush() def recv(proc): hdr = b"" while not hdr.endswith(b"\r\n\r\n"): hdr += proc.stdout.read(1) cl = int([l for l in hdr.decode().split("\r\n") if l.startswith("Content-Length")][0].split(": ")[1]) return json.loads(proc.stdout.read(cl)) import sys, os bin_path = sys.argv[1] if len(sys.argv) > 1 else "./dist/el-lsp" proc = subprocess.Popen([bin_path], stdin=subprocess.PIPE, stdout=subprocess.PIPE) send(proc, {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{}}}) r = recv(proc) print("initialize:", r.get("result", {}).get("serverInfo", {}).get("name"), "OK" if "result" in r else "FAIL") send(proc, {"jsonrpc":"2.0","id":2,"method":"shutdown","params":{}}) r = recv(proc) print("shutdown:", "OK" if r.get("result") is None else "FAIL") send(proc, {"jsonrpc":"2.0","method":"exit","params":{}}) proc.wait() print("exit: OK") PY SMOKETEST