aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--lua/cmake-explorer.lua68
-rw-r--r--lua/cmake-explorer/build.lua107
-rw-r--r--lua/cmake-explorer/config.lua18
-rw-r--r--lua/cmake-explorer/constants.lua7
-rw-r--r--lua/cmake-explorer/globals.lua29
-rw-r--r--lua/cmake-explorer/project.lua133
-rw-r--r--lua/cmake-explorer/runner.lua62
-rw-r--r--lua/cmake-explorer/utils.lua25
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