Skip to content

Commit

Permalink
#2132 Unify temporary file management in command.vim
Browse files Browse the repository at this point in the history
  • Loading branch information
w0rp committed Jan 26, 2019
1 parent f12d312 commit cf14d0a
Show file tree
Hide file tree
Showing 17 changed files with 247 additions and 212 deletions.
2 changes: 1 addition & 1 deletion ale_linters/ada/gcc.vim
Expand Up @@ -12,7 +12,7 @@ function! ale_linters#ada#gcc#GetCommand(buffer) abort
" the .ali file may be created even if no code generation is attempted.
" The output file name must match the source file name (except for the
" extension), so here we cannot use the null file as output.
let l:tmp_dir = fnamemodify(ale#engine#CreateDirectory(a:buffer), ':p')
let l:tmp_dir = fnamemodify(ale#command#CreateDirectory(a:buffer), ':p')
let l:out_file = l:tmp_dir . fnamemodify(bufname(a:buffer), ':t:r') . '.o'

" -gnatc: Check syntax and semantics only (no code generation attempted)
Expand Down
2 changes: 1 addition & 1 deletion ale_linters/cs/mcsc.vim
Expand Up @@ -30,7 +30,7 @@ function! ale_linters#cs#mcsc#GetCommand(buffer) abort

" register temporary module target file with ale
" register temporary module target file with ALE.
let l:out = ale#engine#CreateFile(a:buffer)
let l:out = ale#command#CreateFile(a:buffer)

" The code is compiled as a module and the output is redirected to a
" temporary file.
Expand Down
2 changes: 1 addition & 1 deletion ale_linters/cuda/nvcc.vim
Expand Up @@ -7,7 +7,7 @@ call ale#Set('cuda_nvcc_options', '-std=c++11')
function! ale_linters#cuda#nvcc#GetCommand(buffer) abort
" Unused: use ale#util#nul_file
" let l:output_file = ale#util#Tempname() . '.ii'
" call ale#engine#ManageFile(a:buffer, l:output_file)
" call ale#command#ManageFile(a:buffer, l:output_file)
return '%e -cuda'
\ . ale#Pad(ale#c#IncludeOptions(ale#c#FindLocalHeaderPaths(a:buffer)))
\ . ale#Pad(ale#Var(a:buffer, 'cuda_nvcc_options'))
Expand Down
2 changes: 1 addition & 1 deletion ale_linters/elixir/mix.vim
Expand Up @@ -32,7 +32,7 @@ endfunction
function! ale_linters#elixir#mix#GetCommand(buffer) abort
let l:project_root = ale#handlers#elixir#FindMixProjectRoot(a:buffer)

let l:temp_dir = ale#engine#CreateDirectory(a:buffer)
let l:temp_dir = ale#command#CreateDirectory(a:buffer)

let l:mix_build_path = has('win32')
\ ? 'set MIX_BUILD_PATH=' . ale#Escape(l:temp_dir) . ' &&'
Expand Down
2 changes: 1 addition & 1 deletion ale_linters/erlang/erlc.vim
Expand Up @@ -4,7 +4,7 @@ let g:ale_erlang_erlc_options = get(g:, 'ale_erlang_erlc_options', '')

function! ale_linters#erlang#erlc#GetCommand(buffer) abort
let l:output_file = ale#util#Tempname()
call ale#engine#ManageFile(a:buffer, l:output_file)
call ale#command#ManageFile(a:buffer, l:output_file)

return 'erlc -o ' . ale#Escape(l:output_file)
\ . ' ' . ale#Var(a:buffer, 'erlang_erlc_options')
Expand Down
2 changes: 1 addition & 1 deletion ale_linters/java/javac.vim
Expand Up @@ -73,7 +73,7 @@ function! ale_linters#java#javac#GetCommand(buffer, import_paths) abort
endif

" Create .class files in a temporary directory, which we will delete later.
let l:class_file_directory = ale#engine#CreateDirectory(a:buffer)
let l:class_file_directory = ale#command#CreateDirectory(a:buffer)

" Always run javac from the directory the file is in, so we can resolve
" relative paths correctly.
Expand Down
2 changes: 1 addition & 1 deletion ale_linters/thrift/thrift.vim
Expand Up @@ -16,7 +16,7 @@ function! ale_linters#thrift#thrift#GetCommand(buffer) abort
let l:generators = ['cpp']
endif

let l:output_dir = ale#engine#CreateDirectory(a:buffer)
let l:output_dir = ale#command#CreateDirectory(a:buffer)

return '%e'
\ . ale#Pad(join(map(copy(l:generators), "'--gen ' . v:val")))
Expand Down
2 changes: 1 addition & 1 deletion ale_linters/verilog/verilator.vim
Expand Up @@ -10,7 +10,7 @@ function! ale_linters#verilog#verilator#GetCommand(buffer) abort
let l:filename = ale#util#Tempname() . '_verilator_linted.v'

" Create a special filename, so we can detect it in the handler.
call ale#engine#ManageFile(a:buffer, l:filename)
call ale#command#ManageFile(a:buffer, l:filename)
let l:lines = getbufline(a:buffer, 1, '$')
call ale#util#Writefile(a:buffer, l:lines, l:filename)

Expand Down
2 changes: 1 addition & 1 deletion ale_linters/vim/ale_custom_linting_rules.vim
Expand Up @@ -25,7 +25,7 @@ endfunction
function! ale_linters#vim#ale_custom_linting_rules#GetCommand(buffer) abort
let l:dir = s:GetALEProjectDir(a:buffer)

let l:temp_dir = ale#engine#CreateDirectory(a:buffer)
let l:temp_dir = ale#command#CreateDirectory(a:buffer)
let l:temp_file = l:temp_dir . '/example.vim'

let l:lines = getbufline(a:buffer, 1, '$')
Expand Down
135 changes: 130 additions & 5 deletions autoload/ale/command.vim
@@ -1,6 +1,121 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Special command formatting for creating temporary files and
" passing buffer filenames easily.
" Description: Functions for formatting command strings, running commands, and
" managing files during linting and fixing cycles.

" This dictionary holds lists of files and directories to remove later.
if !exists('s:managed_data')
let s:managed_data = {}
endif

" Used to get the data in tests.
function! ale#command#GetData() abort
return deepcopy(s:managed_data)
endfunction

function! ale#command#ClearData() abort
let s:managed_data = {}
endfunction

function! ale#command#ManageFile(buffer, file) abort
if !has_key(s:managed_data, a:buffer)
let s:managed_data[a:buffer] = {'file_list': [], 'directory_list': []}
endif

call add(s:managed_data[a:buffer].file_list, a:file)
endfunction

function! ale#command#ManageDirectory(buffer, directory) abort
if !has_key(s:managed_data, a:buffer)
let s:managed_data[a:buffer] = {'file_list': [], 'directory_list': []}
endif

call add(s:managed_data[a:buffer].directory_list, a:directory)
endfunction

function! ale#command#CreateFile(buffer) abort
" This variable can be set to 1 in tests to stub this out.
if get(g:, 'ale_create_dummy_temporary_file')
return 'TEMP'
endif

let l:temporary_file = ale#util#Tempname()
call ale#command#ManageFile(a:buffer, l:temporary_file)

return l:temporary_file
endfunction

" Create a new temporary directory and manage it in one go.
function! ale#command#CreateDirectory(buffer) abort
" This variable can be set to 1 in tests to stub this out.
if get(g:, 'ale_create_dummy_temporary_file')
return 'TEMP_DIR'
endif

let l:temporary_directory = ale#util#Tempname()
" Create the temporary directory for the file, unreadable by 'other'
" users.
call mkdir(l:temporary_directory, '', 0750)
call ale#command#ManageDirectory(a:buffer, l:temporary_directory)

return l:temporary_directory
endfunction

function! ale#command#RemoveManagedFiles(buffer) abort
let l:info = get(s:managed_data, a:buffer, {})

if !empty(l:info)
\&& (
\ !exists('*ale#engine#IsCheckingBuffer')
\ || !ale#engine#IsCheckingBuffer(a:buffer)
\)
\&& (
\ !has_key(g:ale_fix_buffer_data, a:buffer)
\ || g:ale_fix_buffer_data[a:buffer].done
\)
" We can't delete anything in a sandbox, so wait until we escape from
" it to delete temporary files and directories.
if ale#util#InSandbox()
return
endif

" Delete files with a call akin to a plan `rm` command.
for l:filename in l:info.file_list
call delete(l:filename)
endfor

" Delete directories like `rm -rf`.
" Directories are handled differently from files, so paths that are
" intended to be single files can be set up for automatic deletion
" without accidentally deleting entire directories.
for l:directory in l:info.directory_list
call delete(l:directory, 'rf')
endfor

call remove(s:managed_data, a:buffer)
endif
endfunction

function! ale#command#CreateTempFile(buffer, temporary_file, input) abort
if empty(a:temporary_file)
" There is no file, so we didn't create anything.
return 0
endif

" Use an existing list of lines of input if we have it, or get the lines
" from the file.
let l:lines = a:input isnot v:null ? a:input : getbufline(a:buffer, 1, '$')

let l:temporary_directory = fnamemodify(a:temporary_file, ':h')
" Create the temporary directory for the file, unreadable by 'other'
" users.
call mkdir(l:temporary_directory, '', 0750)
" Automatically delete the directory later.
call ale#command#ManageDirectory(a:buffer, l:temporary_directory)
" Write the buffer out to a file.
call ale#util#Writefile(a:buffer, l:lines, a:temporary_file)

return 1
endfunction

function! s:TemporaryFilename(buffer) abort
let l:filename = fnamemodify(bufname(a:buffer), ':t')
Expand All @@ -16,11 +131,17 @@ function! s:TemporaryFilename(buffer) abort
return ale#util#Tempname() . (has('win32') ? '\' : '/') . l:filename
endfunction

" Given part of a command, replace any % with %%, so that no characters in
" the string will be replaced with filenames, etc.
function! ale#command#EscapeCommandPart(command_part) abort
return substitute(a:command_part, '%', '%%', 'g')
endfunction

" Given a command string, replace every...
" %s -> with the current filename
" %t -> with the name of an unused file in a temporary directory
" %% -> with a literal %
function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_needed, CreateTemporaryFileForJob) abort
function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_needed, input) abort
let l:temporary_file = ''
let l:command = a:command

