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