From 472631573ed46f8750fd07906068ccf14d341c9b Mon Sep 17 00:00:00 2001 From: Joshua Rubin Date: Tue, 7 Feb 2017 13:36:04 -0700 Subject: [PATCH] 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 --- ale_linters/go/gobuild.vim | 213 +++++++++++++++++++++++++++++++++++-- 1 file changed, 205 insertions(+), 8 deletions(-) diff --git a/ale_linters/go/gobuild.vim b/ale_linters/go/gobuild.vim index 4abf198..832669f 100644 --- a/ale_linters/go/gobuild.vim +++ b/ale_linters/go/gobuild.vim @@ -1,17 +1,214 @@ -" Author: dzhou121 +" Author: Joshua Rubin " 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 + +function! ale_linters#go#gobuild#GoEnv(buffer) abort + if exists('s:go_env') + return '' + endif + + return 'go env GOPATH GOROOT' endfunction -let g:ale#util#gobuild_script = -\ get(g:, 'ale_go_gobuild_script', s:FindGobuildScript()) +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 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', \})