Expand All @@ -40,7 +161,7 @@ function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_ne
let l:command = substitute(l:command, '%s', '\=ale#Escape(l:filename)', 'g')
endif

if l:command =~# '%t'
if a:input isnot v:false && l:command =~# '%t'
" Create a temporary filename, <temp_dir>/<original_basename>
" The file itself will not be created by this function.
let l:temporary_file = s:TemporaryFilename(a:buffer)
Expand All @@ -58,7 +179,11 @@ function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_ne
let l:command = l:command . ' < ' . ale#Escape(l:temporary_file)
endif

let l:file_created = a:CreateTemporaryFileForJob(a:buffer, l:temporary_file)
let l:file_created = ale#command#CreateTempFile(
\ a:buffer,
\ l:temporary_file,
\ a:input,
\)

return [l:temporary_file, l:command, l:file_created]
endfunction
94 changes: 12 additions & 82 deletions autoload/ale/engine.vim
Expand Up @@ -74,15 +74,11 @@ function! ale#engine#InitBufferInfo(buffer) abort
" job_list will hold the list of job IDs
" active_linter_list will hold the list of active linter names
" loclist holds the loclist items after all jobs have completed.
" temporary_file_list holds temporary files to be cleaned up
" temporary_directory_list holds temporary directories to be cleaned up
let g:ale_buffer_info[a:buffer] = {
\ 'job_list': [],
\ 'active_linter_list': [],
\ 'active_other_sources_list': [],
\ 'loclist': [],
\ 'temporary_file_list': [],
\ 'temporary_directory_list': [],
\}

