|
| 1 | +local constants = require("cmake-tools.const") |
1 | 2 | local scanner = {} |
2 | 3 |
|
3 | | ---Helper functions |
4 | | -local function execute_command(cmd) |
5 | | - print(cmd) |
| 4 | +local C_COMPILERS = { "gcc", "clang" } |
| 5 | +local TOOLCHAIN_SEARCH_PATHS = { |
| 6 | + vim.fn.expand("~/.cmake/toolchains"), |
| 7 | + "/usr/share/cmake/toolchains", |
| 8 | + "/usr/local/share/cmake/toolchains", |
| 9 | + "/etc/cmake/toolchains", |
| 10 | +} |
6 | 11 |
|
7 | | - if cmd == nil then |
8 | | - return false, -1, nil |
9 | | - end |
10 | | - local result = vim.system(cmd, { text = true }):wait() |
11 | | - if result == nil then |
12 | | - return false, -1, nil |
| 12 | +local function toolchain_candidates(prefix) |
| 13 | + if prefix == "" then |
| 14 | + return {} |
13 | 15 | end |
14 | | - print(result.stdout) |
15 | | - return true, result.code, result.stdout |
16 | | -end |
17 | 16 |
|
18 | | -local function split_path(path_env) |
19 | | - local paths = {} |
20 | | - local sep = package.config:sub(1, 1) == "\\" and ";" or ":" |
21 | | - for path in string.gmatch(path_env, "([^" .. sep .. "]+)") do |
22 | | - table.insert(paths, path) |
23 | | - end |
24 | | - return paths |
| 17 | + -- strip trailing dash for the filename |
| 18 | + local triplet = prefix:gsub("%-$", "") |
| 19 | + |
| 20 | + return { |
| 21 | + triplet .. ".cmake", |
| 22 | + triplet .. "-toolchain.cmake", |
| 23 | + "toolchain-" .. triplet .. ".cmake", |
| 24 | + } |
25 | 25 | end |
26 | 26 |
|
27 | | -local function get_gcc_version(gcc_path) |
28 | | - local success, exit_code, output = execute_command({ "gcc", "--version" }) |
29 | | - if output == nil then |
| 27 | +local function find_toolchain_file(prefix) |
| 28 | + local candidates = toolchain_candidates(prefix) |
| 29 | + if #candidates == 0 then |
30 | 30 | return nil |
31 | 31 | end |
32 | | - -- Try multiple patterns to match different gcc output formats |
33 | | - local version = output:match("gcc[%s%a]([%d%.]+)") -- "gcc (GCC) 15.2.1" |
34 | | - or output:match("gcc version ([%d%.]+)") -- "gcc version 11.4.0" |
35 | | - return version |
36 | | -end |
37 | 32 |
|
38 | | -local function get_clang_version(clang_path) |
39 | | - local success, exit_code, output = execute_command({ "clang", "--version" }) |
| 33 | + for _, search_dir in ipairs(TOOLCHAIN_SEARCH_PATHS) do |
| 34 | + for _, filename in ipairs(candidates) do |
| 35 | + local full_path = search_dir .. "/" .. filename |
| 36 | + if vim.fn.filereadable(full_path) == 1 then |
| 37 | + return full_path |
| 38 | + end |
| 39 | + end |
| 40 | + end |
40 | 41 |
|
41 | | - if output == nil then |
42 | | - return nil |
| 42 | + return nil |
| 43 | +end |
| 44 | +local function match_c_compiler(exe) |
| 45 | + for _, c_name in ipairs(C_COMPILERS) do |
| 46 | + local prefix = exe:match("^(.+%-)" .. c_name .. "$") |
| 47 | + if prefix then |
| 48 | + return prefix, c_name |
| 49 | + end |
| 50 | + -- Without prefix: "gcc" |
| 51 | + if exe == c_name then |
| 52 | + return "", c_name |
| 53 | + end |
43 | 54 | end |
44 | | - local version_line = output:match("clang version ([%d%.]+)") |
45 | | - return version_line |
| 55 | + return nil, nil |
46 | 56 | end |
47 | | - |
48 | | -local function find_compiler_pair(dir, c_compiler) |
49 | | - local base_name = c_compiler:match("([^/\\]+)$") |
50 | | - local cxx_name |
51 | | - if base_name:match("gcc") then |
52 | | - cxx_name = base_name:gsub("gcc", "g++") |
53 | | - elseif base_name:match("clang") then |
54 | | - cxx_name = base_name:gsub("clang", "clang++") |
55 | | - else |
| 57 | +local function derive_toolchain(prefix, c_name) |
| 58 | + local map = { |
| 59 | + gcc = { cxx = "g++", linker = "ld" }, |
| 60 | + clang = { cxx = "clang++", linker = "lld" }, |
| 61 | + } |
| 62 | + local companions = map[c_name] |
| 63 | + if not companions then |
56 | 64 | return nil |
57 | 65 | end |
58 | | - local cxx_path = dir .. "/" .. cxx_name |
59 | | - if vim.fn.filereadable(cxx_path) then |
60 | | - return cxx_path |
61 | | - end |
62 | | - return nil |
| 66 | + |
| 67 | + return { |
| 68 | + c = (prefix or "") .. c_name, |
| 69 | + cxx = (prefix or "") .. companions.cxx, |
| 70 | + linker = (prefix or "") .. companions.linker, |
| 71 | + -- preserve prefix so we can use it in the kit name |
| 72 | + prefix = prefix or "", |
| 73 | + } |
63 | 74 | end |
64 | 75 |
|
65 | | -local function find_linker_pair(dir, linker_name) |
66 | | - if not linker_name then |
67 | | - return nil |
| 76 | +local function get_path_executables() |
| 77 | + local path_dirs = vim.split(vim.env.PATH or "", ":", { plain = true }) |
| 78 | + local executables = {} |
| 79 | + for _, dir in ipairs(path_dirs) do |
| 80 | + local entries = vim.fn.readdir(dir) -- returns {} on error / missing dir |
| 81 | + for _, entry in ipairs(entries) do |
| 82 | + local full = dir .. "/" .. entry |
| 83 | + if vim.fn.executable(full) == 1 then |
| 84 | + executables[entry] = true -- deduplicate by name |
| 85 | + end |
| 86 | + end |
68 | 87 | end |
69 | | - local linker_path = dir .. "/" .. linker_name |
70 | | - if vim.fn.filereadable(linker_path) then |
71 | | - return linker_path |
| 88 | + return executables |
| 89 | +end |
| 90 | +local function discover_toolchains(executables) |
| 91 | + local seen = {} |
| 92 | + local chains = {} |
| 93 | + |
| 94 | + for exe in pairs(executables) do |
| 95 | + local prefix, c_name = match_c_compiler(exe) |
| 96 | + if c_name then |
| 97 | + local key = prefix .. c_name |
| 98 | + if not seen[key] then |
| 99 | + seen[key] = true |
| 100 | + local chain = derive_toolchain(prefix, c_name) |
| 101 | + if chain then |
| 102 | + table.insert(chains, chain) |
| 103 | + end |
| 104 | + end |
| 105 | + end |
72 | 106 | end |
73 | | - return nil |
| 107 | + vim.notify("Discovered toolchains: " .. vim.inspect(chains)) |
| 108 | + return chains |
74 | 109 | end |
75 | 110 |
|
76 | | -local function get_toolchain_file() |
77 | | - local toolchainFile = os.getenv("CMAKE_TOOLCHAIN_FILE") |
78 | | - if toolchainFile and vim.fn.filereadable(toolchainFile) then |
79 | | - return toolchainFile |
| 111 | +local function check_executable_exists(compiler) |
| 112 | + if not compiler or compiler == "" then |
| 113 | + return nil |
80 | 114 | end |
81 | | - return nil |
| 115 | + local exists = vim.fn.executable(compiler) == 1 |
| 116 | + return exists |
82 | 117 | end |
83 | 118 |
|
84 | | -local function ensure_directory(path) |
85 | | - if not path then |
86 | | - vim.notify("Path is empty", vim.log.levels.ERROR) |
87 | | - return |
88 | | - end |
89 | | - local pattern = "(.*/)" |
90 | | - local dir = path:match(pattern) |
91 | | - if dir then |
92 | | - vim.fn.mkdir(dir, "p") |
| 119 | +local function get_executable_path(compiler) |
| 120 | + if not compiler or compiler == "" then |
| 121 | + return nil |
93 | 122 | end |
| 123 | + local path = vim.fn.exepath(compiler) |
| 124 | + return path |
94 | 125 | end |
95 | 126 |
|
96 | | -local function save_kits(kits, filepath) |
97 | | - ensure_directory(filepath) |
98 | | - local file = io.open(filepath, "w") |
99 | | - if not file then |
100 | | - vim.notify("Failed to open file for writing: " .. filepath, vim.log.levels.ERROR) |
101 | | - return false |
102 | | - end |
103 | | - if not kits then |
104 | | - vim.notify("Can not encode data to json because it is nil", vim.log.levels.ERROR) |
105 | | - return false |
| 127 | +local function get_compiler_version(compiler) |
| 128 | + if not compiler or compiler == "" then |
| 129 | + return nil |
106 | 130 | end |
107 | | - local json_content = vim.json.encode(kits) |
108 | | - file:write(json_content) |
109 | | - file:close() |
110 | | - return true |
| 131 | + local version_output = vim.fn.system({ compiler, "--version" }) |
| 132 | + local version = version_output:match("%d+%.%d+%.%d+") |
| 133 | + return version |
111 | 134 | end |
112 | 135 |
|
113 | 136 | -- Main function to scan for kits |
114 | 137 | function scanner.scan_for_kits() |
| 138 | + vim.notify("Scanning for kits…") |
| 139 | + |
| 140 | + local executables = get_path_executables() |
| 141 | + local toolchains = discover_toolchains(executables) |
115 | 142 | local kits = {} |
116 | | - local const = require("cmake-tools.const") |
117 | | - local path_env = os.getenv("PATH") or "" |
118 | | - local paths = split_path(path_env) |
119 | | - |
120 | | - for _, dir in ipairs(paths) do |
121 | | - local linker_path = find_linker_pair(dir, "lld") |
122 | | - if linker_path == nil then |
123 | | - linker_path = find_linker_pair(dir, "ld") |
124 | | - end |
125 | | - local toolchainFile = get_toolchain_file() |
126 | | - local gcc_path = dir .. "/gcc" |
127 | | - if vim.fn.filereadable(gcc_path) then |
128 | | - local gcc_version = get_gcc_version(gcc_path) |
129 | | - local gxx_path = find_compiler_pair(dir, gcc_path) |
130 | | - if gxx_path then |
131 | | - local kit = { |
132 | | - name = "GCC-" .. (gcc_version or "unknown"), |
133 | | - compilers = { |
134 | | - C = gcc_path, |
135 | | - CXX = gxx_path, |
136 | | - }, |
137 | | - linker = (linker_path or ""), |
138 | | - toolchainFile = (toolchainFile or ""), |
139 | | - } |
140 | | - table.insert(kits, kit) |
| 143 | + |
| 144 | + for _, tc in ipairs(toolchains) do |
| 145 | + local has_c = check_executable_exists(tc.c) |
| 146 | + local has_cxx = check_executable_exists(tc.cxx) |
| 147 | + |
| 148 | + if has_c then |
| 149 | + local kit = { compilers = {} } |
| 150 | + |
| 151 | + local version = get_compiler_version(tc.c) |
| 152 | + local prefix_label = tc.prefix ~= "" and (tc.prefix:gsub("%-$", "") .. " ") or "" |
| 153 | + kit.name = prefix_label .. tc.c .. " " .. (version or "Unknown") |
| 154 | + |
| 155 | + kit.compilers.C = get_executable_path(tc.c) |
| 156 | + |
| 157 | + if has_cxx then |
| 158 | + kit.compilers.CXX = get_executable_path(tc.cxx) |
| 159 | + else |
| 160 | + vim.notify("No C++ compiler found for: " .. tc.c) |
141 | 161 | end |
142 | | - end |
143 | 162 |
|
144 | | - local clang_path = dir .. "/clang" |
145 | | - if vim.fn.filereadable(clang_path) then |
146 | | - local clang_version = get_clang_version(clang_path) |
147 | | - local clangxx_path = find_compiler_pair(dir, clang_path) |
148 | | - if clangxx_path then |
149 | | - local kit = { |
150 | | - name = "Clang-" .. (clang_version or "unknown"), |
151 | | - compilers = { |
152 | | - C = clang_path, |
153 | | - CXX = clangxx_path, |
154 | | - }, |
155 | | - linker = (linker_path or ""), |
156 | | - toolchainFile = (toolchainFile or ""), |
157 | | - } |
158 | | - table.insert(kits, kit) |
| 163 | + if check_executable_exists(tc.linker) then |
| 164 | + kit.linker = get_executable_path(tc.linker) |
| 165 | + else |
| 166 | + vim.notify("No linker found for: " .. tc.c) |
| 167 | + end |
| 168 | + local toolchain_file = find_toolchain_file(tc.prefix) |
| 169 | + if toolchain_file then |
| 170 | + kit.toolchainFile = toolchain_file |
| 171 | + vim.notify("Toolchain file found: " .. toolchain_file) |
| 172 | + else |
| 173 | + if tc.prefix ~= "" then |
| 174 | + vim.notify("No toolchain file found for prefix: " .. tc.prefix, vim.log.levels.WARN) |
| 175 | + end |
159 | 176 | end |
160 | | - end |
161 | | - end |
162 | 177 |
|
163 | | - if #kits == 0 then |
164 | | - vim.notify("No compilers found in PATH.", vim.log.levels.WARN) |
165 | | - return {} |
| 178 | + table.insert(kits, kit) |
| 179 | + else |
| 180 | + vim.notify("Skipping toolchain – C compiler not found: " .. tc.c) |
| 181 | + end |
166 | 182 | end |
167 | | - vim.notify("Found kits", vim.log.levels.INFO) |
168 | | - if const.cmake_kits_path == nil then |
169 | | - vim.notify( |
170 | | - "local const variable is nil, it seems that the required module could not be loaded", |
171 | | - vim.log.levels.ERROR |
172 | | - ) |
173 | | - return {} |
| 183 | + local json_kits = vim.fn.json_encode(kits) |
| 184 | + if json_kits then |
| 185 | + vim.fn.writefile({ json_kits }, constants.cmake_kits_path) |
| 186 | + vim.notify("Kits saved to: " .. constants.cmake_kits_path) |
| 187 | + else |
| 188 | + vim.notify("Failed to encode kits to JSON.", vim.log.levels.ERROR) |
174 | 189 | end |
175 | | - save_kits(kits, const.cmake_kits_path) |
| 190 | + vim.notify("Scanning complete.") |
176 | 191 | return kits |
177 | 192 | end |
178 | 193 |
|
|
0 commit comments