comparison autoload/unreal.vim @ 2:9235d8341a18

Refactor the build system invocation commands. Now we have proper knowledge of the projects inside a codebase ("branch"). The plugin should correctly parse configuration names, find the correct module to build based on the configuration, and so on. Also, added support for generating the clang compilation database.
author Ludovic Chabant <ludovic@chabant.com>
date Fri, 22 Jan 2021 16:38:18 -0800
parents 43d0e448edce
children 613f13dc42f7
comparison
equal deleted inserted replaced
1:43d0e448edce 2:9235d8341a18
1 " unreal.vim - Work with the Unreal Engine in Vim 1 " unreal.vim - Work with the Unreal Engine in Vim
2 2
3 " Utilities {{{ 3 " Utilities {{{
4
5 let s:basedir = expand('<sfile>:p:h:h')
4 6
5 function! unreal#throw(message) 7 function! unreal#throw(message)
6 throw "unreal: ".a:message 8 throw "unreal: ".a:message
7 endfunction 9 endfunction
8 10
53 endfor 55 endfor
54 endfunction 56 endfunction
55 57
56 " }}} 58 " }}}
57 59
58 " Project Management {{{ 60 " {{{ Scripts and Cache Files
59 61
60 function! unreal#find_project_dir() abort 62 let s:scriptsdir = s:basedir.'\scripts'
61 if !empty(g:unreal_project_dir_finder) 63
62 return call(g:unreal_project_dir_finder) 64 function! unreal#get_vim_script_path(scriptname) abort
63 endif 65 return s:scriptsdir.s:dirsep.a:scriptname.s:scriptext
64 66 endfunction
65 let l:path = getcwd() 67
66 try 68 function! unreal#get_cache_path(name, ...) abort
67 let l:proj_dir = unreal#default_project_dir_finder(l:path) 69 if empty(g:unreal_branch_dir)
68 catch /^unreal:/ 70 call unreal#throw("No UE branch defined")
69 let l:proj_dir = '' 71 endif
70 endtry 72 let l:cache_dir = g:unreal_branch_dir.s:dirsep.".vimunreal"
71 call unreal#set_project_dir(l:proj_dir) 73 let l:path = l:cache_dir.s:dirsep.a:name
72 endfunction 74 if a:0 && a:1 && !isdirectory(l:cache_dir)
73 75 call mkdir(l:cache_dir)
74 function! unreal#default_project_dir_finder(path) abort 76 endif
77 return l:path
78 endfunction
79
80 " }}}
81
82 " Branch and Project Management {{{
83
84 function! unreal#find_branch_dir_and_project() abort
85 call unreal#find_branch_dir()
86
87 if !empty(g:unreal_branch_dir)
88 call unreal#find_project()
89 endif
90 endfunction
91
92 function! unreal#find_branch_dir() abort
93 if !empty(g:unreal_branch_dir_finder)
94 let l:branch_dir = call(g:unreal_branch_dir_finder)
95 else
96 let l:branch_dir = unreal#default_branch_dir_finder(getcwd())
97 endif
98
99 if !empty(l:branch_dir)
100 call unreal#set_branch_dir(l:branch_dir, 1) " Set branch silently.
101 else
102 call unreal#throw("No UE branch found!")
103 endif
104 endfunction
105
106 function! unreal#default_branch_dir_finder(path) abort
75 let l:cur = a:path 107 let l:cur = a:path
76 let l:prev = "" 108 let l:prev = ""
77 while l:cur != l:prev 109 while l:cur != l:prev
78 let l:markers = globpath(l:cur, g:unreal_project_dir_marker, 0, 1) 110 let l:markers = globpath(l:cur, g:unreal_branch_dir_marker, 0, 1)
79 if !empty(l:markers) 111 if !empty(l:markers)
80 call unreal#trace("Found marker file: ".l:markers[0]) 112 call unreal#trace("Found marker file: ".l:markers[0])
81 return l:cur 113 return l:cur
82 endif 114 endif
83 let l:prev = l:cur 115 let l:prev = l:cur
84 let l:cur = fnamemodify(l:cur, ':h') 116 let l:cur = fnamemodify(l:cur, ':h')
85 endwhile 117 endwhile
86 call unreal#throw("No UE project markers found.") 118 return ""
87 endfunction 119 endfunction
88 120
89 function! unreal#set_project_dir(project_dir, ...) abort 121 function! unreal#set_branch_dir(branch_dir, ...) abort
90 " Strip any end slashes on the directory path. 122 " Strip any end slashes on the directory path.
91 let g:unreal_project_dir = fnamemodify(a:project_dir, ':s?[/\\]$??') 123 let l:prev_dir = g:unreal_branch_dir
92 124 let g:unreal_branch_dir = fnamemodify(a:branch_dir, ':s?[/\\]$??')
93 let l:proj_was_set = !empty(g:unreal_project_dir) 125 let l:branch_was_set = !empty(g:unreal_branch_dir)
94 126
127 " Update our projects infos.
128 let g:unreal_branch_projects = unreal#get_branch_projects(g:unreal_branch_dir)
129
130 " Notify our modules.
131 if l:branch_was_set
132 call unreal#call_modules('on_branch_changed', g:unreal_branch_dir)
133 else
134 call unreal#call_modules('on_branch_cleared')
135 endif
136
137 " Auto-set the Vimcrosoft solution if that plugin is installed.
138 " TODO: move this into a module.
95 if exists(":VimcrosoftSetSln") 139 if exists(":VimcrosoftSetSln")
96 if l:proj_was_set 140 if l:branch_was_set
97 let l:sln_files = glob(g:unreal_project_dir.s:dirsep."*.sln", 0, 1) 141 let l:sln_files = glob(g:unreal_branch_dir.s:dirsep."*.sln", 0, 1)
98 if !empty(l:sln_files) 142 if !empty(l:sln_files)
99 " Vimcrosoft might have auto-found the same solution, already, 143 " Vimcrosoft might have auto-found the same solution, already,
100 " in which case we don't have to set it. 144 " in which case we don't have to set it.
101 if g:vimcrosoft_current_sln != l:sln_files[0] 145 if g:vimcrosoft_current_sln != l:sln_files[0]
102 execute "VimcrosoftSetSln ".fnameescape(l:sln_files[0]) 146 execute "VimcrosoftSetSln ".fnameescape(l:sln_files[0])
107 else 151 else
108 execute "VimcrosoftUnsetSln" 152 execute "VimcrosoftUnsetSln"
109 endif 153 endif
110 endif 154 endif
111 155
112 if l:proj_was_set
113 call unreal#call_modules('on_project_changed', g:unreal_project_dir)
114 else
115 call unreal#call_modules('on_project_cleared')
116 endif
117
118 let l:silent = a:0 && a:1 156 let l:silent = a:0 && a:1
119 if !l:silent 157 if !l:silent
120 if l:proj_was_set 158 if l:branch_was_set
121 echom "UE Project set to: ".g:unreal_project_dir 159 echom "UE branch set to: ".g:unreal_branch_dir
122 else 160 else
123 echom "UE Project cleared" 161 echom "UE branch cleared"
124 endif 162 endif
125 endif 163 endif
164 endfunction
165
166 function! unreal#find_project() abort
167 if empty(g:unreal_branch_dir)
168 call unreal#throw("No UE branch set!")
169 endif
170
171 if len(g:unreal_branch_projects) == 0
172 call unreal#throw("No UE projects found in branch: ".g:unreal_branch_dir)
173 endif
174
175 let l:proj = ""
176 let l:cached_proj_file = unreal#get_cache_path("LastProject.txt")
177 try
178 let l:cached_proj = readfile(l:cached_proj_file, '', 1)
179 catch
180 let l:cached_proj = []
181 endtry
182 if len(l:cached_proj) > 0 && !empty(l:cached_proj[0])
183 if has_key(g:unreal_branch_projects, l:cached_proj[0])
184 let l:proj = l:cached_proj[0]
185 call unreal#trace("Found previously set project: ".l:proj)
186 endif
187 endif
188
189 if l:proj == ""
190 let l:projnames = sort(keys(g:unreal_branch_projects))
191 if len(l:projnames) > 0
192 let l:proj = l:projnames[0]
193 call unreal#trace("Picking first project in branch: ".l:proj)
194 endif
195 endif
196
197 if l:proj == ""
198 call unreal#throw("No UE projects found in branch: ".g:unreal_branch_dir)
199 else
200 call unreal#set_project(l:proj)
201 endif
202 endfunction
203
204 function! unreal#set_project(projname) abort
205 let g:unreal_project = a:projname
206
207 let l:cached_proj_file = unreal#get_cache_path("LastProject.txt", 1) " Auto-create cache dir.
208 call writefile([a:projname], l:cached_proj_file)
209
210 call unreal#trace("Set UE project: ".a:projname)
211 endfunction
212
213 function! unreal#get_branch_projects(branch_dir)
214 if empty(a:branch_dir)
215 return {}
216 endif
217
218 " Reset the known projects.
219 let l:projs = {}
220 call unreal#trace("Finding projects in branch: ".a:branch_dir)
221
222 " Find project files in the branch directory.
223 let l:dirs = readdir(a:branch_dir)
224 for l:dir in l:dirs
225 let l:dirpath = a:branch_dir.s:dirsep.l:dir.s:dirsep
226 let l:uprojfiles = glob(l:dirpath."*.uproject", 0, 1)
227 if len(l:uprojfiles) > 0
228 let l:lines = readfile(l:uprojfiles[0])
229 let l:jsonraw = join(l:lines, "\n")
230 let l:json = json_decode(l:jsonraw)
231 let l:json["Path"] = l:uprojfiles[0]
232 let l:projname = fnamemodify(l:uprojfiles[0], ':t:r')
233 let l:projs[l:projname] = l:json
234 call unreal#trace("Found project: ".l:projname)
235 endif
236 endfor
237
238 return l:projs
239 endfunction
240
241 function! unreal#get_project_info(proppath) abort
242 if empty(g:unreal_project) || empty(g:unreal_branch_projects)
243 call unreal#throw("No project(s) set!")
244 endif
245
246 let l:proj = g:unreal_branch_projects[g:unreal_project]
247
248 let l:cur = l:proj
249 let l:propnames = split(a:proppath, '.')
250 for l:propname in l:propnames
251 if type(l:cur) == type([])
252 let l:cur = l:cur[str2nr(l:propname)]
253 else
254 let l:cur = l:cur[l:propname]
255 endif
256 endfor
257 endfunction
258
259 function! unreal#find_project_module_of_type(project, module_type) abort
260 if empty(a:project) || empty(g:unreal_branch_projects)
261 call unreal#throw("No project(s) set!")
262 endif
263
264 let l:proj = g:unreal_branch_projects[a:project]
265 for l:module in l:proj["Modules"]
266 if get(l:module, "Type", "") == a:module_type
267 return copy(l:module)
268 endif
269 endfor
270 return {}
126 endfunction 271 endfunction
127 272
128 let s:extra_args_version = 1 273 let s:extra_args_version = 1
129 274
130 function! unreal#generate_vimcrosoft_extra_args(solution) abort 275 function! unreal#generate_vimcrosoft_extra_args(solution) abort
158 endif 303 endif
159 endfunction 304 endfunction
160 305
161 " }}} 306 " }}}
162 307
163 " Commands {{{ 308 " Configuration and Platform {{{
164 309
165 function! unreal#generate_project_files() abort 310 let s:unreal_configs = []
166 call unreal#run_make("ugenprojfiles") 311
312 function! s:cache_unreal_configs() abort
313 if len(s:unreal_configs) == 0
314 for l:state in g:unreal_config_states
315 for l:target in g:unreal_config_targets
316 call add(s:unreal_configs, l:state.l:target)
317 endfor
318 endfor
319 endif
320 endfunction
321
322 function! s:parse_config_state_and_target(config) abort
323 let l:alen = len(a:config)
324
325 let l:config_target = ""
326 for l:target in g:unreal_config_targets
327 let l:tlen = len(l:target)
328 if l:alen > l:tlen && a:config[l:alen - l:tlen : ] == l:target
329 let l:config_target = l:target
330 break
331 endif
332 endfor
333
334 let l:config_state = a:config[0 : l:alen - t:tlen - 1]
335
336 if index(g:unreal_config_states, l:config_state) >= 0 ||
337 \index(g:unreal_config_targets, l:config_target) >= 0
338 return [l:config_state, l:config_target]
339 else
340 call unreal#throw("Invalid config state or target: ".l:config_state.l:config_target)
341 endif
342 endfunction
343
344 function! unreal#set_config(config) abort
345 let [l:config_state, l:config_target] = s:parse_config_state_and_target(a:config)
346 let g:unreal_config_state = l:config_state
347 let g:unreal_config_target = l:config_target
167 endfunction 348 endfunction
168 349
169 function! unreal#set_platform(platform) abort 350 function! unreal#set_platform(platform) abort
170 if index(g:unreal_platforms, a:platform) < 0 351 if index(g:unreal_platforms, a:platform) < 0
171 call unreal#throw("Invalid Unreal platform: ".a:platform) 352 call unreal#throw("Invalid Unreal platform: ".a:platform)
172 endif 353 endif
173 let g:unreal_project_platform = a:platform 354 let g:unreal_project_platform = a:platform
174 endfunction 355 endfunction
175 356
176 function! unreal#build(...) abort 357 " }}}
177 let l:opts = copy(g:unreal_auto_build_options) 358
178 if a:0 359 " Build {{{
179 let l:opts = a:000 + l:opts 360
180 endif 361 function! unreal#get_ubt_args(...) abort
181 let g:unreal_temp_makeprg_args__ = l:opts 362 " Start with modules we should always build.
363 let l:mod_names = keys(g:unreal_auto_build_modules)
364 let l:mod_args = copy(g:unreal_auto_build_modules)
365
366 " Function arguments are:
367 " <Project> <Platform> <Config> [<...MainModuleOptions>] [<...GlobalOptions>] <?NoGlobalModules>
368 let l:project = g:unreal_project
369 if a:0 >= 1 && !empty(a:1)
370 let l:project = a:1
371 endif
372
373 let l:platform = g:unreal_platform
374 if a:0 >= 2 && !empty(a:2)
375 let l:platform = a:2
376 endif
377
378 let [l:config_state, l:config_target] = [g:unreal_config_state, g:unreal_config_target]
379 if a:0 >= 3 && !empty(a:3)
380 let [l:config_state, l:config_target] = s:parse_config_state_and_target(a:3)
381 endif
382
383 let l:mod_opts = []
384 if a:0 >= 4
385 if type(a:4) == type([])
386 let l:mod_opts = a:4
387 else
388 let l:mod_opts = [a:4]
389 endif
390 endif
391
392 let l:global_opts = copy(g:unreal_auto_build_options)
393 if a:0 >= 5
394 if type(a:5) == type([])
395 call extend(l:global_opts, a:5)
396 else
397 call extend(l:global_opts, [a:5])
398 endif
399 endif
400
401 if a:0 >= 6 && a:6
402 let l:mod_names = []
403 endif
404
405 " Find the appropriate module for our project.
406 if l:config_target == "Editor"
407 let l:module = unreal#find_project_module_of_type(l:project, "Editor")
408 else
409 let l:module = unreal#find_project_module_of_type(l:project, "Runtime")
410 endif
411 if empty(l:module)
412 call unreal#throw("Can't find module for target '".l:config_target."' in project: ".l:project)
413 endif
414
415 " Add the module's arguments to the list.
416 call insert(l:mod_names, l:module["Name"], 0)
417 let l:mod_args[l:module["Name"]] = l:mod_opts
418
419 " Build the argument list for our modules.
420 let l:ubt_cmdline = []
421 for l:mod_name in l:mod_names
422 let l:mod_cmdline = '-Target="'.
423 \l:mod_name.' '.
424 \l:platform.' '.
425 \l:config_state
426 let l:mod_arg = l:mod_args[l:mod_name]
427 if !empty(l:mod_arg)
428 let l:mod_cmdline .= ' '.join(l:mod_arg, ' ')
429 endif
430 let l:mod_cmdline .= '"'
431
432 call add(l:ubt_cmdline, l:mod_cmdline)
433 endfor
434
435 " Add any global options.
436 call extend(l:ubt_cmdline, l:global_opts)
437
438 return l:ubt_cmdline
439 endfunction
440
441 function! unreal#build(bang, ...) abort
442 let g:__unreal_makeprg_script = "Build"
443 let g:__unreal_makeprg_args = call('unreal#get_ubt_args', a:000)
444 call unreal#run_make("ubuild", bang)
445 endfunction
446
447 function! unreal#rebuild(...) abort
448 let g:__unreal_makeprg_script = "Rebuild"
449 let g:__unreal_makeprg_args = call('unreal#get_ubt_args', a:000)
182 call unreal#run_make("ubuild") 450 call unreal#run_make("ubuild")
451 endfunction
452
453 function! unreal#clean(...) abort
454 let g:__unreal_makeprg_script = "Clean"
455 let g:__unreal_makeprg_args = call('unreal#get_ubt_args', a:000)
456 call unreal#run_make("ubuild")
457 endfunction
458
459 function! unreal#generate_compilation_database() abort
460 let g:__unreal_makeprg_script = "Build"
461 let g:__unreal_makeprg_args = unreal#get_ubt_args('', '', '', [], ['-allmodules', '-Mode=GenerateClangDatabase'], 1)
462 call unreal#run_make("ubuild")
463 endfunction
464
465 function! unreal#generate_project_files() abort
466 if !g:unreal_auto_generate_compilation_database
467 call unreal#run_make("ugenprojfiles")
468 else
469 " Generate a response file that will run both the project generation
470 " and the compilation database generation one after the other. Then we
471 " pass that to our little script wrapper.
472 let l:genscriptpath = shellescape(
473 \unreal#get_script_path("Engine/Build/BatchFiles/GenerateProjectFiles"))
474 let l:buildscriptpath = shellescape(
475 \unreal#get_script_path("Engine/Build/BatchFiles/Build"))
476 let l:buildscriptargs =
477 \unreal#get_ubt_args('', '', '', [], ['-allmodules', '-Mode=GenerateClangDatabase'], 1)
478
479 let l:rsplines = [
480 \l:genscriptpath,
481 \l:buildscriptpath.' '.join(l:buildscriptargs, ' ')
482 \]
483 let l:rsppath = tempname()
484 call unreal#trace("Writing response file: ".l:rsppath)
485 call writefile(l:rsplines, l:rsppath)
486
487 let g:__unreal_makeprg_args = l:rsppath
488 call unreal#run_make("uscriptwrapper")
489 endif
183 endfunction 490 endfunction
184 491
185 " }}} 492 " }}}
186 493
187 " Completion Functions {{{ 494 " Completion Functions {{{
200 let l:suggestions = filter(a:suggestions, 507 let l:suggestions = filter(a:suggestions,
201 \{idx, val -> val =~? l:argpat}) 508 \{idx, val -> val =~? l:argpat})
202 return s:add_unique_suggestion_trailing_space(l:suggestions) 509 return s:add_unique_suggestion_trailing_space(l:suggestions)
203 endfunction 510 endfunction
204 511
512 function! unreal#complete_projects(ArgLead, CmdLine, CursorPos)
513 return s:filter_suggestions(a:ArgLead, keys(g:unreal_branch_projects))
514 endfunction
515
205 function! unreal#complete_platforms(ArgLead, CmdLine, CursorPos) 516 function! unreal#complete_platforms(ArgLead, CmdLine, CursorPos)
206 return s:filter_suggestions(a:ArgLead, copy(g:unreal_platforms)) 517 return s:filter_suggestions(a:ArgLead, copy(g:unreal_platforms))
207 endfunction 518 endfunction
208 519
209 function! unreal#complete_configs(ArgLead, CmdLine, CursorPos) 520 function! unreal#complete_configs(ArgLead, CmdLine, CursorPos)
210 return s:filter_suggestions(a:ArgLead, copy(g:unreal_configurations)) 521 call s:cache_unreal_configs()
211 endfunction 522 return s:filter_suggestions(a:ArgLead, copy(s:unreal_configs))
212 523 endfunction
213 function! unreal#complete_build_targets(ArgLead, CmdLine, CursorPos) 524
525 function! unreal#complete_build_args(ArgLead, CmdLine, CursorPos)
214 let l:bits = split(a:CmdLine.'_', ' ') 526 let l:bits = split(a:CmdLine.'_', ' ')
215 let l:bits = l:bits[1:] " Remove the `UnrealBuild` command from the line 527 let l:bits = l:bits[1:] " Remove the `UnrealBuild` command from the line.
216 if len(l:bits) <= 1 528 if len(l:bits) <= 1
217 let l:suggestions = vimcrosoft#get_sln_project_names() 529 let l:suggestions = keys(g:unreal_branch_projects)
218 elseif len(l:bits) == 2 530 elseif len(l:bits) == 2
219 let l:suggestions = copy(g:unreal_platforms) 531 let l:suggestions = copy(g:unreal_platforms)
220 elseif len(l:bits) == 3 532 elseif len(l:bits) == 3
221 let l:suggestions = copy(g:unreal_configurations) 533 call s:cache_unreal_configs()
534 let l:suggestions = s:unreal_configs
222 elseif len(l:bits) >= 4 535 elseif len(l:bits) >= 4
223 let l:suggestions = copy(g:unreal_build_options) 536 let l:suggestions = copy(g:unreal_build_options)
224 endif 537 endif
225 return s:filter_suggestions(a:ArgLead, l:suggestions) 538 return s:filter_suggestions(a:ArgLead, l:suggestions)
226 endfunction 539 endfunction
227 540
228 " }}} 541 " }}}
229 542
230 " Build System {{{ 543 " Build System {{{
231 544
232 function! unreal#run_make(compilername) abort 545 function! unreal#run_make(compilername, ...) abort
546 let l:bang = 0
547 if a:0 && a:1
548 let l:bang = 1
549 endif
550
233 execute "compiler ".a:compilername 551 execute "compiler ".a:compilername
552
234 if exists(':Make') " Support for vim-dispatch 553 if exists(':Make') " Support for vim-dispatch
235 Make 554 if l:bang
236 else 555 Make!
237 make 556 else
557 Make
558 endif
559 else
560 if l:bang
561 make!
562 else
563 make
564 endif
238 endif 565 endif
239 endfunction 566 endfunction
240 567
241 " }}} 568 " }}}
242 569
243 " Unreal Scripts {{{ 570 " Unreal Scripts {{{
244 571
245 let s:builds_in_progress = [] 572 let s:builds_in_progress = []
246 573
247 function! unreal#get_script_path(scriptname, ...) abort 574 function! unreal#get_script_path(scriptname, ...) abort
248 return g:unreal_project_dir.s:dirsep.a:scriptname.s:scriptext 575 if s:iswin
576 let l:name = substitute(a:scriptname, '/', "\\", 'g')
577 else
578 let l:name = a:scriptname
579 endif
580 return g:unreal_branch_dir.s:dirsep.l:name.s:scriptext
249 endfunction 581 endfunction
250 582
251 " }}} 583 " }}}
252 584
253 " Initialization {{{ 585 " Initialization {{{
254 586
255 function! unreal#init() abort 587 function! unreal#init() abort
256 if g:unreal_auto_find_project 588 if g:unreal_auto_find_project
257 call unreal#find_project_dir() 589 call unreal#find_branch_dir_and_project()
258 endif 590 endif
259 endfunction 591 endfunction
260 592
261 " }}} 593 " }}}
262 594
263 " Statusline Functions {{{ 595 " Statusline Functions {{{
264 596
265 function! unreal#statusline(...) abort 597 function! unreal#statusline(...) abort
266 if empty(g:unreal_project_dir) 598 if empty(g:unreal_branch_dir)
267 return '' 599 return ''
268 endif 600 endif
269 601 if empty(g:unreal_project)
270 let l:line = 'UE:'.g:unreal_project_dir 602 return 'UE:'.g:unreal_branch_dir.':<no project>'
271 return l:line 603 endif
272 endfunction 604 return 'UE:'.g:unreal_branch_dir.':'.g:unreal_project.'('.g:unreal_config_state.g:unreal_config_target.'|'.g:unreal_platform.')'
273 605 endfunction
274 " }}} 606
607 " }}}