return 1
Expand All @@ -104,73 +100,25 @@ endfunction
" Register a temporary file to be managed with the ALE engine for
" a current job run.
function! ale#engine#ManageFile(buffer, filename) abort
call ale#engine#InitBufferInfo(a:buffer)
call add(g:ale_buffer_info[a:buffer].temporary_file_list, a:filename)
" TODO: Emit deprecation warning here later.
call ale#command#ManageFile(a:buffer, a:filename)
endfunction

" Same as the above, but manage an entire directory.
function! ale#engine#ManageDirectory(buffer, directory) abort
call ale#engine#InitBufferInfo(a:buffer)
call add(g:ale_buffer_info[a:buffer].temporary_directory_list, a:directory)
" TODO: Emit deprecation warning here later.
call ale#command#ManageDirectory(a:buffer, a:directory)
endfunction

function! ale#engine#CreateFile(buffer) abort
" This variable can be set to 1 in tests to stub this out.
if get(g:, 'ale_create_dummy_temporary_file')
return 'TEMP'
endif

let l:temporary_file = ale#util#Tempname()
call ale#engine#ManageFile(a:buffer, l:temporary_file)

return l:temporary_file
" TODO: Emit deprecation warning here later.
return ale#command#CreateFile(a:buffer)
endfunction

