mirror of
1
0
Fork 0
ultimate-vim/sources_non_forked/vim-cmake/autoload/cmake/buildsys.vim

444 lines
14 KiB
VimL

" ==============================================================================
" Location: autoload/cmake/buildsys.vim
" Description: Functions for generating the buildsystem
" ==============================================================================
let s:buildsys = {}
let s:buildsys.cmake_version = 0
let s:buildsys.project_root = ''
let s:buildsys.current_config = ''
let s:buildsys.path_to_current_config = ''
let s:buildsys.configs = []
let s:buildsys.targets = []
let s:logger = cmake#logger#Get()
let s:statusline = cmake#statusline#Get()
let s:system = cmake#system#Get()
let s:terminal = cmake#terminal#Get()
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Private functions
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Find project root by looking for g:cmake_root_markers upwards.
"
" Returns:
" String
" escaped path to the root of the project
"
function! s:FindProjectRoot() abort
let l:root = getcwd()
let l:escaped_cwd = fnameescape(getcwd())
for l:marker in g:cmake_root_markers
" Search CWD upward for l:marker, assuming it is a file.
let l:marker_path = findfile(l:marker, l:escaped_cwd . ';' . $HOME)
if len(l:marker_path) > 0
" If found, strip l:marker from it.
let l:root = fnamemodify(l:marker_path, ':h')
break
endif
" Search CWD upward for l:marker, assuming it is a directory.
let l:marker_path = finddir(l:marker, l:escaped_cwd . ';' . $HOME)
if len(l:marker_path) > 0
" If found, strip l:marker from it.
let l:root = fnamemodify(l:marker_path, ':h')
break
endif
endfor
return l:root
endfunction
" Get absolute path to location where the build directory is located.
"
" Returns:
" String
" path to build directory location
"
function! s:GetBuildDirLocation() abort
return s:system.Path(
\ [s:buildsys.project_root, g:cmake_build_dir_location], v:false)
endfunction
" Find CMake variable in list of options.
"
" Params:
" opts : List
" list of options
" variable : String
" variable to find
"
" Returns:
" String
" value of the CMake variable, or an empty string if the variable was
" not found
"
" Example:
" to find the variable 'CMAKE_BUILD_TYPE', which would be passed by the user
" as '-D CMAKE_BUILD_TYPE=<value>', call
" s:FindVarInOpts(opts, 'CMAKE_BUILD_TYPE')
"
function! s:FindVarInOpts(opts, variable) abort
if len(a:opts) > 0
" Search the list of command-line options for an entry matching
" '-D <variable>=<value>' or '-D <variable>:<type>=<value>' or
" '-D<variable>=<value>' or '-D<variable>:<type>=<value>'.
let l:opt = matchstr(a:opts, '\m\C-D\s*' . a:variable)
" If found, return the value, otherwise return an empty string.
if len(l:opt) > 0
return split(l:opt, '=')[1]
else
return ''
endif
endif
endfunction
" Process build configuration.
"
" Params:
" opts : List
" list of options
"
function! s:ProcessBuildConfig(opts) abort
let l:config = s:buildsys.current_config
" Check if the first entry of the list of command-line options starts with a
" letter (and not with a dash), in which case the user will have passed the
" name of the build configuration as the first option.
if (len(a:opts) > 0) && (match(a:opts[0], '\m\C^\w') >= 0)
" Update build config name and remove from list of options.
let l:config = a:opts[0]
call s:SetCurrentConfig(l:config)
call remove(a:opts, 0)
endif
" If the list of command-line options does not contain an explicit value for
" the 'CMAKE_BUILD_TYPE' variable, add it.
if s:FindVarInOpts(a:opts, 'CMAKE_BUILD_TYPE') ==# ''
call add(a:opts, '-D CMAKE_BUILD_TYPE=' . l:config)
endif
endfunction
" Get list of command-line options from string of arguments.
"
" Params:
" argstring : String
" string containing command-line arguments
"
" Returns:
" List
" list of unprocessed command-line options
"
" Example:
" an argument string like the following
" 'Debug -D VAR_A=1 -DVAR_B=0 -Wdev -U VAR_C'
" results in a list of options like the following
" ['Debug', '-D VAR_A=1', '-DVAR_B=0', '-Wdev', '-U VAR_C']
"
function! s:ArgStringToOptList(argstring) abort
let l:opts = []
for l:arg in split(a:argstring)
" If list of options is empty, append first argument.
if len(l:opts) == 0
call add(l:opts, l:arg)
" If argument starts with a dash, append it to the list of options.
elseif match(l:arg, '\m\C^-') >= 0
call add(l:opts, l:arg)
" If argument does not start with a dash, it must belong to the last
" option that was added to the list, thus extend that option.
else
let l:opts[-1] = join([l:opts[-1], l:arg])
endif
endfor
return l:opts
endfunction
" Process string of arguments and return parsed options.
"
" Params:
" argstring : String
" string containing command-line arguments
"
" Returns:
" Dictionary
" opts : List
" list of options
" source_dir : String
" path to source directory
" build_dir : String
" path to build directory
"
function! s:ProcessArgString(argstring) abort
let l:opts = s:ArgStringToOptList(a:argstring)
call s:ProcessBuildConfig(l:opts)
" If compile commands are to be exported, and the
" 'CMAKE_EXPORT_COMPILE_COMMANDS' variable is not set, set it.
if g:cmake_link_compile_commands
if s:FindVarInOpts(l:opts, 'CMAKE_EXPORT_COMPILE_COMMANDS') ==# ''
call add(l:opts, '-D CMAKE_EXPORT_COMPILE_COMMANDS=ON')
endif
endif
" Set source and build directories. Must be done after processing the build
" configuration so that the current build configuration is up to date before
" setting the build directory.
let l:source_dir = s:system.Path([s:buildsys.project_root], v:true)
let l:build_dir = s:system.Path([s:buildsys.path_to_current_config], v:true)
" Return dictionary of options.
let l:optdict = {}
let l:optdict.opts = l:opts
let l:optdict.source_dir = l:source_dir
let l:optdict.build_dir = l:build_dir
return l:optdict
endfunction
" Refresh list of build configuration directories.
"
function! s:RefreshConfigs() abort
" List of directories inside of which a CMakeCache file is found.
let l:cache_dirs = findfile(
\ 'CMakeCache.txt',
\ s:GetBuildDirLocation() . '/**1',
\ -1)
" Transform paths to just names of directories. These will be the names of
" existing configuration directories.
call map(l:cache_dirs, {_, val -> fnamemodify(val, ':h:t')})
let s:buildsys.configs = l:cache_dirs
call s:logger.LogDebug('Build configs: %s', s:buildsys.configs)
endfunction
" Callback for RefreshTargets().
"
function! s:RefreshTargetsCb(...) abort
let l:data = s:system.ExtractStdoutCallbackData(a:000)
for l:line in l:data
if match(l:line, '\m\C\.\.\.\s') == 0
let l:target = split(l:line)[1]
let s:buildsys.targets += [l:target]
endif
endfor
endfunction
" Refresh list of available CMake targets.
"
function! s:RefreshTargets() abort
let s:buildsys.targets = []
let l:build_dir = s:buildsys.path_to_current_config
let l:command = [g:cmake_command,
\ '--build', l:build_dir,
\ '--target', 'help'
\ ]
call s:system.JobRun(
\ l:command, v:true, function('s:RefreshTargetsCb'), v:null, v:false)
endfunction
" Check if build configuration directory exists.
"
" Params:
" config : String
" configuration to check
"
" Returns:
" Boolean
" v:true if the build configuration exists, v:false otherwise
"
function! s:ConfigExists(config) abort
return index(s:buildsys.configs, a:config) >= 0
endfunction
" Set current build configuration.
"
" Params:
" config : String
" build configuration name
"
function! s:SetCurrentConfig(config) abort
let s:buildsys.current_config = a:config
let l:path = s:system.Path([s:GetBuildDirLocation(), a:config], v:false)
let s:buildsys.path_to_current_config = l:path
call s:logger.LogInfo('Current config: %s (%s)',
\ s:buildsys.current_config,
\ s:buildsys.path_to_current_config
\ )
call s:statusline.SetBuildInfo(s:buildsys.current_config)
endfunction
" Link compile commands from source directory to build directory.
"
function! s:LinkCompileCommands() abort
if !g:cmake_link_compile_commands
return
endif
let l:target = s:system.Path(
\ [s:buildsys.path_to_current_config, 'compile_commands.json'],
\ v:true
\ )
let l:link = s:system.Path(
\ [s:buildsys.project_root, 'compile_commands.json'],
\ v:true,
\ )
let l:command = [g:cmake_command, '-E', 'create_symlink', l:target, l:link]
call s:system.JobRun(l:command, v:true, v:null, v:null, v:false)
endfunction
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Public functions
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Generate a buildsystem for the project using CMake.
"
" Params:
" clean : Boolean
" whether to clean before generating
" argstring : String
" build configuration and additional CMake options
"
function! s:buildsys.Generate(clean, argstring) abort
call s:logger.LogDebug('Invoked: buildsys.Generate(%s, %s)',
\ a:clean, string(a:argstring))
let l:command = [g:cmake_command]
let l:optdict = s:ProcessArgString(a:argstring)
" Construct command.
call extend(l:command, g:cmake_generate_options)
call extend(l:command, l:optdict.opts)
if l:self.cmake_version < 313
call add(l:command, '-H' . l:optdict.source_dir)
call add(l:command, '-B' . l:optdict.build_dir)
else
call add(l:command, '-S ' . l:optdict.source_dir)
call add(l:command, '-B ' . l:optdict.build_dir)
endif
" Clean project buildsystem, if requested.
if a:clean
call l:self.Clean()
endif
" Run generate command.
call s:terminal.Run(
\ l:command, 'generate',
\ [
\ function('s:RefreshConfigs'),
\ function('s:RefreshTargets'),
\ function('s:LinkCompileCommands')
\ ],
\ [function('s:RefreshConfigs')],
\ [], []
\ )
endfunction
" Clean buildsystem.
"
function! s:buildsys.Clean() abort
call s:logger.LogDebug('Invoked: buildsys.Clean()')
if isdirectory(l:self.path_to_current_config)
call delete(l:self.path_to_current_config, 'rf')
endif
call s:RefreshConfigs()
endfunction
" Set current build configuration after checking that the configuration exists.
"
" Params:
" config : String
" build configuration name
"
function! s:buildsys.Switch(config) abort
call s:logger.LogDebug('Invoked: buildsys.Switch(%s)', a:config)
" Check that config exists.
if !s:ConfigExists(a:config)
call s:logger.EchoError(
\ "Build configuration '%s' not found, run ':CMakeGenerate %s'",
\ a:config, a:config)
call s:logger.LogError(
\ "Build configuration '%s' not found, run ':CMakeGenerate %s'",
\ a:config, a:config)
return
endif
call s:SetCurrentConfig(a:config)
call s:LinkCompileCommands()
endfunction
" Get list of configuration directories (containing a buildsystem).
"
" Returns:
" List
" list of existing configuration directories
"
function! s:buildsys.GetConfigs() abort
return l:self.configs
endfunction
" Get list of available build targets.
"
" Returns:
" List
" list of available build targets
"
function! s:buildsys.GetTargets() abort
if len(l:self.targets) == 0
call s:RefreshTargets()
endif
return l:self.targets
endfunction
" Get current build configuration.
"
" Returns:
" String
" build configuration
"
function! s:buildsys.GetCurrentConfig() abort
return l:self.current_config
endfunction
" Get path to CMake source directory of current project.
"
" Returns:
" String
" path to CMake source directory
"
function! s:buildsys.GetSourceDir() abort
return l:self.project_root
endfunction
" Get path to current build configuration.
"
" Returns:
" String
" path to build configuration
"
function! s:buildsys.GetPathToCurrentConfig() abort
return l:self.path_to_current_config
endfunction
" Get buildsys 'object'.
"
function! cmake#buildsys#Get() abort
return s:buildsys
endfunction
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Initialization
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
function! s:GetCMakeVersionCb(...) abort
let l:data = s:system.ExtractStdoutCallbackData(a:000)
for l:line in l:data
if match(l:line, '\m\C^cmake\S* version') == 0
let l:version_str = split(split(l:line)[2], '\.')
let l:major = str2nr(l:version_str[0])
let l:minor = str2nr(l:version_str[1])
let s:buildsys.cmake_version = l:major * 100 + l:minor
break
endif
endfor
endfunction
" Get CMake version. The version is stored as MAJOR * 100 + MINOR (e.g., version
" 3.13.3 would result in 313).
let s:command = [g:cmake_command, '--version']
call s:system.JobRun(
\ s:command, v:true, function('s:GetCMakeVersionCb'), v:null, v:false)
" Must be done before any other initial configuration.
let s:buildsys.project_root = s:system.Path([s:FindProjectRoot()], v:false)
call s:logger.LogInfo('Project root: %s', s:buildsys.project_root)
call s:SetCurrentConfig(g:cmake_default_config)
call s:RefreshConfigs()