diff options
author | Hennadii Chernyshchyk <genaloner@gmail.com> | 2022-09-10 14:05:32 +0300 |
---|---|---|
committer | Hennadii Chernyshchyk <genaloner@gmail.com> | 2022-09-10 17:48:06 +0300 |
commit | fb10b4906aadaeff295883d171c05246943e5571 (patch) | |
tree | eae589c4aeb613b8a2e8a1daf678f008f01069b9 /lua/tasks/module/cmake.lua | |
download | neovim-tasks-fb10b4906aadaeff295883d171c05246943e5571.tar.gz neovim-tasks-fb10b4906aadaeff295883d171c05246943e5571.zip |
Initial commit
Diffstat (limited to 'lua/tasks/module/cmake.lua')
-rw-r--r-- | lua/tasks/module/cmake.lua | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/lua/tasks/module/cmake.lua b/lua/tasks/module/cmake.lua new file mode 100644 index 0000000..4900aec --- /dev/null +++ b/lua/tasks/module/cmake.lua | |||
@@ -0,0 +1,272 @@ | |||
1 | local Path = require('plenary.path') | ||
2 | local utils = require('tasks.utils') | ||
3 | local scandir = require('plenary.scandir') | ||
4 | local ProjectConfig = require('tasks.project_config') | ||
5 | local os = require('ffi').os:lower() | ||
6 | local cmake = {} | ||
7 | |||
8 | --- Parses build dir expression. | ||
9 | ---@param dir string: Path with expressions to replace. | ||
10 | ---@param build_type string | ||
11 | ---@return table | ||
12 | local function parse_dir(dir, build_type) | ||
13 | local parsed_dir = dir:gsub('{cwd}', vim.loop.cwd()) | ||
14 | parsed_dir = parsed_dir:gsub('{os}', os) | ||
15 | parsed_dir = parsed_dir:gsub('{build_type}', build_type:lower()) | ||
16 | return Path:new(parsed_dir) | ||
17 | end | ||
18 | |||
19 | --- Returns reply directory that contains targets information. | ||
20 | ---@param build_dir table | ||
21 | ---@return unknown | ||
22 | local function get_reply_dir(build_dir) return build_dir / '.cmake' / 'api' / 'v1' / 'reply' end | ||
23 | |||
24 | --- Reads information about target. | ||
25 | ---@param codemodel_target table | ||
26 | ---@param reply_dir table | ||
27 | ---@return table | ||
28 | local function get_target_info(codemodel_target, reply_dir) return vim.json.decode((reply_dir / codemodel_target['jsonFile']):read()) end | ||
29 | |||
30 | --- Creates query files that to acess information about targets after CMake configuration. | ||
31 | ---@param build_dir table | ||
32 | ---@return boolean: Returns `true` on success. | ||
33 | local function make_query_files(build_dir) | ||
34 | local query_dir = build_dir / '.cmake' / 'api' / 'v1' / 'query' | ||
35 | if not query_dir:mkdir({ parents = true }) then | ||
36 | utils.notify(string.format('Unable to create "%s"', query_dir.filename), vim.log.levels.ERROR) | ||
37 | return false | ||
38 | end | ||
39 | |||
40 | local codemodel_file = query_dir / 'codemodel-v2' | ||
41 | if not codemodel_file:is_file() then | ||
42 | if not codemodel_file:touch() then | ||
43 | utils.notify(string.format('Unable to create "%s"', codemodel_file.filename), vim.log.levels.ERROR) | ||
44 | return false | ||
45 | end | ||
46 | end | ||
47 | return true | ||
48 | end | ||
49 | |||
50 | --- Reads targets information. | ||
51 | ---@param reply_dir table | ||
52 | ---@return table? | ||
53 | local function get_codemodel_targets(reply_dir) | ||
54 | local found_files = scandir.scan_dir(reply_dir.filename, { search_pattern = 'codemodel*' }) | ||
55 | if #found_files == 0 then | ||
56 | utils.notify('Unable to find codemodel file', vim.log.levels.ERROR) | ||
57 | return nil | ||
58 | end | ||
59 | local codemodel = Path:new(found_files[1]) | ||
60 | local codemodel_json = vim.json.decode(codemodel:read()) | ||
61 | return codemodel_json['configurations'][1]['targets'] | ||
62 | end | ||
63 | |||
64 | ---@return table? | ||
65 | local function get_target_names() | ||
66 | local project_config = ProjectConfig.new() | ||
67 | local build_dir = parse_dir(project_config.cmake.build_dir, project_config.cmake.build_type) | ||
68 | if not build_dir:is_dir() then | ||
69 | utils.notify(string.format('Build directory "%s" does not exist, you need to run "configure" task first', build_dir), vim.log.levels.ERROR) | ||
70 | return nil | ||
71 | end | ||
72 | |||
73 | local reply_dir = get_reply_dir(build_dir) | ||
74 | local codemodel_targets = get_codemodel_targets(reply_dir) | ||
75 | if not codemodel_targets then | ||
76 | return nil | ||
77 | end | ||
78 | |||
79 | local targets = {} | ||
80 | for _, target in ipairs(codemodel_targets) do | ||
81 | local target_info = get_target_info(target, reply_dir) | ||
82 | local target_name = target_info['name'] | ||
83 | if target_name:find('_autogen') == nil then | ||
84 | table.insert(targets, target_name) | ||
85 | end | ||
86 | end | ||
87 | |||
88 | return targets | ||
89 | end | ||
90 | |||
91 | --- Finds path to an executable. | ||
92 | ---@param build_dir table | ||
93 | ---@param name string | ||
94 | ---@param reply_dir table | ||
95 | ---@return unknown? | ||
96 | local function get_executable_path(build_dir, name, reply_dir) | ||
97 | for _, target in ipairs(get_codemodel_targets(reply_dir)) do | ||
98 | if name == target['name'] then | ||
99 | local target_info = get_target_info(target, reply_dir) | ||
100 | if target_info['type'] ~= 'EXECUTABLE' then | ||
101 | utils.notify(string.format('Specified target "%s" is not an executable', name), vim.log.levels.ERROR) | ||
102 | return nil | ||
103 | end | ||
104 | |||
105 | local target_path = Path:new(target_info['artifacts'][1]['path']) | ||
106 | if not target_path:is_absolute() then | ||
107 | target_path = build_dir / target_path | ||
108 | end | ||
109 | |||
110 | return target_path | ||
111 | end | ||
112 | end | ||
113 | |||
114 | utils.notify(string.format('Unable to find target named "%s"', name), vim.log.levels.ERROR) | ||
115 | return nil | ||
116 | end | ||
117 | |||
118 | --- Copies compile_commands.json file from build directory to the current working directory for LSP integration. | ||
119 | local function copy_compile_commands() | ||
120 | local project_config = ProjectConfig.new() | ||
121 | local filename = 'compile_commands.json' | ||
122 | local source = parse_dir(project_config.cmake.build_dir, project_config.cmake.build_type) / filename | ||
123 | local destination = Path:new(vim.loop.cwd(), filename) | ||
124 | source:copy({ destination = destination.filename }) | ||
125 | end | ||
126 | |||
127 | --- Task | ||
128 | ---@param module_config table | ||
129 | ---@return table? | ||
130 | local function configure(module_config, _) | ||
131 | local build_dir = parse_dir(module_config.build_dir, module_config.build_type) | ||
132 | build_dir:mkdir({ parents = true }) | ||
133 | if not make_query_files(build_dir) then | ||
134 | return nil | ||
135 | end | ||
136 | |||
137 | return { | ||
138 | cmd = module_config.cmd, | ||
139 | args = { '-B', build_dir.filename, '-D', 'CMAKE_BUILD_TYPE=' .. module_config.build_type }, | ||
140 | after_success = copy_compile_commands, | ||
141 | } | ||
142 | end | ||
143 | |||
144 | --- Task | ||
145 | ---@param module_config table | ||
146 | ---@return table | ||
147 | local function build(module_config, _) | ||
148 | local build_dir = parse_dir(module_config.build_dir, module_config.build_type) | ||
149 | |||
150 | local args = { '--build', build_dir.filename } | ||
151 | if module_config.target then | ||
152 | vim.list_extend(args, { '--target', module_config.target }) | ||
153 | end | ||
154 | |||
155 | return { | ||
156 | cmd = module_config.cmd, | ||
157 | args = args, | ||
158 | after_success = copy_compile_commands, | ||
159 | } | ||
160 | end | ||
161 | |||
162 | --- Task | ||
163 | ---@param module_config table | ||
164 | ---@return table | ||
165 | local function build_all(module_config, _) | ||
166 | local build_dir = parse_dir(module_config.build_dir, module_config.build_type) | ||
167 | |||
168 | return { | ||
169 | cmd = module_config.cmd, | ||
170 | args = { '--build', build_dir.filename }, | ||
171 | after_success = copy_compile_commands, | ||
172 | } | ||
173 | end | ||
174 | |||
175 | --- Task | ||
176 | ---@param module_config table | ||
177 | ---@return table? | ||
178 | local function run(module_config, _) | ||
179 | if not module_config.target then | ||
180 | utils.notify('No selected target, please set "target" parameter', vim.log.levels.ERROR) | ||
181 | return nil | ||
182 | end | ||
183 | |||
184 | local build_dir = parse_dir(module_config.build_dir, module_config.build_type) | ||
185 | if not build_dir:is_dir() then | ||
186 | utils.notify(string.format('Build directory "%s" does not exist, you need to run "configure" task first', build_dir), vim.log.levels.ERROR) | ||
187 | return nil | ||
188 | end | ||
189 | |||
190 | local target_path = get_executable_path(build_dir, module_config.target, get_reply_dir(build_dir)) | ||
191 | if not target_path then | ||
192 | return | ||
193 | end | ||
194 | |||
195 | if not target_path:is_file() then | ||
196 | utils.notify(string.format('Selected target "%s" is not built', target_path.filename), vim.log.levels.ERROR) | ||
197 | return nil | ||
198 | end | ||
199 | |||
200 | return { | ||
201 | cmd = target_path.filename, | ||
202 | cwd = target_path:parent().filename, | ||
203 | } | ||
204 | end | ||
205 | |||
206 | --- Task | ||
207 | ---@param module_config table | ||
208 | ---@return table? | ||
209 | local function debug(module_config, _) | ||
210 | if module_config.build_type ~= 'Debug' and module_config.build_type ~= 'RelWithDebInfo' then | ||
211 | utils.notify( | ||
212 | string.format('For debugging your "build_type" param should be set to "Debug" or "RelWithDebInfo", but your current build type is "%s"'), | ||
213 | module_config.build_type, | ||
214 | vim.log.levels.ERROR | ||
215 | ) | ||
216 | return nil | ||
217 | end | ||
218 | |||
219 | local command = run(module_config, nil) | ||
220 | if not command then | ||
221 | return nil | ||
222 | end | ||
223 | |||
224 | command.dap_name = module_config.dap_name | ||
225 | return command | ||
226 | end | ||
227 | |||
228 | --- Task | ||
229 | ---@param module_config table | ||
230 | ---@return table | ||
231 | local function clean(module_config, _) | ||
232 | local build_dir = parse_dir(module_config.build_dir, module_config.build_type) | ||
233 | |||
234 | return { | ||
235 | cmd = module_config.cmd, | ||
236 | args = { '--build', build_dir.filename, '--target', 'clean' }, | ||
237 | after_success = copy_compile_commands, | ||
238 | } | ||
239 | end | ||
240 | |||
241 | --- Task | ||
242 | ---@param module_config table | ||
243 | ---@return table | ||
244 | local function open_build_dir(module_config, _) | ||
245 | local build_dir = parse_dir(module_config.build_dir, module_config.build_type) | ||
246 | |||
247 | return { | ||
248 | cmd = os == 'windows' and 'start' or 'xdg-open', | ||
249 | args = { build_dir.filename }, | ||
250 | ignore_stdout = true, | ||
251 | ignore_stderr = true, | ||
252 | } | ||
253 | end | ||
254 | |||
255 | cmake.params = { | ||
256 | target = get_target_names, | ||
257 | build_type = { 'Debug', 'Release', 'RelWithDebInfo', 'MinSizeRel' }, | ||
258 | 'cmake', | ||
259 | 'dap_name', | ||
260 | } | ||
261 | cmake.condition = function() return Path:new('CMakeLists.txt'):exists() end | ||
262 | cmake.tasks = { | ||
263 | configure = configure, | ||
264 | build = build, | ||
265 | build_all = build_all, | ||
266 | run = { build, run }, | ||
267 | debug = debug, | ||
268 | clean = clean, | ||
269 | open_build_dir = open_build_dir, | ||
270 | } | ||
271 | |||
272 | return cmake | ||