" Create a new temporary directory and manage it in one go.
function! ale#engine#CreateDirectory(buffer) abort
" This variable can be set to 1 in tests to stub this out.
if get(g:, 'ale_create_dummy_temporary_file')
return 'TEMP_DIR'
endif

let l:temporary_directory = ale#util#Tempname()
" Create the temporary directory for the file, unreadable by 'other'
" users.
call mkdir(l:temporary_directory, '', 0750)
call ale#engine#ManageDirectory(a:buffer, l:temporary_directory)

return l:temporary_directory
endfunction

function! ale#engine#RemoveManagedFiles(buffer) abort
let l:info = get(g:ale_buffer_info, a:buffer, {})

" We can't delete anything in a sandbox, so wait until we escape from
" it to delete temporary files and directories.
if ale#util#InSandbox()
return
endif

" Delete files with a call akin to a plan `rm` command.
if has_key(l:info, 'temporary_file_list')
for l:filename in l:info.temporary_file_list
call delete(l:filename)
endfor

let l:info.temporary_file_list = []
endif

" Delete directories like `rm -rf`.
" Directories are handled differently from files, so paths that are
" intended to be single files can be set up for automatic deletion without
" accidentally deleting entire directories.
if has_key(l:info, 'temporary_directory_list')
for l:directory in l:info.temporary_directory_list
call delete(l:directory, 'rf')
endfor

let l:info.temporary_directory_list = []
endif
" TODO: Emit deprecation warning here later.
return ale#command#CreateDirectory(a:buffer)
endfunction

function! s:GatherOutput(job_id, line) abort
Expand Down Expand Up @@ -321,7 +269,7 @@ function! ale#engine#SetResults(buffer, loclist) abort

" Automatically remove all managed temporary files and directories
" now that all jobs have completed.
call ale#engine#RemoveManagedFiles(a:buffer)
call ale#command#RemoveManagedFiles(a:buffer)

" Call user autocommands. This allows users to hook into ALE's lint cycle.
silent doautocmd <nomodeline> User ALELintPost
Expand Down Expand Up @@ -472,26 +420,8 @@ endfunction
" Given part of a command, replace any % with %%, so that no characters in
" the string will be replaced with filenames, etc.
function! ale#engine#EscapeCommandPart(command_part) abort
return substitute(a:command_part, '%', '%%', 'g')
endfunction

function! s:CreateTemporaryFileForJob(buffer, temporary_file) abort
if empty(a:temporary_file)
" There is no file, so we didn't create anything.
return 0
endif

let l:temporary_directory = fnamemodify(a:temporary_file, ':h')
" Create the temporary directory for the file, unreadable by 'other'
" users.
call mkdir(l:temporary_directory, '', 0750)
" Automatically delete the directory later.
call ale#engine#ManageDirectory(a:buffer, l:temporary_directory)
" Write the buffer out to a file.
let l:lines = getbufline(a:buffer, 1, '$')
call ale#util#Writefile(a:buffer, l:lines, a:temporary_file)

return 1
" TODO: Emit deprecation warning here later.
return ale#command#EscapeCommandPart(a:command_part)
endfunction

" Run a job.
Expand All @@ -517,7 +447,7 @@ function! s:RunJob(options) abort
\ l:executable,
\ l:command,
\ l:read_buffer,
\ function('s:CreateTemporaryFileForJob'),
\ v:null,
\)

if l:file_created
Expand Down

0 comments on commit cf14d0a

Please sign in to comment.