summaryrefslogtreecommitdiff
path: root/ale_linters/go/gobuild.vim
diff options
context:
space:
mode:
authorJoshua Rubin <joshuarubin@users.noreply.github.com>2017-02-07 13:36:04 -0700
committerw0rp <w0rp@users.noreply.github.com>2017-02-07 20:36:04 +0000
commit472631573ed46f8750fd07906068ccf14d341c9b (patch)
tree3ca0c35c660e30de83858a0e29464ec59543bcd2 /ale_linters/go/gobuild.vim
parentc0ac393297c2ebe43f3beb57a46b0ac1a451671e (diff)
downloadale-472631573ed46f8750fd07906068ccf14d341c9b.zip
try fixing go build (#297)
* try fixing go build * cache some system calls * fix /dev/null * use chained commands, use `go test -c` instead of `go tool compile` * fix some unescaped shell commands * fix a bug with explicitly setting GOPATH * implement changes requested in code review. handle errors from multiple files. fix issue when starting a new package * run `go env` as a job * ensure all functions return the proper type * fix loclist line numbers in some cases * remove multibuffer support for now
Diffstat (limited to 'ale_linters/go/gobuild.vim')
-rw-r--r--ale_linters/go/gobuild.vim213
1 files changed, 205 insertions, 8 deletions
diff --git a/ale_linters/go/gobuild.vim b/ale_linters/go/gobuild.vim
index 4abf1987..832669f2 100644
--- a/ale_linters/go/gobuild.vim
+++ b/ale_linters/go/gobuild.vim
@@ -1,17 +1,214 @@
-" Author: dzhou121 <dzhou121@gmail.com>
+" Author: Joshua Rubin <joshua@rubixconsulting.com>
" Description: go build for Go files
-function! s:FindGobuildScript() abort
- return g:ale#util#stdin_wrapper . ' .go go build -o /dev/null'
+" inspired by work from dzhou121 <dzhou121@gmail.com>
+
+function! ale_linters#go#gobuild#GoEnv(buffer) abort
+ if exists('s:go_env')
+ return ''
+ endif
+
+ return 'go env GOPATH GOROOT'
+endfunction
+
+let s:SplitChar = has('unix') ? ':' : ':'
+
+" get a list of all source directories from $GOPATH and $GOROOT
+function! s:SrcDirs() abort
+ let l:paths = split(s:go_env.GOPATH, s:SplitChar)
+ call add(l:paths, s:go_env.GOROOT)
+
+ return l:paths
+endfunction
+
+" figure out from a directory like `/home/user/go/src/some/package` that the
+" import for that path is simply `some/package`
+function! s:PackageImportPath(buffer) abort
+ let l:bufname = resolve(bufname(a:buffer))
+ let l:pkgdir = fnamemodify(l:bufname, ':p:h')
+
+ for l:path in s:SrcDirs()
+ let l:path = l:path . '/src/'
+
+ if stridx(l:pkgdir, l:path) == 0
+ return l:pkgdir[strlen(l:path):]
+ endif
+ endfor
+
+ return ''
+endfunction
+
+" get the package info data structure using `go list`
+function! ale_linters#go#gobuild#GoList(buffer, goenv_output) abort
+ if !empty(a:goenv_output)
+ let s:go_env = {
+ \ 'GOPATH': a:goenv_output[0],
+ \ 'GOROOT': a:goenv_output[1],
+ \}
+ endif
+
+ return 'go list -json ' . shellescape(s:PackageImportPath(a:buffer))
+endfunction
+
+let s:filekeys = [
+\ 'GoFiles',
+\ 'CgoFiles',
+\ 'CFiles',
+\ 'CXXFiles',
+\ 'MFiles',
+\ 'HFiles',
+\ 'FFiles',
+\ 'SFiles',
+\ 'SwigFiles',
+\ 'SwigCXXFiles',
+\ 'SysoFiles',
+\ 'TestGoFiles',
+\ 'XTestGoFiles',
+\]
+
+" get the go and test go files from the package
+" will return empty list if the package has any cgo or other invalid files
+function! s:PkgFiles(pkginfo) abort
+ let l:files = []
+
+ for l:key in s:filekeys
+ if has_key(a:pkginfo, l:key)
+ call extend(l:files, a:pkginfo[l:key])
+ endif
+ endfor
+
+ " resolve the path of the file relative to the window directory
+ return map(l:files, 'shellescape(fnamemodify(resolve(a:pkginfo.Dir . ''/'' . v:val), '':p''))')
+endfunction
+
+function! ale_linters#go#gobuild#CopyFiles(buffer, golist_output) abort
+ let l:tempdir = tempname()
+ let l:temppkgdir = l:tempdir . '/src/' . s:PackageImportPath(a:buffer)
+ call mkdir(l:temppkgdir, 'p', 0700)
+
+ if empty(a:golist_output)
+ return 'echo ' . shellescape(l:tempdir)
+ endif
+
+ " parse the output
+ let l:pkginfo = json_decode(join(a:golist_output, "\n"))
+
+ " get all files for the package
+ let l:files = s:PkgFiles(l:pkginfo)
+
+ " copy the files to a temp directory with $GOPATH structure
+ return 'cp ' . join(l:files, ' ') . ' ' . shellescape(l:temppkgdir) . ' && echo ' . shellescape(l:tempdir)
+endfunction
+
+function! ale_linters#go#gobuild#GetCommand(buffer, copy_output) abort
+ let l:tempdir = a:copy_output[0]
+ let l:importpath = s:PackageImportPath(a:buffer)
+
+ " write the a:buffer and any modified buffers from the package to the tempdir
+ for l:bufnum in range(1, bufnr('$'))
+ " ignore unloaded buffers (can't be a:buffer or a modified buffer)
+ if !bufloaded(l:bufnum)
+ continue
+ endif
+
+ " ignore non-Go buffers
+ if getbufvar(l:bufnum, '&ft') !=# 'go'
+ continue
+ endif
+
+ " only consider buffers other than a:buffer if they have the same import
+ " path as a:buffer and are modified
+ if l:bufnum != a:buffer
+ if s:PackageImportPath(l:bufnum) !=# l:importpath
+ continue
+ endif
+
+ if !getbufvar(l:bufnum, '&mod')
+ continue
+ endif
+ endif
+
+ call writefile(getbufline(l:bufnum, 1, '$'), l:tempdir . '/src/' . s:PkgFile(l:bufnum))
+ endfor
+
+ let l:gopaths = [ l:tempdir ]
+ call extend(l:gopaths, split(s:go_env.GOPATH, s:SplitChar))
+
+ return 'GOPATH=' . shellescape(join(l:gopaths, s:SplitChar)) . ' go test -c -o /dev/null ' . shellescape(l:importpath)
+endfunction
+
+function! s:PkgFile(buffer) abort
+ let l:bufname = resolve(bufname(a:buffer))
+ let l:importpath = s:PackageImportPath(a:buffer)
+ let l:fname = fnamemodify(l:bufname, ':t')
+
+ return l:importpath . '/' . l:fname
+endfunction
+
+function! s:FindBuffer(file) abort
+ for l:buffer in range(1, bufnr('$'))
+ if !buflisted(l:buffer)
+ continue
+ endif
+
+ let l:pkgfile = s:PkgFile(l:buffer)
+
+ if a:file =~ '/' . l:pkgfile . '$'
+ return l:buffer
+ endif
+ endfor
+
+ return -1
endfunction
-let g:ale#util#gobuild_script =
-\ get(g:, 'ale_go_gobuild_script', s:FindGobuildScript())
+let s:path_pattern = '[a-zA-Z]\?\\\?:\?[[:alnum:]/\.\-_]\+'
+let s:handler_pattern = '^\(' . s:path_pattern . '\):\(\d\+\):\?\(\d\+\)\?: \(.\+\)$'
+
+let s:multibuffer = 0
+
+function! ale_linters#go#gobuild#Handler(buffer, lines) abort
+ let l:output = []
+
+ for l:line in a:lines
+ let l:match = matchlist(l:line, s:handler_pattern)
+
+ if len(l:match) == 0
+ continue
+ endif
+
+ let l:buffer = s:FindBuffer(l:match[1])
+
+ if l:buffer == -1
+ continue
+ endif
+
+ if !s:multibuffer && l:buffer != a:buffer
+ " strip lines from other buffers
+ continue
+ endif
+
+ call add(l:output, {
+ \ 'bufnr': l:buffer,
+ \ 'lnum': l:match[2] + 0,
+ \ 'vcol': 0,
+ \ 'col': l:match[3] + 0,
+ \ 'text': l:match[4],
+ \ 'type': 'E',
+ \ 'nr': -1,
+ \})
+ endfor
+
+ return l:output
+endfunction
call ale#linter#Define('go', {
\ 'name': 'go build',
-\ 'output_stream': 'stderr',
\ 'executable': 'go',
-\ 'command': g:ale#util#gobuild_script,
-\ 'callback': 'ale#handlers#HandleUnixFormatAsError',
+\ 'command_chain': [
+\ {'callback': 'ale_linters#go#gobuild#GoEnv', 'output_stream': 'stdout'},
+\ {'callback': 'ale_linters#go#gobuild#GoList', 'output_stream': 'stdout'},
+\ {'callback': 'ale_linters#go#gobuild#CopyFiles', 'output_stream': 'stdout'},
+\ {'callback': 'ale_linters#go#gobuild#GetCommand', 'output_stream': 'stderr'},
+\ ],
+\ 'callback': 'ale_linters#go#gobuild#Handler',
\})