|
|
@@ -632,73 +632,135 @@ export namespace LSPServer {
|
|
|
root: NearestRoot(["compile_commands.json", "compile_flags.txt", ".clangd", "CMakeLists.txt", "Makefile"]),
|
|
|
extensions: [".c", ".cpp", ".cc", ".cxx", ".c++", ".h", ".hpp", ".hh", ".hxx", ".h++"],
|
|
|
async spawn(root) {
|
|
|
- let bin = Bun.which("clangd", {
|
|
|
- PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
|
|
- })
|
|
|
- if (!bin) {
|
|
|
- if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
|
|
- log.info("downloading clangd from GitHub releases")
|
|
|
+ const args = ["--background-index", "--clang-tidy"]
|
|
|
+ const fromPath = Bun.which("clangd")
|
|
|
+ if (fromPath) {
|
|
|
+ return {
|
|
|
+ process: spawn(fromPath, args, {
|
|
|
+ cwd: root,
|
|
|
+ }),
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- const releaseResponse = await fetch("https://api.github.com/repos/clangd/clangd/releases/latest")
|
|
|
- if (!releaseResponse.ok) {
|
|
|
- log.error("Failed to fetch clangd release info")
|
|
|
- return
|
|
|
+ const ext = process.platform === "win32" ? ".exe" : ""
|
|
|
+ const direct = path.join(Global.Path.bin, "clangd" + ext)
|
|
|
+ if (await Bun.file(direct).exists()) {
|
|
|
+ return {
|
|
|
+ process: spawn(direct, args, {
|
|
|
+ cwd: root,
|
|
|
+ }),
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- const release = (await releaseResponse.json()) as any
|
|
|
+ const entries = await fs.readdir(Global.Path.bin, { withFileTypes: true }).catch(() => [])
|
|
|
+ for (const entry of entries) {
|
|
|
+ if (!entry.isDirectory()) continue
|
|
|
+ if (!entry.name.startsWith("clangd_")) continue
|
|
|
+ const candidate = path.join(Global.Path.bin, entry.name, "bin", "clangd" + ext)
|
|
|
+ if (await Bun.file(candidate).exists()) {
|
|
|
+ return {
|
|
|
+ process: spawn(candidate, args, {
|
|
|
+ cwd: root,
|
|
|
+ }),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- const platform = process.platform
|
|
|
- let assetName = ""
|
|
|
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
|
|
+ log.info("downloading clangd from GitHub releases")
|
|
|
|
|
|
- if (platform === "darwin") {
|
|
|
- assetName = "clangd-mac-"
|
|
|
- } else if (platform === "linux") {
|
|
|
- assetName = "clangd-linux-"
|
|
|
- } else if (platform === "win32") {
|
|
|
- assetName = "clangd-windows-"
|
|
|
- } else {
|
|
|
- log.error(`Platform ${platform} is not supported by clangd auto-download`)
|
|
|
- return
|
|
|
- }
|
|
|
+ const releaseResponse = await fetch("https://api.github.com/repos/clangd/clangd/releases/latest")
|
|
|
+ if (!releaseResponse.ok) {
|
|
|
+ log.error("Failed to fetch clangd release info")
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- assetName += release.tag_name + ".zip"
|
|
|
+ const release: {
|
|
|
+ tag_name?: string
|
|
|
+ assets?: { name?: string; browser_download_url?: string }[]
|
|
|
+ } = await releaseResponse.json()
|
|
|
|
|
|
- const asset = release.assets.find((a: any) => a.name === assetName)
|
|
|
- if (!asset) {
|
|
|
- log.error(`Could not find asset ${assetName} in latest clangd release`)
|
|
|
- return
|
|
|
- }
|
|
|
+ const tag = release.tag_name
|
|
|
+ if (!tag) {
|
|
|
+ log.error("clangd release did not include a tag name")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const platform = process.platform
|
|
|
+ const tokens: Record<string, string> = {
|
|
|
+ darwin: "mac",
|
|
|
+ linux: "linux",
|
|
|
+ win32: "windows",
|
|
|
+ }
|
|
|
+ const token = tokens[platform]
|
|
|
+ if (!token) {
|
|
|
+ log.error(`Platform ${platform} is not supported by clangd auto-download`)
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- const downloadUrl = asset.browser_download_url
|
|
|
- const downloadResponse = await fetch(downloadUrl)
|
|
|
- if (!downloadResponse.ok) {
|
|
|
- log.error("Failed to download clangd")
|
|
|
- return
|
|
|
- }
|
|
|
+ const assets = release.assets ?? []
|
|
|
+ const valid = (item: { name?: string; browser_download_url?: string }) => {
|
|
|
+ if (!item.name) return false
|
|
|
+ if (!item.browser_download_url) return false
|
|
|
+ if (!item.name.includes(token)) return false
|
|
|
+ return item.name.includes(tag)
|
|
|
+ }
|
|
|
|
|
|
- const zipPath = path.join(Global.Path.bin, "clangd.zip")
|
|
|
- await Bun.file(zipPath).write(downloadResponse)
|
|
|
+ const asset =
|
|
|
+ assets.find((item) => valid(item) && item.name?.endsWith(".zip")) ??
|
|
|
+ assets.find((item) => valid(item) && item.name?.endsWith(".tar.xz")) ??
|
|
|
+ assets.find((item) => valid(item))
|
|
|
+ if (!asset?.name || !asset.browser_download_url) {
|
|
|
+ log.error("clangd could not match release asset", { tag, platform })
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- await $`unzip -o -q ${zipPath}`.quiet().cwd(Global.Path.bin).nothrow()
|
|
|
- await fs.rm(zipPath, { force: true })
|
|
|
+ const name = asset.name
|
|
|
+ const downloadResponse = await fetch(asset.browser_download_url)
|
|
|
+ if (!downloadResponse.ok) {
|
|
|
+ log.error("Failed to download clangd")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const archive = path.join(Global.Path.bin, name)
|
|
|
+ const buf = await downloadResponse.arrayBuffer()
|
|
|
+ if (buf.byteLength === 0) {
|
|
|
+ log.error("Failed to write clangd archive")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ await Bun.write(archive, buf)
|
|
|
|
|
|
- const extractedDir = path.join(Global.Path.bin, assetName.replace(".zip", ""))
|
|
|
- bin = path.join(extractedDir, "bin", "clangd" + (platform === "win32" ? ".exe" : ""))
|
|
|
+ const zip = name.endsWith(".zip")
|
|
|
+ const tar = name.endsWith(".tar.xz")
|
|
|
+ if (!zip && !tar) {
|
|
|
+ log.error("clangd encountered unsupported asset", { asset: name })
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- if (!(await Bun.file(bin).exists())) {
|
|
|
- log.error("Failed to extract clangd binary")
|
|
|
- return
|
|
|
- }
|
|
|
+ if (zip) {
|
|
|
+ await $`unzip -o -q ${archive}`.quiet().cwd(Global.Path.bin).nothrow()
|
|
|
+ }
|
|
|
+ if (tar) {
|
|
|
+ await $`tar -xf ${archive}`.cwd(Global.Path.bin).nothrow()
|
|
|
+ }
|
|
|
+ await fs.rm(archive, { force: true })
|
|
|
|
|
|
- if (platform !== "win32") {
|
|
|
- await $`chmod +x ${bin}`.nothrow()
|
|
|
- }
|
|
|
+ const bin = path.join(Global.Path.bin, "clangd_" + tag, "bin", "clangd" + ext)
|
|
|
+ if (!(await Bun.file(bin).exists())) {
|
|
|
+ log.error("Failed to extract clangd binary")
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- log.info(`installed clangd`, { bin })
|
|
|
+ if (platform !== "win32") {
|
|
|
+ await $`chmod +x ${bin}`.nothrow()
|
|
|
}
|
|
|
|
|
|
+ await fs.unlink(path.join(Global.Path.bin, "clangd")).catch(() => {})
|
|
|
+ await fs.symlink(bin, path.join(Global.Path.bin, "clangd")).catch(() => {})
|
|
|
+
|
|
|
+ log.info(`installed clangd`, { bin })
|
|
|
+
|
|
|
return {
|
|
|
- process: spawn(bin, ["--background-index", "--clang-tidy"], {
|
|
|
+ process: spawn(bin, args, {
|
|
|
cwd: root,
|
|
|
}),
|
|
|
}
|