diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
index 0000000..4ff0475
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,47 @@
+name: Test
+
+on:
+ push:
+ branches: [ master, vim-cmake-updates ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ test:
+ strategy:
+ matrix:
+ include:
+ - os: ubuntu-latest
+ install: |
+ sudo apt-get update
+ sudo apt-get install -y vim cmake
+ vim_path: ""
+ run: vim -Es --cmd 'set packpath=' -u test/.vimrc -c 'Vader! test/cmake.vader'
+
+ - os: windows-latest
+ install: |
+ winget install --id vim.vim --silent --accept-package-agreements --accept-source-agreements
+ winget install --id Kitware.CMake --silent --accept-package-agreements --accept-source-agreements
+ vim_path: |
+ $vimpath = Get-ChildItem "C:\Program Files\Vim" -Directory | Select-Object -Last 1
+ echo "$vimpath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
+ run: vim -Es --cmd "set packpath=" -u test/.vimrc -c "Vader! test/cmake.vader"
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install dependencies
+ run: ${{ matrix.install }}
+
+ - name: Add Vim to PATH
+ if: matrix.vim_path != ''
+ run: ${{ matrix.vim_path }}
+
+ - name: Clone vader.vim
+ run: git clone https://github.com/junegunn/vader.vim test/vader.vim
+
+ - name: Run tests
+ run: ${{ matrix.run }}
diff --git a/.gitignore b/.gitignore
index 50a9abd..c8c26ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
+test/vader.vim/
test/test project/build/
doc/tags
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index f85420c..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-language: vim
-
-addons:
- apt:
- sources:
- - ubuntu-toolchain-r-test
- - kubuntu-backports
- packages:
- - g++-4.7
- - cmake
-
-before_script: |
- git clone https://github.com/junegunn/vader.vim.git
-
-script: |
- vim -Nu 'test/.vimrc' -c 'Vader! test/cmake.vader'
diff --git a/README.md b/README.md
index dd565c4..9637f65 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,5 @@
# vim-cmake
-[](https://travis-ci.org/vhdirk/vim-cmake)
-[](https://ci.appveyor.com/project/vhdirk/vim-cmake)
+[](https://github.com/DaveParillo/vim-cmake/actions/workflows/test.yaml)
vim-cmake is a Vim plugin to make working with CMake a little nicer.
@@ -11,11 +10,14 @@ disliked setting makeprg manually each time. This plugin does just that.
### Commands
- * `:CMake` searches for the closest directory named build in an upwards search,
-and whenever one is found, it runs the cmake command there, assuming the CMakeLists.txt
-file is just one directory above. Any arguments given to :CMake will be directly passed
+ * `:CMakeConfigure` searches for the closest directory named build in an upwards search,
+and if found, it runs the `cmake -S 'source_dir' -B 'build_dir'` command there, assuming the CMakeLists.txt
+file is just one directory above. Any arguments given to :CMakeConfigure will be directly passed
on to the cmake command. It also sets the working directory of the make command, so
you can just use quickfix as with a normal Makefile project.
+
+ * `:CMakeBuild` executes the `cmake --build` command in a directory configured
+ previously by the `:CMakeConfigure` command.
If you have the [AsyncRun plugin](https://github.com/skywind3000/asyncrun.vim)
installed, it will be used automatically and you will be able to check the
result of the cmake command in the quickfix as well.
@@ -49,6 +51,40 @@ result of the cmake command in the quickfix as well.
## Installation
+With Vim 8.1 and later, no external plugin manager is required. Vim includes a
+built-in package system that automatically loads plugins placed in specific
+directories under `pack/`.
+
+Create the `start` directory if it does not already exist:
+
+ mkdir -p ~/.vim/pack/dev/start/
+
+Clone the repository into that directory:
+
+ git clone https://github.com/vhdirk/vim-cmake.git \
+ ~/.vim/pack/dev/start/vim-cmake
+
+Plugins placed in `pack/*/start/` are automatically loaded when Vim starts.
+No additional configuration is required.
+
+If you prefer to install the plugin but load it manually, place it in the
+`opt` directory instead:
+
+ mkdir -p ~/.vim/pack/dev/opt/
+ git clone https://github.com/vhdirk/vim-cmake.git \
+ ~/.vim/pack/dev/opt/vim-cmake
+
+Then load it when needed:
+
+ :packadd vim-cmake
+
+### Verifying the installation
+
+Restart Vim and run:
+
+ :CMakeBuild
+
+If the command is recognized, the plugin is installed correctly.
### Vim-pathogen
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 7afdcd7..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-os: Visual Studio 2015
-
-install:
- - cinst vim
- - cinst cmake
- - set PATH=%PATH%;C:\Program Files\CMake\bin
- - git clone https://github.com/junegunn/vader.vim
-
-build_script:
- - vim -Nu test/.vimrc -c 'Vader! test/cmake.vader' --not-a-term
diff --git a/autoload/cmake.vim b/autoload/cmake.vim
new file mode 100644
index 0000000..88379f4
--- /dev/null
+++ b/autoload/cmake.vim
@@ -0,0 +1,295 @@
+" autoload/cmake.vim - Implementation for vim-cmake
+" Maintainer: Dirk Van Haerenborgh
+" Version: 1.0
+
+" Utility function
+" Thanks to tpope/vim-fugitive
+function! s:fnameescape(file) abort
+ if exists('*fnameescape')
+ return fnameescape(a:file)
+ else
+ return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
+ endif
+endfunction
+
+" Returns the path to the dotfile used to persist b:build_dir,
+" anchored to the project root (one level above b:build_dir), or
+" falling back to the directory of the current file.
+function! s:dotfile_path() abort
+ if exists('b:build_dir') && !empty(b:build_dir)
+ return fnamemodify(b:build_dir, ':p:h:h') . '/.vim-cmake'
+ endif
+ return fnamemodify(expand('%:p:h'), ':p') . '/.vim-cmake'
+endfunction
+
+function! s:find_build_dir() abort
+ " Do not overwrite already found build_dir, may be set explicitly by user.
+ if exists("b:build_dir") && b:build_dir != ""
+ return 1
+ endif
+ " Check dotfile cache first
+ let l:dotfile = s:dotfile_path()
+ if filereadable(l:dotfile)
+ let l:lines = readfile(l:dotfile)
+ if !empty(l:lines)
+ let l:candidate = fnamemodify(l:lines[0], ':p')
+ if isdirectory(l:candidate)
+ let b:build_dir = l:candidate
+ echom "vim-cmake: Restored build directory from cache: " . b:build_dir
+ return 1
+ else
+ call delete(l:dotfile)
+ endif
+ endif
+ endif
+ " search filesystem
+ let g:cmake_build_dir = get(g:, 'cmake_build_dir', 'build')
+ let b:build_dir = finddir(g:cmake_build_dir, ';')
+ if b:build_dir == ""
+ " Find build directory in path of current file
+ let b:build_dir = finddir(g:cmake_build_dir, s:fnameescape(expand("%:p:h")) . ';')
+ endif
+ if b:build_dir != ""
+ " expand() would expand "" to working directory, but we need
+ " this as an indicator that build was not found
+ let b:build_dir = fnamemodify(b:build_dir, ':p')
+ echom "Found cmake build directory: " . s:fnameescape(b:build_dir)
+ call cmake#SaveBuildDir()
+ return 1
+ else
+ echom "Unable to find cmake build directory."
+ return 0
+ endif
+endfunction
+
+function! s:find_smp() abort
+ if executable('nproc')
+ let l:nproc = system('nproc')
+ let b:smp = '-j' . substitute(l:nproc, '\n\+$', '', '')
+ return 1
+ endif
+ return 0
+endfunction
+
+" Configure the cmake project in the currently set build dir.
+"
+" This will override any of the following variables if the
+" corresponding vim variable is set:
+" * CMAKE_INSTALL_PREFIX
+" * CMAKE_BUILD_TYPE
+" * CMAKE_BUILD_SHARED_LIBS
+" If the project is not configured already, the following variables will be set
+" whenever the corresponding vim variable for the following is set:
+" * CMAKE_CXX_COMPILER
+" * CMAKE_C_COMPILER
+" * The generator (-G)
+function! s:cmake_configure(cmake_vim_command_args) abort
+ if has('win32')
+ let l:save_shellslash = &shellslash
+ set noshellslash
+ endif
+ exec 'cd' s:fnameescape(b:build_dir)
+ let l:argument = []
+ " Only change values of variables if project is not configured already,
+ " otherwise we overwrite existing configuration.
+ let l:configured = filereadable("CMakeCache.txt")
+ if !l:configured
+ if exists("g:cmake_project_generator")
+ let l:argument += [ "-G \"" . g:cmake_project_generator . "\"" ]
+ endif
+ if exists("g:cmake_cxx_compiler")
+ let l:argument += [ "-DCMAKE_CXX_COMPILER:FILEPATH=" . g:cmake_cxx_compiler ]
+ endif
+ if exists("g:cmake_c_compiler")
+ let l:argument += [ "-DCMAKE_C_COMPILER:FILEPATH=" . g:cmake_c_compiler ]
+ endif
+ if exists("g:cmake_usr_args")
+ let l:argument += [ g:cmake_usr_args ]
+ endif
+ endif
+ if exists("g:cmake_install_prefix")
+ let l:argument += [ "-DCMAKE_INSTALL_PREFIX:FILEPATH=" . g:cmake_install_prefix ]
+ endif
+ if exists("g:cmake_build_type")
+ let l:argument += [ "-DCMAKE_BUILD_TYPE:STRING=" . g:cmake_build_type ]
+ endif
+ if exists("g:cmake_build_shared_libs")
+ let l:argument += [ "-DBUILD_SHARED_LIBS:BOOL=" . g:cmake_build_shared_libs ]
+ endif
+ if exists("g:cmake_toolchain_file")
+ let l:argument += [ "-DCMAKE_TOOLCHAIN_FILE:FILEPATH=" . g:cmake_toolchain_file ]
+ endif
+ if g:cmake_export_compile_commands
+ let l:argument += [ "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" ]
+ endif
+
+ let l:argumentstr = join(l:argument, " ")
+ let l:build_dir = fnamemodify(b:build_dir, ':p')
+ " Remove trailing slash before :h, otherwise fnamemodify treats
+ " the empty component after the slash as the last path element
+ " Remove trailing slash before :h - must handle both / and \ on Windows
+ let l:build_dir = substitute(l:build_dir, '[/\\]$', '', '')
+ let l:source_dir = fnamemodify(l:build_dir, ':h')
+ let l:escaped_build_dir = s:fnameescape(b:build_dir)
+ let s:cmd = 'cmake -S' . shellescape(l:source_dir)
+ \ . ' -B' . shellescape(l:build_dir)
+ \ . ' ' . l:argumentstr
+ \ . " " . join(a:cmake_vim_command_args)
+
+ echo s:cmd
+ if exists(":AsyncRun")
+ execute 'copen'
+ execute 'AsyncRun ' . s:cmd
+ execute 'wincmd p'
+ else
+ silent let s:res = system(s:cmd)
+ silent echo s:res
+ endif
+
+ " Create symbolic link to compilation database for use with YouCompleteMe
+ if g:cmake_ycm_symlinks && filereadable("compile_commands.json")
+ if has("win32")
+ exec "mklink" "../compile_commands.json" "compile_commands.json"
+ else
+ silent echo system("ln -s " . s:fnameescape(b:build_dir) . "/compile_commands.json ../compile_commands.json")
+ endif
+ echom "Created symlink to compilation database"
+ endif
+ exec 'cd -'
+ if has('win32')
+ let &shellslash = l:save_shellslash
+ endif
+endfunction
+
+" ------------------------------------------------------------
+" Public API (cmake# namespace — called from plugin/cmake.vim)
+" ------------------------------------------------------------
+
+function! cmake#Configure(...) abort
+ if !s:find_build_dir()
+ return
+ endif
+ call s:cmake_configure(a:000)
+endfunction
+
+function! cmake#CMakeBuild(...) abort
+ if !s:find_build_dir()
+ return
+ endif
+ echom 'vim-cmake: Using build directory: ' . b:build_dir
+ if !filereadable(b:build_dir . '/CMakeCache.txt')
+ echohl WarningMsg
+ echom 'vim-cmake: Project is not configured. Run :CMakeConfigure first.'
+ echohl None
+ return
+ endif
+ if g:cmake_use_smp && s:find_smp()
+ let l:smp = ' ' . shellescape(b:smp)
+ else
+ let l:smp = ''
+ endif
+ let $CMAKE_BUILD_DIR = b:build_dir
+ let &makeprg = 'sh -c ''cmake --build "$CMAKE_BUILD_DIR"' . l:smp
+ \ . ' ${1:+--target "$@"}'' sh'
+ execute 'make ' . join(a:000)
+ unlet $CMAKE_BUILD_DIR
+endfunction
+
+function! cmake#FindBuildDir() abort
+ unlet! b:build_dir
+ call delete(s:dotfile_path())
+ call s:find_build_dir()
+endfunction
+
+function! cmake#SaveBuildDir() abort
+ if !exists('b:build_dir') || empty(b:build_dir)
+ return
+ endif
+ let l:dotfile = s:dotfile_path()
+ call writefile([b:build_dir], l:dotfile)
+endfunction
+
+function! cmake#LoadBuildDir() abort
+ " Don't overwrite an already-set value
+ if exists('b:build_dir') && !empty(b:build_dir)
+ return
+ endif
+ let l:dotfile = s:dotfile_path()
+ if !filereadable(l:dotfile)
+ return
+ endif
+ let l:lines = readfile(l:dotfile)
+ if empty(l:lines)
+ return
+ endif
+ let l:candidate = expand(fnamemodify(l:lines[0], ':p'))
+ if isdirectory(l:candidate)
+ let b:build_dir = l:candidate
+ else
+ " Dotfile is stale - build dir no longer exists
+ call delete(l:dotfile)
+ endif
+endfunction
+
+function! cmake#CleanBuildDir(bang) abort
+ if !exists('b:build_dir') || empty(b:build_dir)
+ echohl ErrorMsg
+ echom 'vim-cmake: No build directory set. Run :CMake or :CMakeFindBuildDir first.'
+ echohl None
+ return
+ endif
+
+ let l:build_dir = fnamemodify(b:build_dir, ':p')
+
+ if !isdirectory(l:build_dir)
+ echohl ErrorMsg
+ echom 'vim-cmake: Build directory does not exist: ' . l:build_dir
+ echohl None
+ return
+ endif
+
+ " Guard against cleaning obviously dangerous paths
+ let l:home = fnamemodify('~', ':p')
+ let l:cwd = fnamemodify('.', ':p')
+ let l:root = '/'
+ if l:build_dir ==# l:home || l:build_dir ==# l:cwd || l:build_dir ==# l:root
+ echohl ErrorMsg
+ echom 'vim-cmake: Refusing to clean a root, home, or working directory: ' . l:build_dir
+ echohl None
+ return
+ endif
+
+ " Require CMakeCache.txt as a sanity check
+ if !filereadable(l:build_dir . '/CMakeCache.txt')
+ echohl WarningMsg
+ echom 'vim-cmake: Directory does not look like a CMake build dir (no CMakeCache.txt): ' . l:build_dir
+ echohl None
+ if !a:bang
+ return
+ endif
+ echom 'vim-cmake: Proceeding anyway due to !-bang override.'
+ endif
+
+ " Prompt unless bang
+ if !a:bang
+ let l:answer = input('vim-cmake: Clean all files in ' . l:build_dir . '? [y/N] ')
+ echo ' '
+ if l:answer !~? '^y\(es\)\?$'
+ echom 'vim-cmake: Clean cancelled.'
+ return
+ endif
+ endif
+
+ echom 'vim-cmake: Cleaning ' . l:build_dir . ' ...'
+ let l:errors = delete(l:build_dir, 'rf')
+ if l:errors != 0
+ echohl ErrorMsg
+ echom 'vim-cmake: Clean failed (delete returned ' . l:errors . ').'
+ echohl None
+ else
+ echom 'vim-cmake: Done.'
+ unlet b:build_dir
+ call delete(s:dotfile_path())
+ endif
+endfunction
+
diff --git a/doc/cmake.txt b/doc/cmake.txt
index b836e85..bd0889d 100644
--- a/doc/cmake.txt
+++ b/doc/cmake.txt
@@ -8,62 +8,171 @@ License: Same terms as Vim itself (see |license|)
INTRODUCTION *cmake*
-When working on a CMake project, this plugin provides a single command that
-changes the makeprg make command's working directory to the closest 'build'
-directory (see g:cmake_build_dir) upwards
-
-COMMANDS *cmake-commands*
-
-:CMake [args] Runs the cmake command as 'cmake ..', starting in
- first directory called 'build', found in an upwards
- search. All arguments are directly passed on to CMake.
- Also modifies the :make command to build in
- that directory.
-
-:CMakeClean deletes all files in the build directory. You can
- think of this as a CMake version of make clean.
-
-:CMakeFindBuildDir resets the build directory path set for the current buffer
- and then tries to find a new one. Useful if it previously
- found a wrong path to then reset it after a new build folder
- has been created for example.
-
-VARIABLES *cmake-variables*
-
-g:cmake_install_prefix same as -DCMAKE_INSTALL_PREFIX
-
-g:cmake_build_type same as -DCMAKE_BUILD_TYPE
-
-g:cmake_cxx_compiler same as -DCMAKE_CXX_COMPILER, however, this will have
- no effect until you run :CMakeClean and :CMake.
-
-g:cmake_c_compiler same as -DCMAKE_C_COMPILER, however, this will have
- no effect until you run :CMakeClean and :CMake.
-
-g:cmake_build_shared_libs same as -DBUILD_SHARED_LIBS
-
-g:cmake_toolchain_file same as -DCMAKE_TOOLCHAIN_FILE
-
-g:cmake_build_dir set the cmake 'build' directory, default: 'build'
-
-g:cmake_project_generator set project generator, however, this will have
- no effect until you run :CMakeClean and :CMake.
-
-g:cmake_usr_args custom user arguments. Ex: 'let g:cmake_usr_args="-DDEBUG=YES"'
-
-b:build_dir the path to the cmake build directory for the current buffer.
- This variable is set with the first :CMake or :CMakeFindBuildDir call.
- Once found, it will not be searched for again unless you call
- :CMakeFindBuildDir. If automatic finding is not sufficient you can set
- this variable manually to the build dir of your choice.
-
-OPTIONS *cmake-options*
-
-g:cmake_export_compile_commands same as -DCMAKE_EXPORT_COMPILE_COMMANDS=ON, useful for
- exporting a compilation database to be used with YCM
- (https://github.com/Valloric/YouCompleteMe#c-family-semantic-completion)
- CMake only supports this flag with Ninja and Makefile generators.
-
-g:cmake_ycm_symlinks create a symlink to the compile_commands.json file in the
- root of the project (build/..) if the file is found.
+When working on a CMake project, this plugin provides commands to configure
+and build CMake projects without leaving Vim. The plugin automatically locates
+the build directory (see |g:cmake_build_dir|), persists that location across
+sessions, and wires up |'makeprg'| so that |:make| builds in the correct
+directory.
+
+==============================================================================
+COMMANDS *cmake-commands* *:CMakeBuild*
+
+:CMakeBuild [target] [args]
+ CMakeBuild searches for the configured build directory
+ and if found updates the |:makeprg| option and then
+ builds the [target] using |:make|.
+
+ All arguments after the first are directly passed on
+ to CMake.
+
+ The first parameter must be a target name and other
+ cmake parameters can follow the target in any order.
+
+ Use |:CMakeConfigure| to find a build directory,
+ generate toolchain files, and update the
+ *cmake-cache* file.
+
+ Tab completion is available for [target]: >
+ :CMake my_
+<
+ When invoked without arguments, the default build
+ target is used.
+
+ *:CMakeConfigure*
+:CMakeConfigure [args] Run cmake configuration in the build directory,
+ passing [args] directly to cmake.
+ CMakeConfigure searches for the build directory
+ before writing configuration files there
+
+ Build directory discovery follows this order:
+ 1. Use |b:build_dir| if already set.
+ 2. Restore from the *cmake-cache* (.vim-cmake).
+ 3. Search upward from the current file for a
+ directory named |g:cmake_build_dir|.
+
+ Once found, the build directory is saved to the
+ *cmake-cache* automatically.
+
+ Use this to
+ explicitly re-run configuration, for example after
+ changing a compiler or build type: >
+ :CMakeConfigure -DCMAKE_BUILD_TYPE=Debug
+<
+ Plugin global variables are translated into
+ cmake arguments automatically when set. See
+ |cmake-variables| for the full list.
+
+ *:CMakeClean*
+:CMakeClean! Delete all files in the build directory, equivalent
+ to a cmake clean. Prompts for confirmation before
+ proceeding.
+
+ Add [!] to skip the confirmation prompt: >
+ :CMakeClean!
+<
+ The command refuses to delete the build directory if:
+ - It does not contain a CMakeCache.txt (unless [!]
+ is given).
+ - It resolves to the home directory, current working
+ directory, or filesystem root.
+
+ After a successful clean, |b:build_dir| is unset and
+ the session cache is cleared, so the next |:CMake|
+ call will re-run discovery and configuration.
+
+ *:CMakeFindBuildDir*
+:CMakeFindBuildDir Clear the current build directory path for the current
+ buffer and the session cache, then search for a new one.
+ Useful after creating a new build directory or when
+ the wrong directory was previously detected.
+
+==============================================================================
+OPTIONS *cmake-options*
+
+ *g:cmake_build_dir*
+g:cmake_build_dir The directory name to search for when locating the
+ build directory. Searched upward from the current
+ file. Default: 'build'. >
+ let g:cmake_build_dir = 'build'
+<
+ *b:build_dir*
+b:build_dir The resolved path to the cmake build directory for
+ the current buffer. Set automatically by |:CMake| or
+ |:CMakeFindBuildDir|. Can be set manually to override
+ automatic discovery: >
+ let b:build_dir = '/path/to/my/build'
+<
+ Once set it is not overwritten unless you call
+ |:CMakeFindBuildDir| or set it explicitly.
+
+ *g:cmake_install_prefix*
+g:cmake_install_prefix Passed to cmake as -DCMAKE_INSTALL_PREFIX.
+
+ *g:cmake_build_type*
+g:cmake_build_type Passed to cmake as -DCMAKE_BUILD_TYPE. >
+ let g:cmake_build_type = 'Debug'
+<
+ *g:cmake_cxx_compiler*
+g:cmake_cxx_compiler Passed to cmake as -DCMAKE_CXX_COMPILER. Changes
+ take effect after |:CMakeClean| and |:CMakeConfigure|.
+
+ *g:cmake_c_compiler*
+g:cmake_c_compiler Passed to cmake as -DCMAKE_C_COMPILER. Changes
+ take effect after |:CMakeClean| and |:CMakeConfigure|.
+
+ *g:cmake_build_shared_libs*
+g:cmake_build_shared_libs
+ Passed to cmake as -DBUILD_SHARED_LIBS.
+
+ *g:cmake_toolchain_file*
+g:cmake_toolchain_file Passed to cmake as -DCMAKE_TOOLCHAIN_FILE.
+
+ *g:cmake_project_generator*
+g:cmake_project_generator
+ Passed to cmake as -G. Changes take effect after
+ |:CMakeClean| and |:CMakeConfigure|.
+
+ *g:cmake_export_compile_commands*
+g:cmake_export_compile_commands
+ Passed to cmake as -DCMAKE_EXPORT_COMPILE_COMMANDS.
+ Default: 0.
+
+ *g:cmake_ycm_symlinks*
+g:cmake_ycm_symlinks When set to 1, creates a symlink to the generated
+ compile_commands.json in the project root for use
+ with YouCompleteMe or any LSP client that expects it
+ there. Default: 0.
+
+ *g:cmake_use_smp*
+g:cmake_use_smp When set to 1, passes -j{nproc} to the build command
+ to enable parallel builds. Requires `nproc` to be
+ available on PATH. Default: 0.
+
+ *g:cmake_usr_args*
+g:cmake_usr_args Arbitrary extra arguments passed to cmake during
+ configuration. Appended after all other arguments: >
+ let g:cmake_usr_args = '-DSOME_OPTION=ON'
+<
+
+==============================================================================
+SESSION CACHE *cmake-cache*
+
+The plugin persists |b:build_dir| across sessions using a dotfile named
+.vim-cmake written to the project root (one level above the build directory).
+This file is read automatically on |BufReadPost| and written on |BufWritePost|.
+
+The cache is cleared automatically by |:CMakeFindBuildDir| and |:CMakeClean|.
+It is also invalidated silently if the recorded directory no longer exists.
+
+The .vim-cmake file is a per-developer local setting and should be added to
+the project .gitignore: >
+ echo '.vim-cmake' >> .gitignore
+<
+==============================================================================
+LICENSE *cmake-license*
+
+Copyright (c) Dirk Van Haerenborgh, @SteveDeFacto. Distributed under the
+same terms as Vim itself. See |license|.
+
+ vim:tw=78:ts=8:ft=help:norl:
diff --git a/plugin/cmake.vim b/plugin/cmake.vim
index 522d56b..1196adb 100644
--- a/plugin/cmake.vim
+++ b/plugin/cmake.vim
@@ -1,8 +1,8 @@
" cmake.vim - Vim plugin to make working with CMake a little nicer
" Maintainer: Dirk Van Haerenborgh
-" Version: 0.2
+" Version: 1.0
-let s:cmake_plugin_version = '0.2'
+let s:cmake_plugin_version = '1.0'
if exists("loaded_cmake_plugin")
finish
@@ -29,175 +29,26 @@ if !executable("cmake")
finish
endif
-function! s:find_build_dir()
- " Do not overwrite already found build_dir, may be set explicitly
- " by user.
- if exists("b:build_dir") && b:build_dir != ""
- return 1
- endif
-
- let g:cmake_build_dir = get(g:, 'cmake_build_dir', 'build')
- let b:build_dir = finddir(g:cmake_build_dir, ';')
-
- if b:build_dir == ""
- " Find build directory in path of current file
- let b:build_dir = finddir(g:cmake_build_dir, s:fnameescape(expand("%:p:h")) . ';')
- endif
-
- if b:build_dir != ""
- " expand() would expand "" to working directory, but we need
- " this as an indicator that build was not found
- let b:build_dir = fnamemodify(b:build_dir, ':p')
- echom "Found cmake build directory: " . s:fnameescape(b:build_dir)
- return 1
- else
- echom "Unable to find cmake build directory."
- return 0
- endif
-
-endfunction
-
-" Configure the cmake project in the currently set build dir.
-"
-" This will override any of the following variables if the
-" corresponding vim variable is set:
-" * CMAKE_INSTALL_PREFIX
-" * CMAKE_BUILD_TYPE
-" * CMAKE_BUILD_SHARED_LIBS
-" If the project is not configured already, the following variables will be set
-" whenever the corresponding vim variable for the following is set:
-" * CMAKE_CXX_COMPILER
-" * CMAKE_C_COMPILER
-" * The generator (-G)
-function! s:cmake_configure(cmake_vim_command_args)
- exec 'cd' s:fnameescape(b:build_dir)
-
- let l:argument = []
- " Only change values of variables, if project is not configured
- " already, otherwise we overwrite existing configuration.
- let l:configured = filereadable("CMakeCache.txt")
-
- if !l:configured
- if exists("g:cmake_project_generator")
- let l:argument += [ "-G \"" . g:cmake_project_generator . "\"" ]
- endif
- if exists("g:cmake_cxx_compiler")
- let l:argument += [ "-DCMAKE_CXX_COMPILER:FILEPATH=" . g:cmake_cxx_compiler ]
- endif
- if exists("g:cmake_c_compiler")
- let l:argument += [ "-DCMAKE_C_COMPILER:FILEPATH=" . g:cmake_c_compiler ]
- endif
-
- if exists("g:cmake_usr_args")
- let l:argument+= [ g:cmake_usr_args ]
- endif
- endif
-
- if exists("g:cmake_install_prefix")
- let l:argument += [ "-DCMAKE_INSTALL_PREFIX:FILEPATH=" . g:cmake_install_prefix ]
- endif
- if exists("g:cmake_build_type" )
- let l:argument += [ "-DCMAKE_BUILD_TYPE:STRING=" . g:cmake_build_type ]
- endif
- if exists("g:cmake_build_shared_libs")
- let l:argument += [ "-DBUILD_SHARED_LIBS:BOOL=" . g:cmake_build_shared_libs ]
- endif
- if exists("g:cmake_toolchain_file")
- let l:argument += [ "-DCMAKE_TOOLCHAIN_FILE:FILEPATH=" . g:cmake_toolchain_file ]
- endif
- if g:cmake_export_compile_commands
- let l:argument += [ "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" ]
- endif
-
- let l:argumentstr = join(l:argument, " ")
- let l:escaped_build_dir=s:fnameescape(b:build_dir)
- let l:home_dir = "-H".l:escaped_build_dir."/.."
- let l:build_dir_path = "-B".l:escaped_build_dir
- let s:cmd = 'cmake '.l:home_dir.' '.l:build_dir_path.' '.l:argumentstr . " " . join(a:cmake_vim_command_args)
-
- echo s:cmd
- if exists(":AsyncRun")
- execute 'copen'
- execute 'AsyncRun ' . s:cmd
- execute 'wincmd p'
- else
- silent let s:res = system(s:cmd)
- silent echo s:res
- endif
-
- " Create symbolic link to compilation database for use with YouCompleteMe
- if g:cmake_ycm_symlinks && filereadable("compile_commands.json")
- if has("win32")
- exec "mklink" "../compile_commands.json" "compile_commands.json"
- else
- silent echo system("ln -s " . s:fnameescape(b:build_dir) ."/compile_commands.json ../compile_commands.json")
- endif
- echom "Created symlink to compilation database"
- endif
-
- exec 'cd -'
-endfunction
-
-" Utility function
-" Thanks to tpope/vim-fugitive
-function! s:fnameescape(file) abort
- if exists('*fnameescape')
- return fnameescape(a:file)
- else
- return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
- endif
-endfunction
-
-function! s:find_smp()
- if executable('nproc')
- let l:nproc = system('nproc')
- let b:smp = '-j' . substitute(l:nproc, '\n\+$', '', '')
- return 1
- endif
- return 0
-endfunction
-
-command! -complete=customlist,ListTargets -nargs=1 Make :make
-function! ListTargets(A, L, C)
- if !exists("b:build_dir")
- return []
- endif
- let all_targets = split(system("cmake --build ". b:build_dir . " --target help | awk ' NR > 1 {print $2}'"), '\n')
- let targets = filter(all_targets, "v:val =~ '^" .. a:A .. "'")
- return targets
-endfunction
-
" Public Interface:
-command! -nargs=? CMake call s:cmake()
-command! CMakeClean call s:cmakeclean()
-command! CMakeFindBuildDir call s:cmake_find_build_dir()
-
-function! s:cmake_find_build_dir()
- unlet! b:build_dir
- call s:find_build_dir()
-endfunction
-
-function! s:cmake(...)
- if !s:find_build_dir()
- return
- endif
-
- if g:cmake_use_smp && s:find_smp()
- let l:smp = ' ' . shellescape(b:smp)
- else
- let l:smp = ''
- endif
-
- let &makeprg = 'cmake --build ' . shellescape(b:build_dir) . l:smp . ' --target'
- call s:cmake_configure(a:000)
-endfunction
-
-function! s:cmakeclean()
- if !s:find_build_dir()
- return
- endif
-
- silent echo system("rm -r '" . b:build_dir. "'/*")
- echom "Build directory has been cleaned."
-endfunction
+command! -nargs=? -complete=customlist,s:list_targets CMakeBuild call cmake#CMakeBuild()
+command! -nargs=? CMakeConfigure call cmake#Configure()
+command! -bang CMakeClean call cmake#CleanBuildDir(0)
+command! CMakeFindBuildDir call cmake#FindBuildDir()
+
+function! s:list_targets(A, L, C) abort
+ if !exists("b:build_dir")
+ return []
+ endif
+ let l:all_targets = split(
+ \ system("cmake --build " . b:build_dir . " --target help | awk 'NR > 1 {print $2}'"),
+ \ '\n')
+ return filter(l:all_targets, "v:val =~ '^" . a:A . "'")
+endfunction
+
+" Persist and restore b:build_dir across sessions
+augroup vim_cmake_persist
+ autocmd!
+ autocmd BufReadPost * call cmake#LoadBuildDir()
+ autocmd BufWritePost * call cmake#SaveBuildDir()
+augroup END
diff --git a/test/.vimrc b/test/.vimrc
index 4113900..5b80db3 100644
--- a/test/.vimrc
+++ b/test/.vimrc
@@ -1,7 +1,15 @@
filetype off
-
-set rtp+=vader.vim
-set rtp+=.
+set nocompatible
+set noloadplugins
+exec 'set rtp+=' . getcwd() . '/test/vader.vim'
+exec 'set rtp+=' . getcwd()
+runtime plugin/cmake.vim
+runtime plugin/vader.vim
filetype plugin indent on
syntax enable
+function! Normalize(path, ...) abort
+ let l:modifier = a:0 > 0 ? a:1 : ':p'
+ let l:fname = fnamemodify(resolve(a:path), l:modifier)
+ return expand(l:fname)
+endfunction
diff --git a/test/cmake.vader b/test/cmake.vader
index a18ee77..7dc21d8 100644
--- a/test/cmake.vader
+++ b/test/cmake.vader
@@ -1,65 +1,240 @@
+" cmake.vader - Tests for vim-cmake
+" Uses vader.vim: https://github.com/junegunn/vader.vim
+
+" ============================================================
+" Setup and teardown
+" ============================================================
+
Before:
- " Ensure we are in the test directory
- if isdirectory("test")
- cd test
+ if !exists("test_dir")
+ let test_dir = globpath(&rtp, 'test/cmake.vader')
+ let test_dir = fnamemodify(test_dir, ':p:h')
endif
+ exec "cd" fnameescape(test_dir)
- if !exists("test_dir")
- let test_dir = fnamemodify(getcwd(), ':p')
+ " Clean unit test scratch space
+ if exists("g:unit_test_dir")
+ call delete(g:unit_test_dir, 'rf')
endif
+ let g:unit_test_dir = tempname()
+ call mkdir(g:unit_test_dir . '/build', 'p')
+ call writefile([''], g:unit_test_dir . '/build/CMakeCache.txt')
- Assert !isdirectory("test project/tmp-build"), "TEST ERROR: build directory was not properly deleted"
- echo system("mkdir 'test project/tmp-build'")
- Assert isdirectory("test project/tmp-build"), "TEST ERROR: build directory was not created"
+ " Clean integration test build directory
+ Assert !isdirectory("test project/tmp-build"),
+ \ "TEST ERROR: build directory was not properly deleted"
+ call mkdir('test project/tmp-build', 'p')
+ Assert isdirectory("test project/tmp-build"),
+ \ "TEST ERROR: build directory was not created"
- " Under travis CI the entire project is in a build/ directory
+ " Under Travis CI the entire project is in a build/ directory
" which will make the search from cwd always return a result.
" To be able to test searching build dir from current file, the
" build dir needs to be named differently as a workaround.
let g:cmake_build_dir = "tmp-build"
+
+ " Reset plugin state
+ unlet! b:build_dir
+
+ " Reset to defaults rather than unlet - autoload code references these directly
+ let g:cmake_export_compile_commands = 0
+ let g:cmake_ycm_symlinks = 0
+ let g:cmake_use_smp = 0
+
After:
+ " Restore integration test environment
exec "cd" fnameescape(test_dir)
- echo system("rm -rf 'test project/tmp-build'")
- echo system("rm -f 'test project/compile_commands.json'")
+ call delete('test project/tmp-build', 'rf')
+ call delete('test project/compile_commands.json')
+ call delete('test project/.vim-cmake')
-Execute (Find build directory from working dir):
- cd test\ project
- CMake
+ " Tear down unit test scratch space
+ if exists("g:unit_test_dir")
+ call delete(g:unit_test_dir, 'rf')
+ unlet g:unit_test_dir
+ endif
+
+ " Reset plugin state
+ unlet! b:build_dir
+ unlet! g:cmake_build_dir
+ " Reset to defaults rather than unlet - autoload code references these directly
+ let g:cmake_export_compile_commands = 0
+ let g:cmake_ycm_symlinks = 0
+ let g:cmake_use_smp = 0
+
+" ============================================================
+" Unit tests: build directory discovery
+" ============================================================
+
+Execute (FindBuildDir locates build/ upward from current file):
+ let saved_cwd = getcwd()
+ exec 'cd' g:unit_test_dir
+ let g:cmake_build_dir = 'build'
+ call cmake#FindBuildDir()
+Then:
+ AssertEqual Normalize(g:unit_test_dir . '/build/'), b:build_dir
+ exec 'cd' saved_cwd
+
+Execute (FindBuildDir resets and rediscovers on second call):
+ let saved_cwd = getcwd()
+ exec 'cd' g:unit_test_dir
+ let g:cmake_build_dir = 'build'
+ let b:build_dir = '/some/wrong/path'
+ call cmake#FindBuildDir()
+Then:
+ AssertEqual Normalize(g:unit_test_dir . '/build/'), b:build_dir
+ exec 'cd' saved_cwd
+
+" ============================================================
+" Unit tests: session cache
+" ============================================================
+
+Execute (SaveBuildDir writes dotfile):
+ let b:build_dir = g:unit_test_dir . '/build/'
+ call cmake#SaveBuildDir()
+Then:
+ Assert filereadable(g:unit_test_dir . '/.vim-cmake'),
+ \ 'Expected .vim-cmake to exist after SaveBuildDir'
+
+Execute (LoadBuildDir restores from dotfile):
+ execute 'edit ' . g:unit_test_dir . '/CMakeLists.txt'
+ let b:build_dir = g:unit_test_dir . '/build/'
+ call cmake#SaveBuildDir()
+ unlet b:build_dir
+ call cmake#LoadBuildDir()
+Then:
+ Assert exists('b:build_dir'), 'Expected b:build_dir to be set'
+ AssertEqual Normalize(g:unit_test_dir . '/build/'), b:build_dir
+ bwipeout!
- Assert filereadable("tmp-build/CMakeCache.txt"), "CMakeCache.txt should be generated"
- Assert !filereadable("tmp-build/compile_commands.json"), "Compile commands should not be exported by default"
+Execute (LoadBuildDir ignores stale dotfile and deletes it):
+ execute 'edit ' . g:unit_test_dir . '/CMakeLists.txt'
+ let b:build_dir = g:unit_test_dir . '/build/'
+ let g:cache_file = g:unit_test_dir . '/.vim-cmake'
+ call cmake#SaveBuildDir()
+ if exists("b:build_dir")
+ call delete(b:build_dir, 'rf')
+ unlet b:build_dir
+ endif
+ call cmake#LoadBuildDir()
+Then:
+ Assert !exists('b:build_dir'),
+ \ 'Expected b:build_dir to be unset for stale cache'
+ Assert !filereadable(g:cache_file),
+ \ 'Expected stale session cache to be deleted'
+ bwipeout!
+
+Execute (LoadBuildDir does not overwrite existing b:build_dir):
+ let b:build_dir = Normalize(g:unit_test_dir . '/build/')
+ call writefile(['/some/other/path/'], g:unit_test_dir . '/.vim-cmake')
+ call cmake#LoadBuildDir()
+Then:
+ AssertEqual Normalize(g:unit_test_dir . '/build/'), b:build_dir,
+ \ 'Expected existing b:build_dir to be preserved'
+
+Execute (FindBuildDir clears session cache before rediscovery):
+ let saved_cwd = getcwd()
+ execute 'cd' g:unit_test_dir
+ let g:cmake_build_dir = 'build'
+ let g:cache_file = Normalize(g:unit_test_dir . '/.vim-cmake', ':p')
+ call writefile(['/some/wrong/path/'], g:cache_file)
+ call cmake#FindBuildDir()
+Then:
+ AssertEqual Normalize(g:unit_test_dir . '/build/'), b:build_dir,
+ \ 'Expected fresh discovery to ignore stale session cache'
+ execute 'cd' saved_cwd
+
+" ============================================================
+" Unit tests: CMakeClean safety checks
+" ============================================================
+
+Execute (CMakeClean refuses when no build dir is set):
+ unlet! b:build_dir
+ redir => g:output
+ call cmake#CleanBuildDir(1)
+ redir END
+Then:
+ Assert g:output =~# 'No build directory set',
+ \ 'Expected error about missing build dir'
+
+Execute (CMakeClean refuses directory without CMakeCache.txt):
+ let b:build_dir = g:unit_test_dir . '/build/'
+ call delete(g:unit_test_dir . '/build/CMakeCache.txt')
+ redir => g:output
+ call cmake#CleanBuildDir(0)
+ redir END
+Then:
+ Assert g:output =~# 'does not look like a CMake build dir',
+ \ 'Expected warning about missing CMakeCache.txt'
+ Assert isdirectory(g:unit_test_dir . '/build/'),
+ \ 'Expected build dir to survive refused clean'
+
+Execute (CMakeClean with bang deletes build dir):
+ let b:build_dir = g:unit_test_dir . '/build/'
+ call cmake#CleanBuildDir(1)
+Then:
+ Assert !isdirectory(g:unit_test_dir . '/build/'),
+ \ 'Expected build dir to be deleted'
+ Assert !exists('b:build_dir'),
+ \ 'Expected b:build_dir to be unset after clean'
+ Assert !filereadable(g:unit_test_dir . '/.vim-cmake'),
+ \ 'Expected .vim-cmake to be deleted after clean'
+
+Execute (CMakeClean refuses to delete home directory):
+ let b:build_dir = fnamemodify('~', ':p')
+ redir => g:output
+ call cmake#CleanBuildDir(1)
+ redir END
+Then:
+ Assert g:output =~# 'Refusing to clean',
+ \ 'Expected refusal for home directory'
+
+" ============================================================
+" Integration tests: CMake configure and build
+" ============================================================
+
+Execute (Find build directory from working dir):
+ exec "cd" "test project"
+ CMakeConfigure
+ Assert filereadable("tmp-build/CMakeCache.txt"),
+ \ "CMakeCache.txt should be generated"
Execute (Find build directory from currently open file):
e test\ project/CMakeLists.txt
- CMake
- Assert filereadable("test project/tmp-build/CMakeCache.txt"), "CMakeCache.txt should be generated"
+ CMakeConfigure
+ Assert filereadable("test project/tmp-build/CMakeCache.txt"),
+ \ "CMakeCache.txt should be generated"
+ Assert !filereadable("tmp-build/compile_commands.json"),
+ \ "Compile commands should not be exported by default"
Execute (Create symlink to compilation database):
let g:cmake_export_compile_commands = 1
let g:cmake_ycm_symlinks = 1
cd test\ project
- CMake
-
+ CMakeConfigure
" Exporting compile commands does not work with Visual Studio generator
if !has("win32") && !has("win32unix")
- Assert filereadable("tmp-build/compile_commands.json"), "Compile commands should be exported"
- Assert filereadable(resolve("compile_commands.json")), "A symlink should be generated"
+ Assert filereadable("tmp-build/compile_commands.json"),
+ \ "Compile commands should be exported"
+ Assert filereadable(resolve("compile_commands.json")),
+ \ "A symlink should be generated"
+ else
+ Log "Export compile_commands skipped on windows."
endif
Execute (Open already configured cmake project):
cd test\ project/tmp-build
silent !cmake .. -DWITH_BYE=ON
e ../CMakeLists.txt
- CMake
- silent make
-
+ CMakeBuild
+ make
enew
- if has("win32") || has("win32unix")
- read !Debug/hello.exe
- else
- read !./hello
- endif
+ let exe = executable('Debug/hello.exe') ? 'Debug/hello.exe' : './hello'
+ let exe = fnamemodify(exe, ':p')
+ let lines = systemlist(shellescape(exe))
+ call map(lines, {_, v -> substitute(v, '\r$', '', '')})
+ call setline(1, lines)
Expect:
-
Hello World
Bye World
+
diff --git a/test/test project/CMakeLists.txt b/test/test project/CMakeLists.txt
index 82ccdff..b25b37a 100644
--- a/test/test project/CMakeLists.txt
+++ b/test/test project/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.15)
project(HelloWorld)