Skip to content

Commit a5022a5

Browse files
authored
Presets refactoring (#255)
* feat: parse CMakePresets if CMakeUserPresets is found This commits enables parsing the Presets file even if a CMakeUserPreset file was found. That way it is possible to look up inherited presets from the CMakePresets file when a CMakeUserPresets file is found * perf: reduce amount of disk access * feat: support environment variables for build_dir * feat: class structure for presets * refac: make accessing build and configure presets more convenient * feat: resolve cache variables * feat: partial conditions support * refac: accumulate tables while inheriting * feat: reset configure preset if it got disabled * refac: only call cmake --target clean when cache is available This prevents a "cache not available" error * fix: only generate after BufWritePost This fixes an issue when the user has to select a configure preset while generating the config. If the generation is done at BufWritePre, the buffer is written while the popup is still shown, which moves the cursor back to the now written buffer. Selecting the popup to choose a preset is not possible anymore. Writing the Buffer first and then prompting the user to select a configure preset bypasses that issue * feat: try to find configure preset based on build preset When a previously unavailable configure preset referenced by the current build preset, we try to auto-select the configure preset and only ask the user if there is still no configure preset available for the current build preset * chore: update documentation * feat: config option to hide build presets with a disabled configure preset This is a pure personal take which is not default cmake beheviour. When working with a CMake[User]Preset.json which contains lots of configure and build presets, but only some of the configure presets are active, having a huge list of buid presets, whose configurePreset is disabled and can not be used, is a bit of a nuisance. To help with that, with the newly added option these build presets can be hidden from the selection list of available presets * fix: use breadth first search for environment and cache variables just like conditions, these have to be parsed layer by layer. Building these structures via recursion does not honor the inheritance order for multiple inherits on a single preset * feat: support penv macro expansion --------- Co-authored-by: Denzel <>
1 parent d8eb437 commit a5022a5

6 files changed

Lines changed: 523 additions & 257 deletions

File tree

docs/cmake_presets.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ Read more about CMake presets from [CMake docs](https://cmake.org/cmake/help/lat
1111
1. [Test Preset](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html#test-preset) is not supported.
1212
2. [Package Preset](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html#package-preset) is not supported.
1313
3. [Workflow Preset](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html#workflow-preset) is not supported.
14-
4. [Condition](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html#condition) is not supported.
15-
5. Some macros not supported yet: `$env{<variable-name>}`, `$penv{<variable-name>}`, `$vendor{<macro-name>}`
14+
4. [Condition](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html#condition) mostly supported. Types `matches` and `notMatches` currently not supported due to lua's differences in regex capabilities
15+
5. Some macros not supported yet: `$vendor{<macro-name>}`

lua/cmake-tools/config.lua

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ local Config = {
2323
use_preset = true,
2424
generate_options = {},
2525
build_options = {},
26+
show_disabled_build_presets = true,
2627
}, -- general config
2728
target_settings = {}, -- target specific config
2829
executor = nil,
@@ -41,6 +42,8 @@ function Config:new(const)
4142
obj.base_settings.build_options = const.cmake_build_options
4243
obj.base_settings.use_preset = const.cmake_use_preset
4344

45+
obj.base_settings.show_disabled_build_presets = const.cmake_show_disabled_build_presets
46+
4447
obj.executor = const.cmake_executor
4548
obj.runner = const.cmake_runner
4649

@@ -134,6 +137,10 @@ function Config:build_options()
134137
return self.base_settings.build_options and self.base_settings.build_options or {}
135138
end
136139

140+
function Config:show_disabled_build_presets()
141+
return self.base_settings.show_disabled_build_presets
142+
end
143+
137144
function Config:generate_build_directory()
138145
local build_directory = Path:new(self.build_directory)
139146

lua/cmake-tools/const.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ local const = {
66
cmake_regenerate_on_save = true, -- auto generate when save CMakeLists.txt
77
cmake_generate_options = { "-DCMAKE_EXPORT_COMPILE_COMMANDS=1" }, -- this will be passed when invoke `CMakeGenerate`
88
cmake_build_options = {}, -- this will be passed when invoke `CMakeBuild`
9+
cmake_show_disabled_build_presets = true,
910
cmake_build_directory = function()
1011
if osys.iswin32 then
1112
return "out\\${variant:buildType}"

lua/cmake-tools/init.lua

Lines changed: 122 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ local const = require("cmake-tools.const")
55
local Config = require("cmake-tools.config")
66
local variants = require("cmake-tools.variants")
77
local kits = require("cmake-tools.kits")
8-
local presets = require("cmake-tools.presets")
8+
local Presets = require("cmake-tools.presets")
99
local log = require("cmake-tools.log")
1010
local hints = require("cmake-tools.hints")
1111
local _session = require("cmake-tools.session")
@@ -14,6 +14,7 @@ local environment = require("cmake-tools.environment")
1414
local file_picker = require("cmake-tools.file_picker")
1515
local scratch = require("cmake-tools.scratch")
1616
local Result = require("cmake-tools.result")
17+
local Path = require("plenary.path")
1718

1819
local ctest = require("cmake-tools.test.ctest")
1920

@@ -110,56 +111,83 @@ function cmake.generate(opt, callback)
110111
-- if exists presets, preset include all info that cmake
111112
-- needed to execute, so we don't use cmake-kits.json and
112113
-- cmake-variants.[json|yaml] event they exist.
113-
local presets_file = config.base_settings.use_preset and presets.check(config.cwd)
114-
if presets_file and not config.configure_preset then
115-
-- this will also set value for build type from preset.
116-
-- default to be "Debug"
117-
return cmake.select_configure_preset(function(result)
118-
if not result:is_ok() then
119-
callback(result)
120-
return
114+
local presets_exists = config.base_settings.use_preset and Presets.exists(config.cwd)
115+
if presets_exists then
116+
local presets = Presets:parse(config.cwd)
117+
118+
if not config.configure_preset then
119+
-- try to determine the confiure preset based on the build preset
120+
if config.build_preset then
121+
local build_preset = presets:get_build_preset(config.build_preset)
122+
if build_preset then
123+
local preset =
124+
presets:get_configure_preset(build_preset.configurePreset, { include_hidden = true })
125+
if preset then
126+
config.configure_preset = preset.name
127+
end
128+
end
121129
end
122-
cmake.generate(opt, callback)
123-
end)
124-
end
125130

126-
if presets_file and config.configure_preset then
127-
-- if exsist preset file and set configure preset, then
128-
-- set build directory to the `binaryDir` option of `configurePresets`
129-
local build_directory, no_expand_build_directory = presets.get_build_dir(
130-
presets.get_preset_by_name(config.configure_preset, "configurePresets", config.cwd),
131-
config.cwd
132-
)
133-
if build_directory ~= "" then
134-
config:update_build_dir(build_directory, no_expand_build_directory)
131+
if not config.configure_preset then
132+
-- this will also set value for build type from preset.
133+
-- default to be "Debug"
134+
return cmake.select_configure_preset(function(result)
135+
if not result:is_ok() then
136+
callback(result)
137+
return
138+
end
139+
cmake.generate(opt, callback)
140+
end)
141+
end
142+
return
135143
end
136-
config:generate_build_directory()
137-
138-
local args = {
139-
"--preset",
140-
config.configure_preset,
141-
}
142-
vim.list_extend(args, config:generate_options())
143-
vim.list_extend(args, fargs)
144144

145-
local env = environment.get_build_environment(config)
146-
local cmd = const.cmake_command
147-
return utils.execute(
148-
cmd,
149-
config.env_script,
150-
env,
151-
args,
152-
config.cwd,
153-
config.executor,
154-
---@param result cmake.Result
155-
function(result)
156-
callback(result)
157-
if result:is_ok() then
158-
cmake.configure_compile_commands()
159-
cmake.create_regenerate_on_save_autocmd()
145+
if config.configure_preset then
146+
-- if exsist preset file and set configure preset, then
147+
-- set build directory to the `binaryDir` option of `configurePresets`
148+
local preset = presets:get_configure_preset(config.configure_preset)
149+
if not preset then
150+
config.configure_preset = nil
151+
if config.build_preset then
152+
local build_preset = presets:get_build_preset(config.build_preset)
153+
if not build_preset then
154+
config.build_preset = nil
155+
end
160156
end
157+
return
161158
end
162-
)
159+
local build_directory, no_expand_build_directory = preset.binaryDirExpanded, preset.binaryDir
160+
if build_directory ~= "" then
161+
config:update_build_dir(build_directory, no_expand_build_directory)
162+
end
163+
config:generate_build_directory()
164+
165+
local args = {
166+
"--preset",
167+
config.configure_preset,
168+
}
169+
vim.list_extend(args, config:generate_options())
170+
vim.list_extend(args, fargs)
171+
172+
local env = environment.get_build_environment(config)
173+
local cmd = const.cmake_command
174+
return utils.execute(
175+
cmd,
176+
config.env_script,
177+
env,
178+
args,
179+
config.cwd,
180+
config.executor,
181+
---@param result cmake.Result
182+
function(result)
183+
callback(result)
184+
if result:is_ok() then
185+
cmake.configure_compile_commands()
186+
cmake.create_regenerate_on_save_autocmd()
187+
end
188+
end
189+
)
190+
end
163191
end
164192

165193
-- if exists cmake-kits.json, kit is used to set
@@ -247,24 +275,24 @@ function cmake.clean(callback)
247275
return
248276
end
249277

278+
local path = Path:new(
279+
utils.transform_path(config:build_directory_path(), config.executor.name == "quickfix")
280+
)
281+
if not (path / "CMakeCache.txt"):exists() then
282+
-- no need to clean up as we do not have a cache
283+
return
284+
end
285+
250286
local args = {
251287
"--build",
252-
utils.transform_path(config:build_directory_path(), config.executor.name == "quickfix"),
288+
path.filename,
253289
"--target",
254290
"clean",
255291
}
256292

257293
local env = environment.get_build_environment(config)
258294
local cmd = const.cmake_command
259-
return utils.execute(
260-
cmd,
261-
config.env_script,
262-
env,
263-
args,
264-
config.cwd,
265-
config.executor,
266-
callback
267-
)
295+
return utils.execute(cmd, config.env_script, env, args, config.cwd, config.executor, callback)
268296
end
269297

270298
--- Build this project using the make toolchain of target platform
@@ -314,9 +342,9 @@ function cmake.build(opt, callback)
314342
end
315343

316344
local args
317-
local presets_file = config.base_settings.use_preset and presets.check(config.cwd)
345+
local presets_exists = config.base_settings.use_preset and Presets.exists(config.cwd)
318346

319-
if presets_file and config.build_preset then
347+
if presets_exists and config.build_preset then
320348
args = { "--build", "--preset", config.build_preset } -- preset don't need define build dir.
321349
else
322350
args = {
@@ -340,15 +368,7 @@ function cmake.build(opt, callback)
340368

341369
local env = environment.get_build_environment(config)
342370
local cmd = const.cmake_command
343-
return utils.execute(
344-
cmd,
345-
config.env_script,
346-
env,
347-
args,
348-
config.cwd,
349-
config.executor,
350-
callback
351-
)
371+
return utils.execute(cmd, config.env_script, env, args, config.cwd, config.executor, callback)
352372
end
353373

354374
function cmake.quick_build(opt, callback)
@@ -503,15 +523,7 @@ function cmake.run(opt, callback)
503523
local env = environment.get_run_environment(config, opt.target)
504524
local _args = opt.args and opt.args or config.target_settings[opt.target].args
505525
local cmd = target_path
506-
utils.run(
507-
cmd,
508-
config.env_script,
509-
env,
510-
_args,
511-
launch_path,
512-
config.runner,
513-
callback
514-
)
526+
utils.run(cmd, config.env_script, env, _args, launch_path, config.runner, callback)
515527
end)
516528
else
517529
local result = config:get_launch_target()
@@ -734,14 +746,11 @@ function cmake.select_configure_preset(callback)
734746
end
735747

736748
-- if exists presets
737-
local presets_file = presets.check(config.cwd)
738-
if presets_file then
739-
local configure_preset_names =
740-
presets.parse("configurePresets", { include_hidden = false }, config.cwd)
741-
local configure_presets =
742-
presets.parse_name_mapped("configurePresets", { include_hidden = false }, config.cwd)
749+
if Presets.exists(config.cwd) then
750+
local presets = Presets:parse(config.cwd)
751+
local configure_preset_names = presets:get_configure_preset_names()
743752
local format_preset_name = function(p_name)
744-
local p = configure_presets[p_name]
753+
local p = presets:get_configure_preset(p_name)
745754
return p.displayName or p.name
746755
end
747756
vim.ui.select(
@@ -757,9 +766,7 @@ function cmake.select_configure_preset(callback)
757766
end
758767
if config.configure_preset ~= choice then
759768
config.configure_preset = choice
760-
config.build_type = presets.get_build_type(
761-
presets.get_preset_by_name(choice, "configurePresets", config.cwd)
762-
)
769+
config.build_type = presets:get_configure_preset(choice):get_build_type()
763770
end
764771
callback(Result:new(Types.SUCCESS, nil, nil))
765772
end)
@@ -788,15 +795,16 @@ function cmake.select_build_preset(callback)
788795
end
789796

790797
-- if exists presets
791-
local presets_file = presets.check(config.cwd)
792-
if presets_file then
793-
local build_preset_names = presets.parse("buildPresets", { include_hidden = false }, config.cwd)
794-
local build_presets =
795-
presets.parse_name_mapped("buildPresets", { include_hidden = false }, config.cwd)
798+
if Presets.exists(config.cwd) then
799+
local presets = Presets:parse(config.cwd)
800+
local build_preset_names =
801+
presets:get_build_preset_names({ include_disabled = config:show_disabled_build_presets() })
796802
build_preset_names = vim.list_extend(build_preset_names, { "None" })
797-
build_presets = vim.tbl_extend("keep", build_presets, { None = { displayName = "None" } })
798803
local format_preset_name = function(p_name)
799-
local p = build_presets[p_name]
804+
if p_name == "None" then
805+
return p_name
806+
end
807+
local p = presets:get_build_preset(p_name)
800808
return p.displayName or p.name
801809
end
802810
vim.ui.select(
@@ -813,14 +821,17 @@ function cmake.select_build_preset(callback)
813821
if config.build_preset ~= choice then
814822
config.build_preset = choice
815823
end
816-
local associated_configure_preset =
817-
presets.get_preset_by_name(choice, "buildPresets", config.cwd)["configurePreset"]
824+
local associated_configure_preset = presets:get_configure_preset(
825+
presets:get_build_preset(choice).configurePreset,
826+
{ include_hidden = true }
827+
)
828+
local associated_configure_preset_name = associated_configure_preset
829+
and associated_configure_preset.name
830+
or nil
818831
local configure_preset_updated = false
819832

820-
if
821-
associated_configure_preset and config.configure_preset ~= associated_configure_preset
822-
then
823-
config.configure_preset = associated_configure_preset
833+
if config.configure_preset ~= associated_configure_preset_name then
834+
config.configure_preset = associated_configure_preset_name
824835
configure_preset_updated = true
825836
end
826837

@@ -1234,8 +1245,7 @@ function cmake.is_cmake_project()
12341245
end
12351246

12361247
function cmake.has_cmake_preset()
1237-
local presets_file = presets.check(config.cwd)
1238-
return presets_file ~= nil
1248+
return Presets.exists(config.cwd)
12391249
end
12401250

12411251
function cmake.get_build_targets()
@@ -1391,8 +1401,8 @@ function cmake.create_regenerate_on_save_autocmd()
13911401
table.insert(pattern, ss)
13921402
end
13931403

1394-
local presets_file = config.base_settings.use_preset and presets.check(config.cwd)
1395-
if presets_file then
1404+
local presets_exists = config.base_settings.use_preset and Presets.exists(config.cwd)
1405+
if presets_exists then
13961406
for _, item in ipairs({
13971407
"CMakePresets.json",
13981408
"CMakeUserPresets.json",
@@ -1423,10 +1433,16 @@ function cmake.create_regenerate_on_save_autocmd()
14231433
local buf = vim.api.nvim_get_current_buf()
14241434
-- Check if buffer is actually modified, and only if it is modified,
14251435
-- execute the :CMakeGenerate, otherwise return. This is to avoid unnecessary regenerattion
1426-
local buf_modified = vim.api.nvim_buf_get_option(buf, "modified")
1427-
if buf_modified then
1428-
cmake.generate({ bang = false, fargs = {} }, nil)
1429-
config:update_targets()
1436+
if vim.api.nvim_buf_get_option(buf, "modified") then
1437+
vim.api.nvim_create_autocmd("BufWritePost", {
1438+
group = group,
1439+
once = true,
1440+
buffer = buf,
1441+
callback = function()
1442+
cmake.generate({ bang = false, fargs = {} }, nil)
1443+
config:update_targets()
1444+
end,
1445+
})
14301446
end
14311447
end,
14321448
})

0 commit comments

Comments
 (0)