diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | lua/cmake-explorer.lua | 68 | ||||
-rw-r--r-- | lua/cmake-explorer/build.lua | 107 | ||||
-rw-r--r-- | lua/cmake-explorer/config.lua | 18 | ||||
-rw-r--r-- | lua/cmake-explorer/constants.lua | 7 | ||||
-rw-r--r-- | lua/cmake-explorer/globals.lua | 29 | ||||
-rw-r--r-- | lua/cmake-explorer/project.lua | 133 | ||||
-rw-r--r-- | lua/cmake-explorer/runner.lua | 62 | ||||
-rw-r--r-- | lua/cmake-explorer/utils.lua | 25 |
9 files changed, 450 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bfa6551 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +notes diff --git a/lua/cmake-explorer.lua b/lua/cmake-explorer.lua new file mode 100644 index 0000000..8e01a57 --- /dev/null +++ b/lua/cmake-explorer.lua @@ -0,0 +1,68 @@ +local globals = require("cmake-explorer.globals") +local config = require("cmake-explorer.config") +local runner = require("cmake-explorer.runner") +local Project = require("cmake-explorer.project") +local Build = require("cmake-explorer.build") + +local M = {} + +local projects = {} + +local current_project = nil + +local function set_current_project(path) + if path then + for _, v in ipairs(projects) do + -- print(v.path:absolute() .. " ? " .. path) + if v.path:absolute() == path then + current_project = v + return + end + end + end + if #projects ~= 0 then + current_project = projects[1] + else + print("set_current_project. no projects available") + end +end + +function M.list_build_dirs() + if current_project then + vim.print(current_project:list_build_dirs_names()) + end +end + +function M.configure(opts) + print("configure. #projects " .. #projects) + if current_project then + runner.start(current_project:configure(opts)) + end +end + +M.setup = function(cfg) + cfg = cfg or {} + globals.setup() + config.setup(cfg) + + projects = { Project:new(vim.loop.cwd()) } + set_current_project() + + local cmd = vim.api.nvim_create_user_command + + cmd("CMakeConfigure", function(opts) + if #opts.fargs ~= 0 then + M.configure({ build_type = opts.fargs[1] }) + else + M.configure() + end + end, { -- opts + nargs = "*", + bang = true, + desc = "CMake configure", + }) + + cmd("CMakeListBuildDirs", M.list_build_dirs, { nargs = 0 }) +end + +return M diff --git a/lua/cmake-explorer/build.lua b/lua/cmake-explorer/build.lua new file mode 100644 index 0000000..ead0646 --- /dev/null +++ b/lua/cmake-explorer/build.lua @@ -0,0 +1,107 @@ +local config = require("cmake-explorer.config") +local Path = require("plenary.path") +local Scandir = require("plenary.scandir") + +-- initial action to create query files (before first build) +local init_query_dir = function(path) + Path:new(path, ".cmake", "api", "v1", "query", "codemodel-v2"):touch({ parents = true }) + Path:new(path, ".cmake", "api", "v1", "query", "cmakeFiles-v1"):touch({ parents = true }) + Path:new(path, ".cmake", "api", "v1", "reply"):mkdir({ parents = true }) +end + +local read_reply_dir = function(path) + local index, cmakefiles, codemodel, targets + local reply_dir = Path:new(path, ".cmake", "api", "v1", "reply") + if not reply_dir:exists() then + return + end + index = Scandir.scan_dir(reply_dir:absolute(), { search_pattern = "index*" }) + if #index == 0 then + return + end + index = vim.json.decode(Path:new(index[1]):read()) + for _, object in ipairs(index.objects) do + if object.kind == "codemodel" then + codemodel = vim.json.decode((reply_dir / object.jsonFile):read()) + for _, target in ipairs(codemodel.configurations[1].targets) do + targets = targets or {} + table.insert(targets, vim.json.decode((reply_dir / target.jsonFile):read())) + end + elseif object.kind == "cmakeFiles" then + cmakefiles = vim.json.decode(Path:new(reply_dir / object.jsonFile):read()) + end + end + return index, cmakefiles, codemodel, targets +end + +local Build = {} + +Build.__index = Build + +-- new buildsystem +function Build:new(o) + o = o or {} + + local path + if type(o) == "string" then + path = o + elseif type(o) == "table" and o.path then + path = o.path + else + print("Build.new wrong argument. type(o) = " .. type(o)) + return + end + + local obj = { + path = Path:new(path), + index = nil, + cmakefiles = nil, + codemodel = nil, + targets = nil, + } + + obj.path:mkdir({ parents = true }) + init_query_dir(path) + obj.index, obj.cmakefiles, obj.codemodel, obj.targets = read_reply_dir(path) + + setmetatable(obj, Build) + return obj +end + +-- update all internals +function Build:update() + self:set_codemodel() + self:set_cmakefiles() +end + +function Build:build() end + +function Build:generator() + return self.index.cmake.generator.name +end + +function Build:build_type() + return self.codemodel.configurations[1].name +end + +function Build:is_multiconfig() + return self.index.cmake.generator.multiConfig +end + +Build.name = function(opts) + return config.build_dir_template:gsub("{buildType}", opts.build_type or config.build_types[1]) +end + +Build.is_build_dir = function(path) + if not (Path:new(path):is_dir()) then + return + end + if not Path:new(path, ".cmake", "api", "v1", "query", "codemodel-v2"):exists() then + return + elseif not Path:new(path, ".cmake", "api", "v1", "query", "cmakeFiles-v1"):exists() then + return + end + return true +end + +return Build diff --git a/lua/cmake-explorer/config.lua b/lua/cmake-explorer/config.lua new file mode 100644 index 0000000..4e1740d --- /dev/null +++ b/lua/cmake-explorer/config.lua @@ -0,0 +1,18 @@ +local default_config = { + cmake_cmd = "cmake", + build_dir_template = "build-{buildType}", + build_types = { "Debug", "Release" }, + options = { "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" }, +} + +local M = vim.deepcopy(default_config) + +M.setup = function(opts) + local newconf = vim.tbl_deep_extend("force", default_config, opts or {}) + + for k, v in pairs(newconf) do + M[k] = v + end +end + +return M diff --git a/lua/cmake-explorer/constants.lua b/lua/cmake-explorer/constants.lua new file mode 100644 index 0000000..3a6fb1f --- /dev/null +++ b/lua/cmake-explorer/constants.lua @@ -0,0 +1,7 @@ +local Enum = require("overseer.enum") + +local M = {} + +M.TAG = Enum.new({ "CONFIGURE", "BUILD", "INSTALL", "TEST", "PACK" }) + +return M diff --git a/lua/cmake-explorer/globals.lua b/lua/cmake-explorer/globals.lua new file mode 100644 index 0000000..759bba4 --- /dev/null +++ b/lua/cmake-explorer/globals.lua @@ -0,0 +1,29 @@ +local config = require("cmake-explorer.config") + +local M = { + capabilities = nil, + generators = {}, +} + +local available_generators = function(capabilities) + local ret = {} + if not capabilities or not capabilities.generators then + return ret + end + for k, v in pairs(capabilities.generators) do + table.insert(ret, v.name) + end + return vim.fn.reverse(ret) +end + +local set_capabilities = function() + local output = vim.fn.system({ config.cmake_cmd, "-E", "capabilities" }) + M.capabilities = vim.json.decode(output) + M.generators = available_generators(M.capabilities) +end + +M.setup = function() + set_capabilities() +end + +return M diff --git a/lua/cmake-explorer/project.lua b/lua/cmake-explorer/project.lua new file mode 100644 index 0000000..967ec1b --- /dev/null +++ b/lua/cmake-explorer/project.lua @@ -0,0 +1,133 @@ +local config = require("cmake-explorer.config") +local globals = require("cmake-explorer.globals") +local Build = require("cmake-explorer.build") +local Path = require("plenary.path") +local Scandir = require("plenary.scandir") +local utils = require("cmake-explorer.utils") + +local get_builds_in_dir = function(path) + local ret = {} + -- add to builds directories which accept is_build_dir() + local candidates = Scandir.scan_dir(path, { hidden = false, only_dirs = true, depth = 0, silent = true }) + for _, v in ipairs(candidates) do + if Build.is_build_dir(v) then + local b = Build:new(v) + table.insert(ret, b) + end + end + return ret +end + +local set_current_build = function(builds, filter) + local filter_func + if type(filter) == "number" then + if filter >= 1 and filter <= #builds then + return builds[filter] + else + print("set_current_build. index out of range. set to first") + return builds[1] + end + elseif type(filter) == "string" then + filter_func = function(v) + return v:build_type() == filter + end + elseif type(filter) == "function" then + filter_func = filter + else + return builds[1] + end + for _, v in ipairs(builds) do + if filter_func(v) == true then + return v + end + end + return builds[1] +end + +local Project = {} + +Project.__index = Project + +function Project:new(o) + o = o or {} + + local path + if type(o) == "string" then + path = o + elseif type(o) == "table" and o.path then + path = o.path + else + return + end + + if not Path:new(path, "CMakeLists.txt"):exists() then + return + end + + local obj = { + path = Path:new(path), + builds = nil, + current_build = nil, + } + obj.builds = get_builds_in_dir(path) + obj.current_build = set_current_build(obj.builds, config.build_types[1]) + setmetatable(obj, Project) + return obj +end + +-- finds build with passed params, creates new build if not found +function Project:append_build(params) + local build_dir = (self.path / Build.name(params)):absolute() + for _, v in ipairs(self.builds) do + if v.path:absolute() == build_dir then + print("append_build. build found") + return v + end + end + print("append_build. new build") + table.insert(self.builds, Build:new(build_dir)) + return self.builds[#self.builds] +end + +function Project:symlink_compile_commands() + local src = (self.current_build.path / "compile_commands.json") + if src:exists() then + vim.cmd( + 'silent exec "!' + .. config.cmake_cmd + .. " -E create_symlink " + .. src:absolute() + .. " " + .. (self.path / "compile_commands.json"):absolute() + .. '"' + ) + end +end + +function Project:list_build_dirs_names() + local ret = {} + for _, v in ipairs(self.builds) do + table.insert(ret, v.path:absolute()) + end + return ret +end + +function Project:configure(params) + params = params or {} + + self.current_build = self:append_build(params) + local args = vim.tbl_deep_extend("keep", params.args or {}, config.options or {}) + table.insert(args, "-G" .. (params.generator or globals.generators[1])) + table.insert(args, "-DCMAKE_BUILD_TYPE=" .. (params.build_type or config.build_types[1])) + table.insert(args, "-S" .. self.path:absolute()) + table.insert(args, "-B" .. self.current_build.path:absolute()) + return { + cmd = config.cmake_cmd, + args = args, + after_success = function() + self:symlink_compile_commands() + end, + } +end + +return Project diff --git a/lua/cmake-explorer/runner.lua b/lua/cmake-explorer/runner.lua new file mode 100644 index 0000000..f9c3d84 --- /dev/null +++ b/lua/cmake-explorer/runner.lua @@ -0,0 +1,62 @@ +local Job = require("plenary.job") + +local M = {} + +local running_jobs = {} +local last_job = nil + +function M.start(command) + if not command then + print("runner start. command is nil") + return + end + local env = vim.tbl_extend("force", vim.loop.os_environ(), command.env and command.env or {}) + + vim.notify(command.cmd .. " " .. table.concat(command.args, " ")) + local job = Job:new({ + command = command.cmd, + args = command.args, + env = env, + on_exit = vim.schedule_wrap(function(_, code, signal) + if code == 0 and signal == 0 and command.after_success then + command.after_success() + end + end), + }) + job:start() + table.insert(running_jobs, job) + last_job = job +end + +function M.cancel_job() + if not last_job then + return false + end + + -- Check if this job was run through debugger. + if last_job.session then + if not last_job.session() then + return false + end + last_job.terminate() + return true + end + + if last_job.is_shutdown then + return false + end + + last_job:shutdown(1, 9) + + if vim.fn.has("win32") == 1 or vim.fn.has("mac") == 1 then + -- Kill all children. + for _, pid in ipairs(vim.api.nvim_get_proc_children(last_job.pid)) do + vim.loop.kill(pid, 9) + end + else + vim.loop.kill(last_job.pid, 9) + end + return true +end + +return M diff --git a/lua/cmake-explorer/utils.lua b/lua/cmake-explorer/utils.lua new file mode 100644 index 0000000..8fff940 --- /dev/null +++ b/lua/cmake-explorer/utils.lua @@ -0,0 +1,25 @@ +local utils = { + plugin_prefix = "CM", +} + +function utils.with_prefix(command) + return utils.plugin_prefix .. command +end + +function utils.has_value(tab, val) + for index, value in ipairs(tab) do + if type(val) == "function" then + if val(value) then + return true + end + else + if value == val then + return true + end + end + end + + return false +end + +return utils |