Skip to content

Commit

Permalink
Presets refactoring (#255)
Browse files Browse the repository at this point in the history
* 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 <>
  • Loading branch information
lceWolf authored Nov 10, 2024
1 parent d8eb437 commit a5022a5
Show file tree
Hide file tree
Showing 6 changed files with 523 additions and 257 deletions.
4 changes: 2 additions & 2 deletions docs/cmake_presets.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ Read more about CMake presets from [CMake docs](https://cmake.org/cmake/help/lat
1. [Test Preset](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html#test-preset) is not supported.
2. [Package Preset](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html#package-preset) is not supported.
3. [Workflow Preset](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html#workflow-preset) is not supported.
4. [Condition](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html#condition) is not supported.
5. Some macros not supported yet: `$env{<variable-name>}`, `$penv{<variable-name>}`, `$vendor{<macro-name>}`
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
5. Some macros not supported yet: `$vendor{<macro-name>}`
7 changes: 7 additions & 0 deletions lua/cmake-tools/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ local Config = {
use_preset = true,
generate_options = {},
build_options = {},
show_disabled_build_presets = true,
}, -- general config
target_settings = {}, -- target specific config
executor = nil,
Expand All @@ -41,6 +42,8 @@ function Config:new(const)
obj.base_settings.build_options = const.cmake_build_options
obj.base_settings.use_preset = const.cmake_use_preset

obj.base_settings.show_disabled_build_presets = const.cmake_show_disabled_build_presets

obj.executor = const.cmake_executor
obj.runner = const.cmake_runner

Expand Down Expand Up @@ -134,6 +137,10 @@ function Config:build_options()
return self.base_settings.build_options and self.base_settings.build_options or {}
end

function Config:show_disabled_build_presets()
return self.base_settings.show_disabled_build_presets
end

function Config:generate_build_directory()
local build_directory = Path:new(self.build_directory)

Expand Down
1 change: 1 addition & 0 deletions lua/cmake-tools/const.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ local const = {
cmake_regenerate_on_save = true, -- auto generate when save CMakeLists.txt
cmake_generate_options = { "-DCMAKE_EXPORT_COMPILE_COMMANDS=1" }, -- this will be passed when invoke `CMakeGenerate`
cmake_build_options = {}, -- this will be passed when invoke `CMakeBuild`
cmake_show_disabled_build_presets = true,
cmake_build_directory = function()
if osys.iswin32 then
return "out\\${variant:buildType}"
Expand Down
228 changes: 122 additions & 106 deletions lua/cmake-tools/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ local const = require("cmake-tools.const")
local Config = require("cmake-tools.config")
local variants = require("cmake-tools.variants")
local kits = require("cmake-tools.kits")
local presets = require("cmake-tools.presets")
local Presets = require("cmake-tools.presets")
local log = require("cmake-tools.log")
local hints = require("cmake-tools.hints")
local _session = require("cmake-tools.session")
Expand All @@ -14,6 +14,7 @@ local environment = require("cmake-tools.environment")
local file_picker = require("cmake-tools.file_picker")
local scratch = require("cmake-tools.scratch")
local Result = require("cmake-tools.result")
local Path = require("plenary.path")

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

Expand Down Expand Up @@ -110,56 +111,83 @@ function cmake.generate(opt, callback)
-- if exists presets, preset include all info that cmake
-- needed to execute, so we don't use cmake-kits.json and
-- cmake-variants.[json|yaml] event they exist.
local presets_file = config.base_settings.use_preset and presets.check(config.cwd)
if presets_file and not config.configure_preset then
-- this will also set value for build type from preset.
-- default to be "Debug"
return cmake.select_configure_preset(function(result)
if not result:is_ok() then
callback(result)
return
local presets_exists = config.base_settings.use_preset and Presets.exists(config.cwd)
if presets_exists then
local presets = Presets:parse(config.cwd)

if not config.configure_preset then
-- try to determine the confiure preset based on the build preset
if config.build_preset then
local build_preset = presets:get_build_preset(config.build_preset)
if build_preset then
local preset =
presets:get_configure_preset(build_preset.configurePreset, { include_hidden = true })
if preset then
config.configure_preset = preset.name
end
end
end
cmake.generate(opt, callback)
end)
end

if presets_file and config.configure_preset then
-- if exsist preset file and set configure preset, then
-- set build directory to the `binaryDir` option of `configurePresets`
local build_directory, no_expand_build_directory = presets.get_build_dir(
presets.get_preset_by_name(config.configure_preset, "configurePresets", config.cwd),
config.cwd
)
if build_directory ~= "" then
config:update_build_dir(build_directory, no_expand_build_directory)
if not config.configure_preset then
-- this will also set value for build type from preset.
-- default to be "Debug"
return cmake.select_configure_preset(function(result)
if not result:is_ok() then
callback(result)
return
end
cmake.generate(opt, callback)
end)
end
return
end
config:generate_build_directory()

local args = {
"--preset",
config.configure_preset,
}
vim.list_extend(args, config:generate_options())
vim.list_extend(args, fargs)

local env = environment.get_build_environment(config)
local cmd = const.cmake_command
return utils.execute(
cmd,
config.env_script,
env,
args,
config.cwd,
config.executor,
---@param result cmake.Result
function(result)
callback(result)
if result:is_ok() then
cmake.configure_compile_commands()
cmake.create_regenerate_on_save_autocmd()
if config.configure_preset then
-- if exsist preset file and set configure preset, then
-- set build directory to the `binaryDir` option of `configurePresets`
local preset = presets:get_configure_preset(config.configure_preset)
if not preset then
config.configure_preset = nil
if config.build_preset then
local build_preset = presets:get_build_preset(config.build_preset)
if not build_preset then
config.build_preset = nil
end
end
return
end
)
local build_directory, no_expand_build_directory = preset.binaryDirExpanded, preset.binaryDir
if build_directory ~= "" then
config:update_build_dir(build_directory, no_expand_build_directory)
end
config:generate_build_directory()

local args = {
"--preset",
config.configure_preset,
}
vim.list_extend(args, config:generate_options())
vim.list_extend(args, fargs)

local env = environment.get_build_environment(config)
local cmd = const.cmake_command
return utils.execute(
cmd,
config.env_script,
env,
args,
config.cwd,
config.executor,
---@param result cmake.Result
function(result)
callback(result)
if result:is_ok() then
cmake.configure_compile_commands()
cmake.create_regenerate_on_save_autocmd()
end
end
)
end
end

-- if exists cmake-kits.json, kit is used to set
Expand Down Expand Up @@ -247,24 +275,24 @@ function cmake.clean(callback)
return
end

local path = Path:new(
utils.transform_path(config:build_directory_path(), config.executor.name == "quickfix")
)
if not (path / "CMakeCache.txt"):exists() then
-- no need to clean up as we do not have a cache
return
end

local args = {
"--build",
utils.transform_path(config:build_directory_path(), config.executor.name == "quickfix"),
path.filename,
"--target",
"clean",
}

local env = environment.get_build_environment(config)
local cmd = const.cmake_command
return utils.execute(
cmd,
config.env_script,
env,
args,
config.cwd,
config.executor,
callback
)
return utils.execute(cmd, config.env_script, env, args, config.cwd, config.executor, callback)
end

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

local args
local presets_file = config.base_settings.use_preset and presets.check(config.cwd)
local presets_exists = config.base_settings.use_preset and Presets.exists(config.cwd)

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

local env = environment.get_build_environment(config)
local cmd = const.cmake_command
return utils.execute(
cmd,
config.env_script,
env,
args,
config.cwd,
config.executor,
callback
)
return utils.execute(cmd, config.env_script, env, args, config.cwd, config.executor, callback)
end

function cmake.quick_build(opt, callback)
Expand Down Expand Up @@ -503,15 +523,7 @@ function cmake.run(opt, callback)
local env = environment.get_run_environment(config, opt.target)
local _args = opt.args and opt.args or config.target_settings[opt.target].args
local cmd = target_path
utils.run(
cmd,
config.env_script,
env,
_args,
launch_path,
config.runner,
callback
)
utils.run(cmd, config.env_script, env, _args, launch_path, config.runner, callback)
end)
else
local result = config:get_launch_target()
Expand Down Expand Up @@ -734,14 +746,11 @@ function cmake.select_configure_preset(callback)
end

-- if exists presets
local presets_file = presets.check(config.cwd)
if presets_file then
local configure_preset_names =
presets.parse("configurePresets", { include_hidden = false }, config.cwd)
local configure_presets =
presets.parse_name_mapped("configurePresets", { include_hidden = false }, config.cwd)
if Presets.exists(config.cwd) then
local presets = Presets:parse(config.cwd)
local configure_preset_names = presets:get_configure_preset_names()
local format_preset_name = function(p_name)
local p = configure_presets[p_name]
local p = presets:get_configure_preset(p_name)
return p.displayName or p.name
end
vim.ui.select(
Expand All @@ -757,9 +766,7 @@ function cmake.select_configure_preset(callback)
end
if config.configure_preset ~= choice then
config.configure_preset = choice
config.build_type = presets.get_build_type(
presets.get_preset_by_name(choice, "configurePresets", config.cwd)
)
config.build_type = presets:get_configure_preset(choice):get_build_type()
end
callback(Result:new(Types.SUCCESS, nil, nil))
end)
Expand Down Expand Up @@ -788,15 +795,16 @@ function cmake.select_build_preset(callback)
end

-- if exists presets
local presets_file = presets.check(config.cwd)
if presets_file then
local build_preset_names = presets.parse("buildPresets", { include_hidden = false }, config.cwd)
local build_presets =
presets.parse_name_mapped("buildPresets", { include_hidden = false }, config.cwd)
if Presets.exists(config.cwd) then
local presets = Presets:parse(config.cwd)
local build_preset_names =
presets:get_build_preset_names({ include_disabled = config:show_disabled_build_presets() })
build_preset_names = vim.list_extend(build_preset_names, { "None" })
build_presets = vim.tbl_extend("keep", build_presets, { None = { displayName = "None" } })
local format_preset_name = function(p_name)
local p = build_presets[p_name]
if p_name == "None" then
return p_name
end
local p = presets:get_build_preset(p_name)
return p.displayName or p.name
end
vim.ui.select(
Expand All @@ -813,14 +821,17 @@ function cmake.select_build_preset(callback)
if config.build_preset ~= choice then
config.build_preset = choice
end
local associated_configure_preset =
presets.get_preset_by_name(choice, "buildPresets", config.cwd)["configurePreset"]
local associated_configure_preset = presets:get_configure_preset(
presets:get_build_preset(choice).configurePreset,
{ include_hidden = true }
)
local associated_configure_preset_name = associated_configure_preset
and associated_configure_preset.name
or nil
local configure_preset_updated = false

if
associated_configure_preset and config.configure_preset ~= associated_configure_preset
then
config.configure_preset = associated_configure_preset
if config.configure_preset ~= associated_configure_preset_name then
config.configure_preset = associated_configure_preset_name
configure_preset_updated = true
end

Expand Down Expand Up @@ -1234,8 +1245,7 @@ function cmake.is_cmake_project()
end

function cmake.has_cmake_preset()
local presets_file = presets.check(config.cwd)
return presets_file ~= nil
return Presets.exists(config.cwd)
end

function cmake.get_build_targets()
Expand Down Expand Up @@ -1391,8 +1401,8 @@ function cmake.create_regenerate_on_save_autocmd()
table.insert(pattern, ss)
end

local presets_file = config.base_settings.use_preset and presets.check(config.cwd)
if presets_file then
local presets_exists = config.base_settings.use_preset and Presets.exists(config.cwd)
if presets_exists then
for _, item in ipairs({
"CMakePresets.json",
"CMakeUserPresets.json",
Expand Down Expand Up @@ -1423,10 +1433,16 @@ function cmake.create_regenerate_on_save_autocmd()
local buf = vim.api.nvim_get_current_buf()
-- Check if buffer is actually modified, and only if it is modified,
-- execute the :CMakeGenerate, otherwise return. This is to avoid unnecessary regenerattion
local buf_modified = vim.api.nvim_buf_get_option(buf, "modified")
if buf_modified then
cmake.generate({ bang = false, fargs = {} }, nil)
config:update_targets()
if vim.api.nvim_buf_get_option(buf, "modified") then
vim.api.nvim_create_autocmd("BufWritePost", {
group = group,
once = true,
buffer = buf,
callback = function()
cmake.generate({ bang = false, fargs = {} }, nil)
config:update_targets()
end,
})
end
end,
})
Expand Down
Loading

0 comments on commit a5022a5

Please sign in to comment.