├── .gitignore ├── README.md ├── autoload ├── erlang_compiler.vim └── erlang_compiler │ └── errors.vim ├── compiler ├── erlang.vim └── erlang_check.erl ├── doc └── vim-erlang-compiler.txt ├── ftplugin └── erlang.vim └── plugin └── erlang_compiler.vim /.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-erlang-compiler 2 | 3 | `vim-erlang-compiler` is an Erlang **syntax checking and compiler plugin** for 4 | Vim. 5 | 6 | ## Table of Contents 7 | 8 | * [Installation](#installation) 9 | * [Documentation](#documentation) 10 | * [Troubleshooting](#troubleshooting) 11 | * ["I get compilation error"](#i-get-compilation-error) 12 | * [Contributing](#contributing) 13 | 14 | ## Installation 15 | 16 |
17 | Vim's built-in package manager 18 | 19 | This is the recommended installation method if you use at least Vim 8 and you 20 | don't use another package manager. 21 | 22 | Information about Vim's built-in package manager: [`:help packages`]. 23 | 24 | Installation steps: 25 | 26 | 1. Clone this repository (you can replace `foo` with the directory name of your 27 | choice): 28 | 29 | ```sh 30 | $ git clone https://github.com/vim-erlang/vim-erlang-compiler.git \ 31 | ~/.vim/pack/foo/start/vim-erlang-compiler 32 | ``` 33 | 34 | 2. Restart Vim. 35 | 36 | 3. Generate help page (replace `foo` with the same directory name as above): 37 | 38 | ``` 39 | :helptags ~/.vim/pack/foo/start/vim-erlang-compiler/doc 40 | ``` 41 |
42 | 43 |
44 | Pathogen 45 | 46 | Information about Pathogen: [Pathogen repository]. 47 | 48 | Installation steps: 49 | 50 | 1. Clone this repository: 51 | 52 | ``` 53 | $ git clone https://github.com/vim-erlang/vim-erlang-compiler.git \ 54 | ~/.vim/bundle/vim-erlang-compiler 55 | ``` 56 | 57 | 2. Restart Vim. 58 | 59 | 3. Generate help page: 60 | 61 | ``` 62 | :Helptags 63 | ``` 64 |
65 | 66 |
67 | Vundle 68 | 69 | Information about Vundle: [Vundle repository]. 70 | 71 | Installation steps: 72 | 73 | 1. Add `vim-erlang-compiler` to your plugin list in `.vimrc` by inserting 74 | the line that starts with `Plugin`: 75 | 76 | ``` 77 | call vundle#begin() 78 | [...] 79 | Plugin 'vim-erlang/vim-erlang-compiler' 80 | [...] 81 | call vundle#end() 82 | ``` 83 | 84 | 2. Restart Vim. 85 | 86 | 3. Run `:PluginInstall`. 87 |
88 | 89 |
90 | Vim-Plug 91 | 92 | Information about Vim-Plug: [vim-plug repository]. 93 | 94 | Installation steps: 95 | 96 | 1. Add `vim-erlang-compiler` to your plugin list in `.vimrc` by inserting the 97 | line that starts with `Plug`: 98 | 99 | ``` 100 | call plug#begin() 101 | [...] 102 | Plug 'vim-erlang/vim-erlang-compiler' 103 | [...] 104 | call plug#end() 105 | ``` 106 | 107 | 2. Restart Vim. 108 | 109 | 3. Run `:PlugInstall`. 110 |
111 | 112 | ## Documentation 113 | 114 | * On the web: [user documentation][doc]. 115 | * Inside Vim: `:help vim-erlang-compiler`. 116 | 117 | ## Troubleshooting 118 | 119 | ### "I get compilation error" 120 | 121 | If `vim-erlang-compiler` shows you a compilation error even though there is no 122 | compilation error when using your usual build tool, please follow these steps: 123 | 124 | 1. Compile your project with your usual build tool first (e.g., `rebar 125 | compile`, `rebar3 compile` or `make`), and then try again with 126 | `vim-erlang-compiler`. 127 | 128 | Reason: `vim-erlang-compiler` needs the `ebin` directories and `app` files 129 | to be in place, because that is how Erlang itself finds applications when 130 | compiling the `-include_lib` directives. 131 | 132 | 2. Execute the following command from the terminal: 133 | 134 | ``` 135 | $ /path/to/vim-erlang-compiler/compiler/erlang_check.erl \ 136 | --verbose /path/to/myfile.erl 137 | ``` 138 | 139 | Reason: 140 | 141 | - `vim-erlang-compiler` uses `erlang_check.erl` to compile the Erlang 142 | source files. The `--verbose` option shows more information, which might 143 | help you to figure out the problem. 144 | 145 | - If this produces the same error, we can be sure that the problem is on 146 | the Erlang side (not the Vim side). 147 | 148 | 3. Check the existing open issues on GitHub. Someone may have already reported 149 | the same problem. 150 | 151 | 4. If none of the above helps, open a new issue with the following information: 152 | 153 | - Which build system do you use? `rebar2`, `rebar3`, Makefile, or nothing? 154 | 155 | - What is the piece of code that cannot be compiled? 156 | 157 | - What is the verbose output of the `erlang_check.erl` script? (See 158 | step 2.) 159 | 160 | - It helps if you can share the project which fails to compile (or a 161 | simplified version of it). 162 | 163 | ### I modified `g:erlang_make_options` but it didn't take effect 164 | 165 | See the "Reloading" paragraph in the `g:erlang_make_options_rules` section in 166 | the [user documentation][doc]. 167 | 168 | ## Contributing 169 | 170 | * Please read the [Contributing][vim-erlang-contributing] section of the 171 | [`vim-erlang`] README. 172 | 173 | * If you modify [`compiler/erlang_check.erl`], please: 174 | 175 | - update the tests in the [`vim-erlang`] repository. 176 | 177 | - also modify `vim-erlang-omnicomplete` (if you modify the 178 | ["load build information" code block][common-code-block]) 179 | 180 | 181 | 182 | [`:help packages`]: https://vimhelp.org/repeat.txt.html#packages 183 | [`compiler/erlang_check.erl`]: compiler/erlang_check.erl 184 | [`vim-erlang`]: https://github.com/vim-erlang/vim-erlang 185 | [common-code-block]: https://github.com/vim-erlang/vim-erlang-compiler/blob/ac5088087ab43692f0b31c8d006535c00db8a174/compiler/erlang_check.erl#L272-L1107 186 | [doc]: https://github.com/vim-erlang/vim-erlang-compiler/blob/master/doc/vim-erlang-compiler.txt 187 | [Pathogen repository]: https://github.com/tpope/vim-pathogen 188 | [vim-erlang-contributing]: https://github.com/vim-erlang/vim-erlang#contributing 189 | [vim-plug repository]: https://github.com/junegunn/vim-plug 190 | [Vundle repository]: https://github.com/VundleVim/Vundle.vim 191 | -------------------------------------------------------------------------------- /autoload/erlang_compiler.vim: -------------------------------------------------------------------------------- 1 | " vim-erlang-compiler file 2 | " Language: Erlang 3 | " Author: Pawel 'kTT' Salata 4 | " Contributors: Ricardo Catalinas Jiménez 5 | " James Fish 6 | " License: Vim license 7 | 8 | if exists('g:autoloaded_erlang_compiler') 9 | finish 10 | endif 11 | 12 | let s:cpo_save = &cpo 13 | set cpo&vim 14 | 15 | let g:autoloaded_erlang_compiler = 1 16 | let s:show_errors = 0 17 | 18 | sign define ErlangError text=>> texthl=Error 19 | sign define ErlangWarning text=>> texthl=Todo 20 | 21 | function erlang_compiler#EnableShowErrors() 22 | augroup erlang_compiler 23 | autocmd! 24 | autocmd BufWritePost *.erl,*.escript call erlang_compiler#AutoRun(expand("")+0) 25 | autocmd BufDelete *.erl,*.hrl,*.escript call erlang_compiler#Unload(expand("")+0) 26 | autocmd CursorHold,CursorMoved *.erl,*.hrl,*.escript 27 | \ call erlang_compiler#EchoLineError(expand("")+0, getpos(".")) 28 | augroup END 29 | let s:show_errors = 1 30 | endfunction 31 | 32 | function erlang_compiler#DisableShowErrors() 33 | call erlang_compiler#errors#Clear() 34 | augroup erlang_compiler 35 | autocmd! 36 | augroup END 37 | let s:show_errors = 0 38 | endfunction 39 | 40 | function erlang_compiler#ToggleShowErrors() 41 | if s:show_errors 42 | call erlang_compiler#DisableShowErrors() 43 | echo "Showing Erlang errors off." 44 | else 45 | call erlang_compiler#EnableShowErrors() 46 | echo "Showing Erlang errors on." 47 | endif 48 | endfunction 49 | 50 | function erlang_compiler#AutoRun(buffer) 51 | let info = erlang_compiler#GetLocalInfo() 52 | try 53 | compiler erlang 54 | let &l:makeprg = fnameescape(g:erlang_compiler_check_script) . ' ' . 55 | \ erlang_compiler#GetFlymakeOptions() 56 | if !g:erlang_quickfix_support 57 | setlocal shellpipe=> 58 | execute "silent lmake!" shellescape(bufname(a:buffer), 1) 59 | call erlang_compiler#errors#SetList(a:buffer, getloclist(0)) 60 | else 61 | setlocal shellpipe=> 62 | execute "silent make!" shellescape(bufname(a:buffer), 1) 63 | call erlang_compiler#errors#SetList(a:buffer, getqflist()) 64 | endif 65 | finally 66 | call erlang_compiler#SetLocalInfo(info) 67 | endtry 68 | endfunction 69 | 70 | function erlang_compiler#GetFlymakeOptions() 71 | let abs_filename = expand('%:p') 72 | let flymake_options = g:erlang_flymake_options 73 | for rule in g:erlang_flymake_options_rules 74 | if abs_filename =~# get(rule, 'path_re', '') 75 | let flymake_options = rule['options'] 76 | break 77 | endif 78 | endfor 79 | return flymake_options 80 | endfunction 81 | 82 | function erlang_compiler#GetLocalInfo() 83 | return [get(b:, 'current_compiler', ''), &l:makeprg, &l:efm, &l:shellpipe] 84 | endfunction 85 | 86 | function erlang_compiler#SetLocalInfo(info) 87 | let [name, &l:makeprg, &l:efm, &l:shellpipe] = a:info 88 | if empty(name) 89 | unlet! b:current_compiler 90 | else 91 | let b:current_compiler = name 92 | endif 93 | endfunction 94 | 95 | function erlang_compiler#Unload(bufnr) 96 | call erlang_compiler#errors#DelList(a:bufnr) 97 | call erlang_compiler#errors#DelLineErrors(a:bufnr) 98 | endfunction 99 | 100 | function erlang_compiler#EchoLineError(bufnr, pos) 101 | call erlang_compiler#errors#EchoLineError(a:bufnr, a:pos[1]) 102 | endfunction 103 | 104 | let &cpo = s:cpo_save 105 | unlet s:cpo_save 106 | -------------------------------------------------------------------------------- /autoload/erlang_compiler/errors.vim: -------------------------------------------------------------------------------- 1 | " vim-erlang-compiler file 2 | " Language: Erlang 3 | " Author: Pawel 'kTT' Salata 4 | " Contributors: Ricardo Catalinas Jiménez 5 | " James Fish 6 | " License: Vim license 7 | 8 | if exists('g:autoloaded_erlang_compiler_errors') 9 | finish 10 | endif 11 | 12 | let s:cpo_save = &cpo 13 | set cpo&vim 14 | 15 | let g:autoloaded_erlang_compiler_errors = 1 16 | 17 | let g:erlang_errors = {} 18 | let g:erlang_errors_by_srcnr = {} 19 | let g:erlang_errors_by_lnum = {} 20 | let g:erlang_next_error_id = 2000 21 | 22 | function erlang_compiler#errors#Clear() 23 | for error_id in keys(g:erlang_errors) 24 | call erlang_compiler#errors#DelError(error_id) 25 | endfor 26 | endfunction 27 | 28 | function erlang_compiler#errors#SetList(srcnr, loclist) 29 | call erlang_compiler#errors#DelList(a:srcnr) 30 | for loc_error in a:loclist 31 | let error = copy(loc_error) 32 | " QF locations in an unnamed file (i.e. generic warnings) get bufnr=0: Ignore these. 33 | " NB: getqflist can give us a previously not-loaded buffer nr 34 | " which may not even appear in :bufs/:ls but is available and all 35 | " marks are pre-loaded if you navigate to it from the qflist 36 | if error.bufnr == 0 37 | continue 38 | endif 39 | if error.type != 'W' 40 | let error.type = 'E' 41 | endif 42 | let error.srcnr = a:srcnr 43 | let error_id = erlang_compiler#errors#GetId() 44 | call erlang_compiler#errors#AddError(error_id, error) 45 | endfor 46 | endfunction 47 | 48 | function erlang_compiler#errors#DelList(srcnr) 49 | if !has_key(g:erlang_errors_by_srcnr, a:srcnr) 50 | let g:erlang_errors_by_srcnr[a:srcnr] = {} 51 | endif 52 | for error_id in keys(g:erlang_errors_by_srcnr[a:srcnr]) 53 | call erlang_compiler#errors#DelError(error_id) 54 | endfor 55 | endfunction 56 | 57 | function erlang_compiler#errors#AddError(error_id, error) 58 | let error = copy(a:error) 59 | let error.id = copy(a:error_id) 60 | let g:erlang_errors[a:error_id] = error 61 | call erlang_compiler#errors#AddSign(a:error_id, error) 62 | call erlang_compiler#errors#AddSrcError(error.srcnr, a:error_id, error) 63 | call erlang_compiler#errors#AddLineError(error.bufnr, error.lnum, a:error_id, error) 64 | endfunction 65 | 66 | function erlang_compiler#errors#DelError(error_id) 67 | let error = g:erlang_errors[a:error_id] 68 | call erlang_compiler#errors#DelLineError(error.bufnr, error.lnum, a:error_id) 69 | call erlang_compiler#errors#DelSrcError(error.srcnr, a:error_id) 70 | call erlang_compiler#errors#DelSign(a:error_id) 71 | call remove(g:erlang_errors, a:error_id) 72 | call erlang_compiler#errors#DelId(a:error_id) 73 | endfunction 74 | 75 | function erlang_compiler#errors#AddSrcError(srcnr, error_id, error) 76 | if !has_key(g:erlang_errors_by_srcnr, a:srcnr) 77 | let g:erlang_errors_by_srcnr[a:srcnr] = {} 78 | endif 79 | let src_dict = g:erlang_errors_by_srcnr[a:srcnr] 80 | let src_dict[a:error_id] = copy(a:error) 81 | endfunction 82 | 83 | function erlang_compiler#errors#DelSrcError(srcnr, error_id) 84 | call remove(g:erlang_errors_by_srcnr[a:srcnr], a:error_id) 85 | endfunction 86 | 87 | function erlang_compiler#errors#GetId() 88 | let next_id = copy(g:erlang_next_error_id) 89 | let found = 0 90 | while !found 91 | if !has_key(g:erlang_errors, next_id) 92 | let found = 1 93 | let g:erlang_next_error_id = next_id + 1 94 | else 95 | let next_id += 1 96 | endif 97 | endwhile 98 | return copy(next_id) 99 | endfunction 100 | 101 | function erlang_compiler#errors#DelId(error_id) 102 | if g:erlang_next_error_id > a:error_id 103 | let g:erlang_next_error_id = copy(a:error_id) 104 | endif 105 | endfunction 106 | 107 | function erlang_compiler#errors#AddSign(error_id, error) 108 | let type = a:error.type == "W" ? "ErlangWarning" : "ErlangError" 109 | if a:error.lnum > 0 110 | execute "sign place" a:error_id "line=" . a:error.lnum "name=" . type "buffer=" . a:error.bufnr 111 | endif 112 | endfunction 113 | 114 | function erlang_compiler#errors#DelSign(error_id) 115 | execute "sign unplace" a:error_id 116 | endfunction 117 | 118 | function erlang_compiler#errors#AddLineError(bufnr, lnum, error_id, error) 119 | if !has_key(g:erlang_errors_by_lnum, a:bufnr) 120 | let g:erlang_errors_by_lnum[a:bufnr] = {} 121 | endif 122 | let buf_dict = g:erlang_errors_by_lnum[a:bufnr] 123 | if !has_key(buf_dict, a:lnum) 124 | let buf_dict[a:lnum] = {'E':{}, 'W': {}} 125 | endif 126 | let line_dict = buf_dict[a:lnum] 127 | let type_dict = line_dict[a:error.type] 128 | let type_dict[a:error_id] = copy(a:error) 129 | endfunction 130 | 131 | function erlang_compiler#errors#DelLineError(bufnr, lnum, error_id) 132 | let buf_dict = g:erlang_errors_by_lnum[a:bufnr] 133 | let line_dict = buf_dict[a:lnum] 134 | if has_key(line_dict.E, a:error_id) 135 | call remove(line_dict.E, a:error_id) 136 | else 137 | call remove(line_dict.W, a:error_id) 138 | endif 139 | endfunction 140 | 141 | function erlang_compiler#errors#DelLineErrors(bufnr) 142 | if !has_key(g:erlang_errors_by_lnum, a:bufnr) 143 | let g:erlang_errors_by_lnum[a:bufnr] = {} 144 | endif 145 | let buf_dict = g:erlang_errors_by_lnum[a:bufnr] 146 | for line_dict in values(buf_dict) 147 | for error_id in keys(line_dict.E) 148 | call erlang_compiler#errors#DelError(error_id) 149 | endfor 150 | for error_id in keys(line_dict.W) 151 | call erlang_compiler#errors#DelError(error_id) 152 | endfor 153 | endfor 154 | call remove(g:erlang_errors_by_lnum, a:bufnr) 155 | endfunction 156 | 157 | function erlang_compiler#errors#GetLineError(bufnr, pos) 158 | let buf_dict = get(g:erlang_errors_by_lnum, a:bufnr, {}) 159 | let line_dict = get(buf_dict, a:pos, {}) 160 | let line_errors = get(line_dict, 'E', {}) 161 | if !empty(line_errors) 162 | return copy(get(values(line_errors), 0)) 163 | else 164 | let line_warnings = get(line_dict, 'W', {}) 165 | if !empty(line_warnings) 166 | return copy(get(values(line_warnings), 0)) 167 | else 168 | return {} 169 | endif 170 | endif 171 | endfunction 172 | 173 | function erlang_compiler#errors#EchoLineError(bufnr, pos) 174 | let error = erlang_compiler#errors#GetLineError(a:bufnr, a:pos) 175 | let text = get(error, 'text', '') 176 | if !empty(text) 177 | echo text 178 | let b:erlang_echo_error = 1 179 | elseif exists('b:erlang_echo_error') && b:erlang_echo_error 180 | echo '' 181 | let b:erlang_echo_error = 0 182 | endif 183 | endfunction 184 | 185 | let &cpo = s:cpo_save 186 | unlet s:cpo_save 187 | -------------------------------------------------------------------------------- /compiler/erlang.vim: -------------------------------------------------------------------------------- 1 | " vim-erlang-compiler file 2 | " Language: Erlang 3 | " Author: Pawel 'kTT' Salata 4 | " Contributors: Ricardo Catalinas Jiménez 5 | " James Fish 6 | " License: Vim license 7 | 8 | if exists("current_compiler") || v:version < 703 9 | finish 10 | else 11 | let current_compiler = "erlang" 12 | endif 13 | 14 | let s:cpo_save = &cpo 15 | set cpo&vim 16 | 17 | if exists(":CompilerSet") != 2 18 | command -nargs=* CompilerSet setlocal 19 | endif 20 | 21 | let g:erlang_compiler_check_script = expand(":p:h") . "/erlang_check.erl" 22 | 23 | " Find the appropriate make options 24 | let s:make_options = g:erlang_make_options 25 | for s:rule in g:erlang_make_options_rules 26 | if expand('%:p') =~# get(s:rule, 'path_re', '') 27 | let s:make_options = s:rule['options'] 28 | break 29 | endif 30 | endfor 31 | 32 | execute "CompilerSet makeprg=" . 33 | \ escape(fnameescape(g:erlang_compiler_check_script) . ' ' . 34 | \ s:make_options . ' ', ' \') . '%' 35 | 36 | CompilerSet errorformat=%f:%l:%c:\ %tarning:\ %m,%f:%l:%c:\ %m,%f:%l:\ %tarning:\ %m,%f:%l:\ %m,%f:\ %m 37 | 38 | let &cpo = s:cpo_save 39 | unlet s:cpo_save 40 | -------------------------------------------------------------------------------- /compiler/erlang_check.erl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %%! -hidden 3 | 4 | %%% This script performs syntax check on the given files, and optionally 5 | %%% compiles them. 6 | %%% 7 | %%% See more information in the {@link print_help/0} function. 8 | 9 | % 'compile' mode gives better error messages if the script throws an error. 10 | -mode(compile). 11 | 12 | %%%============================================================================= 13 | %%% Types 14 | %%%============================================================================= 15 | 16 | -type build_system() :: rebar | rebar3 | makefile. 17 | 18 | %%%============================================================================= 19 | %%% Main function 20 | %%%============================================================================= 21 | 22 | %%------------------------------------------------------------------------------ 23 | %% @doc This function is the entry point of the script. 24 | %% 25 | %% Iterate over the given files, print their compilation warnings and errors, 26 | %% and exit with an appropriate exit code. 27 | %% @end 28 | %%------------------------------------------------------------------------------ 29 | -spec main(Args) -> no_return() when 30 | Args :: [string()]. 31 | main([]) -> 32 | io:format("Usage: see --help.~n"), 33 | halt(2); 34 | main(Args) -> 35 | Files = parse_args(Args), 36 | 37 | case get(outdir) of 38 | undefined -> 39 | % xref, load and copy is supported only if outdir is also specified 40 | disable(xref), 41 | disable(load), 42 | disable(copy); 43 | _ -> 44 | ok 45 | end, 46 | 47 | case [File || File <- Files, check_file(File) /= ok ] of 48 | % No Errors (but there could be Warnings!) 49 | [] -> 50 | halt(0); 51 | % At least one Error 52 | _Errors -> 53 | halt(1) 54 | end. 55 | 56 | %%%============================================================================= 57 | %%% Parse command line arguments 58 | %%%============================================================================= 59 | 60 | %%------------------------------------------------------------------------------ 61 | %% @doc Parse the argument list. 62 | %% 63 | %% Put the options into the process dictionary and return the list of files. 64 | %% @end 65 | %%------------------------------------------------------------------------------ 66 | -spec parse_args(Args) -> FileNames when 67 | Args :: [string()], 68 | FileNames :: [string()]. 69 | parse_args(Args) -> 70 | lists:reverse(parse_args(Args, [])). 71 | 72 | %%------------------------------------------------------------------------------ 73 | %% @doc Parse the argument list. 74 | %% 75 | %% Put the options into the process dictionary and return the list of files in 76 | %% reverse order. 77 | %% @end 78 | %%------------------------------------------------------------------------------ 79 | -spec parse_args(Args, FileNames) -> FileNames when 80 | Args :: [string()], 81 | FileNames :: [string()]. 82 | parse_args([], Acc) -> 83 | Acc; 84 | parse_args([Help|_], _Acc) when Help == "-h"; 85 | Help == "--help" -> 86 | print_help(), 87 | halt(0); 88 | parse_args([Verbose|OtherArgs], Acc) when Verbose == "-v"; 89 | Verbose == "--verbose" -> 90 | put(verbose, true), 91 | log("Verbose mode on.~n"), 92 | parse_args(OtherArgs, Acc); 93 | parse_args(["--outdir", OutDir|OtherArgs], Acc) -> 94 | put(outdir, OutDir), 95 | parse_args(OtherArgs, Acc); 96 | parse_args(["--outdir"], _Acc) -> 97 | log_error("Argument needed after '--outdir'.~n"), 98 | halt(2); 99 | parse_args(["--nooutdir"|OtherArgs], Acc) -> 100 | erase(outdir), 101 | parse_args(OtherArgs, Acc); 102 | parse_args(["--xref"|OtherArgs], Acc) -> 103 | put(xref, true), 104 | parse_args(OtherArgs, Acc); 105 | parse_args(["--load", LongOrShortNames|_OtherArgs], _Acc) 106 | when LongOrShortNames =/= "shortnames", 107 | LongOrShortNames =/= "longnames" -> 108 | log_error("First argument after '--load' should be shortnames or " 109 | "longnames.~n"), 110 | halt(2); 111 | parse_args(["--load", LongOrShortNames, MyNodeName, TargetNodeName|OtherArgs], 112 | Acc) -> 113 | put(load, {list_to_atom(LongOrShortNames), 114 | list_to_atom(MyNodeName), 115 | list_to_atom(TargetNodeName)}), 116 | parse_args(OtherArgs, Acc); 117 | parse_args(["--load"|_], _Acc) -> 118 | log_error("More arguments needed after '--load'.~n"), 119 | halt(2); 120 | parse_args(["--cookie", Cookie|OtherArgs], Acc) -> 121 | put(cookie, list_to_atom(Cookie)), 122 | parse_args(OtherArgs, Acc); 123 | parse_args(["--cookie"], _Acc) -> 124 | log_error("Argument needed after '--cookie'.~n"), 125 | halt(2); 126 | parse_args(["--copy", TargetDir|OtherArgs], Acc) -> 127 | put(copy, TargetDir), 128 | parse_args(OtherArgs, Acc); 129 | parse_args(["--copy"], _Acc) -> 130 | log_error("Argument needed after '--copy'.~n"), 131 | halt(2); 132 | parse_args(["--"|Files], Acc) -> 133 | Files ++ Acc; 134 | parse_args([[$-|_] = Arg|_], _Acc) -> 135 | log_error("Unknown option: ~s~n", [Arg]), 136 | halt(2); 137 | parse_args([File|OtherArgs], Acc) -> 138 | parse_args(OtherArgs, [File|Acc]). 139 | 140 | %%------------------------------------------------------------------------------ 141 | %% @doc Print the script's help text. 142 | %% @end 143 | %%------------------------------------------------------------------------------ 144 | -spec print_help() -> ok. 145 | print_help() -> 146 | Text = 147 | "Usage: erlang_check.erl [options] [--] 148 | 149 | Description: 150 | erlang_check.erl performs syntax check on the given files, and optionally 151 | (with the --outdir option) compiles them. 152 | 153 | Options: 154 | -- Process all remaining parameters as filenames. 155 | -h, --help Print help. 156 | -v, --verbose Verbose output. 157 | --outdir DIR Put the compiled beam file into the given directory. It is 158 | relative to directory containing the file to compile. 159 | --nooutdir Don't create beam file (default). 160 | --xref Execute xref on the beam file and print undefined functions. 161 | (Other xref warnings are not printed, because those should be 162 | also printed by the compiler.) Works only if --outdir is 163 | specified. 164 | --load NODE_NAME_TYPE MY_NODE_NAME TARGET_NODE_NAME 165 | After successful compilation, start a node with MY_NODE_NAME and 166 | load the module into the target node. NODE_NAME_TYPE must be 167 | either 'shortnames' or 'longnames'. Works only if --outdir is 168 | specified. 169 | --cookie COOKIE 170 | When --load is used, this option can be used to set the cookie 171 | to be used towards the TARGET_NODE_NAME. 172 | --copy DIR After successful compilation, all beam files with the same 173 | number (recursively) under DIR will be overwritten with the 174 | newly generated beam file. Works only with Erlang R16 and above. 175 | ", 176 | io:format(Text). 177 | 178 | %%%============================================================================= 179 | %%% Preparation 180 | %%%============================================================================= 181 | 182 | %%------------------------------------------------------------------------------ 183 | %% @doc Disable the given feature and print a warning if it was turned on. 184 | %% @end 185 | %%------------------------------------------------------------------------------ 186 | -spec disable(Arg) -> ok when 187 | Arg :: atom(). 188 | disable(Arg) -> 189 | case get(Arg) of 190 | undefined -> 191 | ok; 192 | _ -> 193 | erase(Arg), 194 | log_error("Warning: ~p disabled (it requires --outdir).~n", [Arg]) 195 | end. 196 | 197 | %%------------------------------------------------------------------------------ 198 | %% @doc Try to compile the given file, print the warnings and errors, and return 199 | %% whether there were errors. 200 | %% @end 201 | %%------------------------------------------------------------------------------ 202 | -spec check_file(File) -> ok | error when 203 | File :: string(). 204 | check_file(File) -> 205 | case file_type(File) of 206 | module -> 207 | check_module(File); 208 | escript -> 209 | check_escript(File); 210 | {error, Reason} -> 211 | file_error(File, {file_error, Reason}), 212 | error 213 | end. 214 | 215 | %%------------------------------------------------------------------------------ 216 | %% @doc Return the type of the Erlang source file. 217 | %% @end 218 | %%------------------------------------------------------------------------------ 219 | -spec file_type(File) -> Result when 220 | File :: string(), 221 | Result :: module | escript | {error, term()}. 222 | file_type(File) -> 223 | case file:open(File, [raw, read]) of 224 | {ok, Fd} -> 225 | Result = read_file_type(Fd), 226 | ok = file:close(Fd), 227 | Result; 228 | {error, _Reason} = Error -> 229 | Error 230 | end. 231 | 232 | %%------------------------------------------------------------------------------ 233 | %% @doc Return the type of the Erlang source file. 234 | %% @end 235 | %%------------------------------------------------------------------------------ 236 | -spec read_file_type(FileDescriptor) -> Result when 237 | FileDescriptor :: file:io_device(), 238 | Result :: module | escript | {error, term()}. 239 | read_file_type(FileDescriptor) -> 240 | case file:read(FileDescriptor, 256) of 241 | {ok, Beginning} -> 242 | case re:run(Beginning, "^#!.*escript", [{capture, none}]) of 243 | nomatch -> 244 | module; 245 | match -> 246 | escript 247 | end; 248 | {error, _Reason} = Error -> 249 | Error 250 | end. 251 | 252 | %%------------------------------------------------------------------------------ 253 | %% @doc Try to compile the given module, print the warnings and errors, and 254 | %% return whether there were errors. 255 | %% @end 256 | %%------------------------------------------------------------------------------ 257 | -spec check_module(File) -> ok | error when 258 | File :: string(). 259 | check_module(File) -> 260 | Dir = filename:dirname(File), 261 | AbsFile = filename:absname(File), 262 | AbsDir = filename:absname(Dir), 263 | put(compiled_file_path, AbsFile), 264 | 265 | {AppRoot, ProjectRoot, BuildSystemOpts} = load_build_info(AbsDir), 266 | 267 | case BuildSystemOpts of 268 | {opts, Opts} -> 269 | check_module_2(AbsFile, AbsDir, AppRoot, ProjectRoot, Opts); 270 | error -> 271 | error 272 | end. 273 | 274 | %%%============================================================================= 275 | %%% Load build information. 276 | %%% 277 | %%% This code block is also present in erlang_complete.erl in the 278 | %%% vim-erlang-omnicomplete project. If you modify this code block, please also 279 | %%% modify erlang_complete.erl. 280 | %%%============================================================================= 281 | 282 | %%------------------------------------------------------------------------------ 283 | %% @doc Load information about the build system. 284 | %% 285 | %% The `Path' parameter is the absolute path to the parent directory of the 286 | %% relevant Erlang source file. 287 | %% 288 | %% The code paths are set by the function. The include paths are returned in 289 | %% `BuildSystemOpts'. 290 | %% @end 291 | %%------------------------------------------------------------------------------ 292 | -spec load_build_info(Path) -> Result when 293 | Path :: string(), 294 | Result :: {AppRoot, ProjectRoot, BuildSystemOpts}, 295 | AppRoot :: string(), 296 | ProjectRoot :: string(), 297 | BuildSystemOpts :: {opts, [{atom(), term()}]} | 298 | error. 299 | load_build_info(Path) -> 300 | 301 | % AppRoot: the directory of the Erlang app. 302 | AppRoot = 303 | case find_app_root(Path) of 304 | no_root -> 305 | log("Could not find project root.~n"), 306 | Path; 307 | Root -> 308 | log("Found project root: ~p~n", [Root]), 309 | Root 310 | end, 311 | 312 | {BuildSystem, BuildFiles} = guess_build_system(AppRoot), 313 | 314 | % ProjectRoot: the directory of the Erlang release (if it exists; otherwise 315 | % same as AppRoot). 316 | ProjectRoot = get_project_root(BuildSystem, BuildFiles, AppRoot), 317 | BuildSystemOpts = load_build_files(BuildSystem, ProjectRoot, BuildFiles), 318 | 319 | {AppRoot, ProjectRoot, BuildSystemOpts}. 320 | 321 | %%------------------------------------------------------------------------------ 322 | %% @doc Traverse the directory structure upwards until is_app_root matches. 323 | %% @end 324 | %%------------------------------------------------------------------------------ 325 | -spec find_app_root(Path) -> Root when 326 | Path :: string(), 327 | Root :: string() | 'no_root'. 328 | find_app_root("/") -> 329 | case is_app_root("/") of 330 | true -> "/"; 331 | false -> no_root 332 | end; 333 | find_app_root(Path) -> 334 | case is_app_root(Path) of 335 | true -> Path; 336 | false -> find_app_root(filename:dirname(Path)) 337 | end. 338 | 339 | %%------------------------------------------------------------------------------ 340 | %% @doc Check directory if it is the root of an OTP application. 341 | %% @end 342 | %%------------------------------------------------------------------------------ 343 | -spec is_app_root(Path) -> boolean() when 344 | Path :: string(). 345 | is_app_root(Path) -> 346 | filelib:wildcard("ebin/*.app", Path) /= [] orelse 347 | filelib:wildcard("src/*.app.src", Path) /= []. 348 | 349 | %%------------------------------------------------------------------------------ 350 | %% @doc Check for some known files and try to guess what build system is being 351 | %% used. 352 | %% @end 353 | %%------------------------------------------------------------------------------ 354 | -spec guess_build_system(Path) -> Result when 355 | Path :: string(), 356 | Result :: {build_system(), 357 | BuildFiles :: [string()]}. 358 | guess_build_system(Path) -> 359 | % The order is important, at least Makefile needs to come last since a lot 360 | % of projects include a Makefile along any other build system. 361 | BuildSystems = [ 362 | {rebar3, [ 363 | "rebar.lock" 364 | ] 365 | }, 366 | {rebar, [ 367 | "rebar.config", 368 | "rebar.config.script" 369 | ] 370 | }, 371 | {makefile, [ 372 | "Makefile" 373 | ] 374 | } 375 | ], 376 | guess_build_system(Path, BuildSystems). 377 | 378 | %%------------------------------------------------------------------------------ 379 | %% @doc Check which build system's files are contained by the project. 380 | %% @end 381 | %%------------------------------------------------------------------------------ 382 | -spec guess_build_system(Path, BuildSystems) -> Result when 383 | BuildSystems :: [{build_system(), 384 | BaseNames :: [string()]}], 385 | Path :: string(), 386 | Result :: {build_system(), 387 | BuildFiles :: [string()]}. 388 | guess_build_system(_Path, []) -> 389 | log("Unknown build system.~n"), 390 | {unknown_build_system, []}; 391 | guess_build_system(Path, [{BuildSystem, BaseNames}|Rest]) -> 392 | log("Try build system: ~p~n", [BuildSystem]), 393 | case find_files(Path, BaseNames) of 394 | [] -> 395 | guess_build_system(Path, Rest); 396 | BuildFiles -> 397 | {BuildSystem, BuildFiles} 398 | end. 399 | 400 | %%------------------------------------------------------------------------------ 401 | %% @doc Get the root directory of the project. 402 | %% @end 403 | %%------------------------------------------------------------------------------ 404 | -spec get_project_root(BuildSystem, BuildFiles, AppRoot) -> ProjectRoot when 405 | BuildSystem :: build_system(), 406 | BuildFiles :: [string()], 407 | AppRoot :: string(), 408 | ProjectRoot :: string(). 409 | get_project_root(rebar3, BuildFiles, _) -> 410 | RebarLocks = [F || F <- BuildFiles, 411 | filename:basename(F) == "rebar.lock"], 412 | RebarLocksWithPriority = [{F, rebar3_lock_priority(F)} || F <- RebarLocks], 413 | {RebarLock, _Priority} = hd(lists:keysort(2, RebarLocksWithPriority)), 414 | filename:dirname(RebarLock); 415 | get_project_root(_BuildSystem, _Files, AppRoot) -> 416 | AppRoot. 417 | 418 | %%------------------------------------------------------------------------------ 419 | %% @doc Get the priority of a rebar3 lock file. 420 | %% 421 | %% Standalone rebar3 lock files found along the parent paths could end up making 422 | %% their directories be prioritised in our attempt to search for the true root 423 | %% of the project. 424 | %% 425 | %% This will in turn result in 'rebar.config not found in [...]' kind of errors 426 | %% being printed out when checking for syntax errors. 427 | %% 428 | %% This function attempts to minimise the risk of that happening by prioritising 429 | %% the found locks according to simple heuristics for how likely are those lock 430 | %% files to be the genuine article. 431 | %% @end 432 | %%------------------------------------------------------------------------------ 433 | -spec rebar3_lock_priority(Filename) -> Result when 434 | Filename :: string(), 435 | Result :: [non_neg_integer()]. 436 | rebar3_lock_priority(Filename) -> 437 | Dir = filename:dirname(Filename), 438 | AbsDir = filename:absname(Dir), 439 | {ok, Siblings} = file:list_dir(AbsDir), 440 | {SiblingDirs, SiblingFiles} = 441 | lists:partition(fun filelib:is_dir/1, Siblings), 442 | AbsDirComponents = filename:split(AbsDir), 443 | 444 | MightBeRebarProject = lists:member("rebar.config", SiblingFiles), 445 | MightBeSingleApp = lists:member("src", SiblingDirs), 446 | MightBeUmbrellaApp = lists:member("apps", SiblingDirs), 447 | Depth = length(AbsDirComponents), 448 | 449 | if MightBeRebarProject -> 450 | % Lock files standing beside a rebar.config file 451 | % get a higher priority than to those that don't. 452 | % Between them, those higher in file system hierarchy will 453 | % themselves get prioritised. 454 | [1, Depth]; 455 | MightBeSingleApp xor MightBeUmbrellaApp -> 456 | % Lock files standing beside either a src or apps directory 457 | % get a higher priority than those that don't. 458 | % Between them, those higher in file system hierarchy will 459 | % themselves get prioritised. 460 | [2, Depth]; 461 | true -> 462 | % No good criteria remain. Prioritise by placement in 463 | % file system hierarchy. 464 | [3, Depth] 465 | end. 466 | 467 | %%------------------------------------------------------------------------------ 468 | %% @doc Load the settings from a given set of build system files. 469 | %% @end 470 | %%------------------------------------------------------------------------------ 471 | -spec load_build_files(BuildSystem, ProjectRoot, ConfigFiles) -> Result when 472 | BuildSystem :: build_system(), 473 | ProjectRoot :: string(), 474 | ConfigFiles :: [string()], 475 | Result :: {opts, [{atom(), term()}]} | 476 | error. 477 | load_build_files(rebar, _ProjectRoot, ConfigFiles) -> 478 | load_rebar_files(ConfigFiles, no_config); 479 | load_build_files(rebar3, ProjectRoot, _ConfigFiles) -> 480 | % _ConfigFiles is a list containing only rebar.lock. 481 | ConfigNames = ["rebar.config", "rebar.config.script"], 482 | case find_files(ProjectRoot, ConfigNames) of 483 | [] -> 484 | log_error("rebar.config not found in ~p~n", [ProjectRoot]), 485 | error; 486 | [RebarConfigFile|_] -> 487 | load_rebar3_files(RebarConfigFile) 488 | end; 489 | load_build_files(makefile, _ProjectRoot, ConfigFiles) -> 490 | load_makefiles(ConfigFiles); 491 | load_build_files(unknown_build_system, ProjectRoot, _) -> 492 | {opts, [ 493 | {i, absname(ProjectRoot, "include")}, 494 | {i, absname(ProjectRoot, "../include")}, 495 | {i, ProjectRoot} 496 | ]}. 497 | 498 | %%------------------------------------------------------------------------------ 499 | %% @doc Load the content of each rebar file. 500 | %% 501 | %% Note worthy: The config returned by this function only represents the first 502 | %% rebar file (the one closest to the file to compile). The subsequent rebar 503 | %% files will be processed for code path only. 504 | %% @end 505 | %%------------------------------------------------------------------------------ 506 | -spec load_rebar_files(ConfigFiles, Config) -> Result when 507 | ConfigFiles :: [string()], 508 | Config :: no_config | [{atom(), term()}], 509 | Result :: {opts, [{atom(), term()}]} | 510 | error. 511 | load_rebar_files([], no_config) -> 512 | error; 513 | load_rebar_files([], Config) -> 514 | {opts, Config}; 515 | load_rebar_files([ConfigFile|Rest], Config) -> 516 | ConfigPath = filename:dirname(ConfigFile), 517 | ConfigResult = case filename:extension(ConfigFile) of 518 | ".script" -> file:script(ConfigFile); 519 | ".config" -> file:consult(ConfigFile) 520 | end, 521 | case ConfigResult of 522 | {ok, ConfigTerms} -> 523 | log("rebar.config read: ~s~n", [ConfigFile]), 524 | NewConfig = process_rebar_config(ConfigPath, ConfigTerms, Config), 525 | case load_rebar_files(Rest, NewConfig) of 526 | {opts, SubConfig} -> {opts, SubConfig}; 527 | error -> {opts, NewConfig} 528 | end; 529 | {error, Reason} -> 530 | log_error("rebar.config consult failed:~n"), 531 | file_error(ConfigFile, {file_error, Reason}), 532 | error 533 | end. 534 | 535 | %%------------------------------------------------------------------------------ 536 | %% @doc Apply a rebar.config file. 537 | %% 538 | %% This function adds the directories in the rebar.config file to the code path 539 | %% and returns and compilation options to be used when compiling the file. 540 | %% @end 541 | %%------------------------------------------------------------------------------ 542 | -spec process_rebar_config(Path, Terms, Config) -> Result when 543 | Path :: string(), 544 | Terms :: [{atom(), term()}], 545 | Config :: no_config | [{atom(), term()}], 546 | Result :: [{atom(), term()}]. 547 | process_rebar_config(Path, Terms, Config) -> 548 | 549 | % App layout: 550 | % 551 | % * rebar.config 552 | % * src/ 553 | % * ebin/ => ebin -> code_path 554 | % * include/ => ".." -> include. This is needed because files in src may 555 | % use `-include_lib("appname/include/f.hrl")` 556 | 557 | % Project layout: 558 | % 559 | % * rebar.config 560 | % * src/ 561 | % * $(deps_dir)/ 562 | % * $(app_name)/ 563 | % * ebin/ => deps -> code_path 564 | % * apps/ 565 | % * $(sub_dir)/ 566 | % * ebin/ => sub_dirs -> code_path 567 | % * include/ => apps -> include 568 | 569 | DepsDir = proplists:get_value(deps_dir, Terms, "deps"), 570 | LibDirs = proplists:get_value(lib_dirs, Terms, []), 571 | SubDirs = proplists:get_value(sub_dirs, Terms, []), 572 | ErlOpts = proplists:get_value(erl_opts, Terms, []), 573 | 574 | % ebin -> code_path (when the rebar.config file is in the app directory 575 | code:add_pathsa([absname(Path, "ebin")]), 576 | 577 | % deps -> code_path 578 | code:add_pathsa(filelib:wildcard(absname(Path, DepsDir) ++ "/*/ebin")), 579 | 580 | % libs -> code_path 581 | code:add_pathsa(filelib:wildcard(absname(Path, LibDirs) ++ "/*/ebin")), 582 | 583 | % sub_dirs -> code_path 584 | [ code:add_pathsa(filelib:wildcard(absname(Path, SubDir) ++ "/ebin")) 585 | || SubDir <- SubDirs ], 586 | 587 | case Config of 588 | no_config -> 589 | Includes = 590 | [ {i, absname(Path, Dir)} 591 | || Dir <- ["apps", "include"] ] ++ 592 | [ {i, absname(Path, filename:append(SubDir, "include"))} 593 | || SubDir <- SubDirs ], 594 | 595 | Opts = ErlOpts ++ Includes, 596 | remove_warnings_as_errors(Opts); 597 | _ -> 598 | Config 599 | end. 600 | 601 | %%------------------------------------------------------------------------------ 602 | %% @doc Load the content of each rebar3 file. 603 | %% 604 | %% Note worthy: The config returned by this function only represent the first 605 | %% rebar file (the one closest to the file to compile). 606 | %% @end 607 | %%------------------------------------------------------------------------------ 608 | -spec load_rebar3_files(ConfigFile) -> Result when 609 | ConfigFile :: string(), 610 | Result :: {opts, [{atom(), term()}]} | 611 | error. 612 | load_rebar3_files(ConfigFile) -> 613 | ConfigPath = filename:dirname(ConfigFile), 614 | ConfigResult = case filename:extension(ConfigFile) of 615 | ".script" -> file:script(ConfigFile); 616 | ".config" -> file:consult(ConfigFile) 617 | end, 618 | case ConfigResult of 619 | {ok, ConfigTerms} -> 620 | log("rebar.config read: ~s~n", [ConfigFile]), 621 | try process_rebar3_config(ConfigPath, ConfigTerms) of 622 | error -> 623 | error; 624 | Config -> 625 | {opts, Config} 626 | catch 627 | throw:error -> 628 | error 629 | end; 630 | {error, Reason} -> 631 | log_error("rebar.config consult failed:~n"), 632 | file_error(ConfigFile, {file_error, Reason}), 633 | error 634 | end. 635 | 636 | %%------------------------------------------------------------------------------ 637 | %% @doc Apply a rebar.config file. 638 | %% 639 | %% This function adds the directories returned by rebar3 to the code path and 640 | %% returns and compilation options to be used when compiling the file. 641 | %% @end 642 | %%------------------------------------------------------------------------------ 643 | -spec process_rebar3_config(ConfigPath, Terms) -> Result when 644 | ConfigPath :: string(), 645 | Terms :: [{atom(), term()}], 646 | Result :: [{atom(), term()}] | error. 647 | process_rebar3_config(ConfigPath, Terms) -> 648 | case find_rebar3(ConfigPath) of 649 | not_found -> 650 | % Compilation would likely fail without settings the paths, so let's 651 | % give an explicit error instead of proceeding anyway. 652 | log_error("rebar3 executable not found.~n"), 653 | error; 654 | {ok, Rebar3Rel} -> 655 | log("rebar3 executable found: ~s~n", [Rebar3Rel]), 656 | Rebar3 = filename:absname(Rebar3Rel), 657 | log("Absolute path to rebar3 executable: ~s~n", [Rebar3]), 658 | % load the profile used by rebar3 to print the dependency path list 659 | Profile = rebar3_get_profile(Terms), 660 | % "rebar3 path" prints all paths that belong to the project; we add 661 | % these to the Erlang paths. 662 | % 663 | % QUIET=1 ensures that it won't print other messages, see 664 | % https://github.com/erlang/rebar3/issues/1143. 665 | {ok, Cwd} = file:get_cwd(), 666 | file:set_cwd(ConfigPath), 667 | os:putenv("QUIET", "1"), 668 | MainCmd = io_lib:format("~p as ~p path", [Rebar3, Profile]), 669 | log("Call: ~s~n", [MainCmd]), 670 | {ExitCode, Output} = command(MainCmd, []), 671 | log("Result: ~p~n", [{ExitCode, Output}]), 672 | file:set_cwd(Cwd), 673 | 674 | Paths = 675 | case ExitCode of 676 | 0 -> 677 | Output; 678 | _ -> 679 | file_error( 680 | get(compiled_file_path), 681 | {format, 682 | "'~s' failed with exit code ~p: ~s~n", 683 | [MainCmd, ExitCode, Output]}), 684 | throw(error) 685 | end, 686 | 687 | CleanedPaths = [absname(ConfigPath, SubDir) 688 | || SubDir <- string:tokens(Paths, " ")], 689 | code:add_pathsa(CleanedPaths), 690 | 691 | % Add _checkouts dependencies to code_path. 692 | % 693 | % These dependencies are compiled into the following directories: 694 | % 695 | % - `_checkouts//ebin' until rebar 3.13 696 | % - `_build//checkouts//ebin/' from rebar 3.14 697 | % 698 | % Documentation for _checkouts dependencies: 699 | % https://www.rebar3.org/docs/dependencies#section-checkout-dependencies 700 | code:add_pathsa( 701 | filelib:wildcard(absname(ConfigPath, "_checkouts") ++ 702 | "/*/ebin")), 703 | code:add_pathsa( 704 | filelib:wildcard(absname(ConfigPath, "_build") ++ 705 | "/default/checkouts/*/ebin")), 706 | 707 | lists:foreach( 708 | fun({ProfileName, Deps}) -> 709 | Apps = string:join([atom_to_list(D) || D <- Deps], ","), 710 | file:set_cwd(ConfigPath), 711 | Cmd2 = io_lib:format("QUIET=1 ~p as ~p path --app=~s", 712 | [Rebar3, ProfileName, Apps]), 713 | log("Call: ~s~n", [Cmd2]), 714 | ProfilePaths = os:cmd(Cmd2), 715 | log("Result: ~s~n", [Paths]), 716 | file:set_cwd(Cwd), 717 | Cleaned = [absname(ConfigPath, SubDir) 718 | || SubDir <- string:tokens(ProfilePaths, " ")], 719 | code:add_pathsa(Cleaned); 720 | (_) -> ok 721 | end, rebar3_get_extra_profiles(Terms)), 722 | 723 | ErlOpts = proplists:get_value(erl_opts, Terms, []), 724 | remove_warnings_as_errors(ErlOpts) 725 | end. 726 | 727 | %%------------------------------------------------------------------------------ 728 | %% @doc Find the rebar3 executable. 729 | %% 730 | %% First we try to find rebar3 in the project directory. Second we try to find 731 | %% it in the PATH. 732 | %% @end 733 | %%------------------------------------------------------------------------------ 734 | -spec find_rebar3(ConfigPath) -> Result when 735 | ConfigPath :: [string()], 736 | Result :: {ok, string()} | 737 | not_found. 738 | find_rebar3(ConfigPath) -> 739 | case find_files(ConfigPath, ["rebar3"]) of 740 | [Rebar3|_] -> 741 | {ok, Rebar3}; 742 | [] -> 743 | case os:find_executable("rebar3") of 744 | false -> 745 | not_found; 746 | Rebar3 -> 747 | {ok, Rebar3} 748 | end 749 | end. 750 | 751 | %%------------------------------------------------------------------------------ 752 | %% @doc Read the profile name defined in rebar.config for Rebar3 753 | %% 754 | %% Look inside rebar.config to find a special configuration called 755 | %% `vim_erlang_compiler`. 756 | %% 757 | %% E.g. to use the "test" profile: 758 | %% 759 | %% ``` 760 | %% {vim_erlang_compiler, [ 761 | %% {profile, "test"} 762 | %% ]}. 763 | %% ''' 764 | %%------------------------------------------------------------------------------ 765 | -spec rebar3_get_profile(Terms) -> Result when 766 | Terms :: [{atom(), term()}], 767 | Result :: string(). 768 | rebar3_get_profile(Terms) -> 769 | case proplists:get_value(vim_erlang_compiler, Terms) of 770 | undefined -> "default"; 771 | Options -> proplists:get_value(profile, Options, "default") 772 | end. 773 | 774 | %%------------------------------------------------------------------------------ 775 | %% @doc Read all extra profile names declared within the rebar.config 776 | %% 777 | %%------------------------------------------------------------------------------ 778 | -spec rebar3_get_extra_profiles(Terms) -> Result when 779 | Terms :: [{atom(), term()}], 780 | Result :: [{ProfileName :: string(), 781 | [Dependency :: term()]}]. 782 | rebar3_get_extra_profiles(Terms) -> 783 | case proplists:get_value(profiles, Terms, []) of 784 | [] -> 785 | []; 786 | Profiles -> 787 | lists:flatmap( 788 | fun({ProfileName, Profile}) -> 789 | case proplists:get_value(deps, Profile, []) of 790 | [] -> 791 | []; 792 | Deps -> 793 | [{ProfileName, [Dep || {Dep, _} <- Deps]}] 794 | end; 795 | (_) -> 796 | [] 797 | end, Profiles) 798 | end. 799 | 800 | %%------------------------------------------------------------------------------ 801 | %% @doc Remove the "warnings_as_errors" option from the given Erlang options. 802 | %% 803 | %% If "warnings_as_errors" is left in, rebar sometimes prints the following 804 | %% line: 805 | %% 806 | %% compile: warnings being treated as errors 807 | %% 808 | %% The problem is that Vim interprets this as a line about an actual warning 809 | %% about a file called "compile", so it will jump to the "compile" file. 810 | %% 811 | %% And anyway, it is fine to show warnings as warnings not not errors: the 812 | %% developer knows whether their project handles warnings as errors and can 813 | %% interpret them accordingly. 814 | %% @end 815 | %%------------------------------------------------------------------------------ 816 | -spec remove_warnings_as_errors(ErlOpts) -> ErlOpts when 817 | ErlOpts :: [{atom(), string()}]. 818 | remove_warnings_as_errors(ErlOpts) -> 819 | proplists:delete(warnings_as_errors, ErlOpts). 820 | 821 | %%------------------------------------------------------------------------------ 822 | %% @doc Set code paths and options for a simple Makefile 823 | %% @end 824 | %%------------------------------------------------------------------------------ 825 | -spec load_makefiles(BuildFiles) -> Result when 826 | BuildFiles :: [string()], 827 | Result :: {opts, [{atom(), term()}]} | 828 | error. 829 | load_makefiles([Makefile|_Rest]) -> 830 | Path = filename:dirname(Makefile), 831 | code:add_pathsa([absname(Path, "ebin")]), 832 | code:add_pathsa(filelib:wildcard(absname(Path, "deps") ++ "/*/ebin")), 833 | code:add_pathsa(filelib:wildcard(absname(Path, "lib") ++ "/*/ebin")), 834 | {opts, [{i, absname(Path, "include")}, 835 | {i, absname(Path, "deps")}, 836 | {i, absname(Path, "lib")}]}. 837 | 838 | %%%============================================================================= 839 | %%% Execution 840 | %%%============================================================================= 841 | 842 | -spec check_module_2(AbsFile, AbsDir, AppRoot, ProjectRoot, 843 | Opts) -> Result when 844 | AbsFile :: string(), 845 | AbsDir :: string(), 846 | AppRoot :: string(), 847 | ProjectRoot :: string(), 848 | Opts :: [{atom(), term()}], 849 | Result :: ok | error. 850 | check_module_2(AbsFile, _AbsDir, AppRoot, ProjectRoot, Opts) -> 851 | 852 | Defs = [warn_export_all, 853 | warn_export_vars, 854 | warn_shadow_vars, 855 | warn_obsolete_guard, 856 | warn_unused_import, 857 | report, 858 | 859 | % Don't print part of the source code. 860 | % 861 | % By default, the compile:file/2 function in OTP 24+ prints not only 862 | % the error message but also a part of the source code by default. 863 | % E.g.: 864 | % 865 | % /path/to/x.erl:19:7: syntax error before: MyVar 866 | % || % 19| x MyVar 867 | % || % | ^ 868 | % || 869 | % 870 | % The `brief' option disabled this. 871 | brief, 872 | 873 | % By adding debug_info, we ensure that the output of xref:m will 874 | % contain the caller MFAs too. 875 | debug_info], 876 | 877 | {ExtOpts, OutDir} = case get(outdir) of 878 | undefined -> 879 | {[strong_validation], undefined}; 880 | OutDir0 -> 881 | AbsOutDir = filename:join(ProjectRoot, OutDir0), 882 | {[{outdir, AbsOutDir}], AbsOutDir} 883 | end, 884 | 885 | CompileOpts = 886 | Defs ++ Opts ++ ExtOpts ++ 887 | [ 888 | 889 | % rebar3/rebar_compiler_erl.erl adds /src, /include and 890 | % to the include path: 891 | % 892 | % PrivIncludes = [{i, filename:join(OutDir, Src)} 893 | % || Src <- rebar_dir:all_src_dirs( 894 | % RebarOpts, ["src"], [])], 895 | % AdditionalOpts = 896 | % PrivIncludes ++ 897 | % [{i, filename:join(OutDir, "include")}, {i, OutDir}, return] 898 | % 899 | % So we do the same. 900 | 901 | {i, filename:join([AppRoot, "src"])}, 902 | {i, filename:join([AppRoot, "include"])}, 903 | {i, filename:join([AppRoot])} 904 | 905 | ], 906 | log("Code paths: ~p~n", [code:get_path()]), 907 | log("Compiling: compile:file(~p,~n ~p)~n", 908 | [AbsFile, CompileOpts]), 909 | case compile:file(AbsFile, CompileOpts) of 910 | {ok, ModName} -> 911 | post_compilation(OutDir, ModName); 912 | error -> 913 | error 914 | end. 915 | 916 | %%------------------------------------------------------------------------------ 917 | %% @doc Perform tasks after successful compilation (xref, etc.) 918 | %% @end 919 | %%------------------------------------------------------------------------------ 920 | -spec post_compilation(AbsOutDir, ModName) -> ok when 921 | AbsOutDir :: string() | undefined, 922 | ModName :: module(). 923 | post_compilation(undefined, _ModName) -> 924 | ok; 925 | post_compilation(AbsOutDir, ModName) -> 926 | BeamFileRoot = filename:join(AbsOutDir, atom_to_list(ModName)), 927 | maybe_run_xref(AbsOutDir, BeamFileRoot), 928 | code:add_patha(AbsOutDir), 929 | maybe_load(ModName), 930 | maybe_copy(BeamFileRoot, ModName), 931 | ok. 932 | 933 | %%------------------------------------------------------------------------------ 934 | %% @doc Run xref on the given module and prints the warnings if the xref option 935 | %% is specified. 936 | %% @end 937 | %%------------------------------------------------------------------------------ 938 | -spec maybe_run_xref(AbsOutDir, BeamFileRoot) -> ok when 939 | AbsOutDir :: string(), 940 | BeamFileRoot :: string(). 941 | maybe_run_xref(AbsOutDir, BeamFileRoot) -> 942 | case get(xref) of 943 | true -> 944 | XRefWarnings = xref:m(BeamFileRoot), 945 | 946 | %% We add this directory to the code path because so that 947 | %% print_xref_warnings can find the beam file. It would not be good 948 | %% to add it before, because this directory might be e.g. /var/tmp 949 | %% so it could contain a bunch of outdates beam files, derailing 950 | %% xref. 951 | code:add_patha(AbsOutDir), 952 | 953 | print_xref_warnings(XRefWarnings); 954 | _ -> 955 | ok 956 | end. 957 | 958 | %%------------------------------------------------------------------------------ 959 | %% @doc Print the warnings returned by xref to the standard output. 960 | %% @end 961 | %%------------------------------------------------------------------------------ 962 | -spec print_xref_warnings(XRefWarnings) -> ok when 963 | XRefWarnings :: [{deprecated, [{mfa(), mfa()}]} | 964 | {undefined, [{mfa(), mfa()}]} | 965 | {unused, [mfa()]}]. 966 | print_xref_warnings(XRefWarnings) -> 967 | {undefined, UndefFuns} = lists:keyfind(undefined, 1, XRefWarnings), 968 | [begin 969 | {CallerFile, CallerLine} = find_mfa_source(Caller), 970 | io:format("~s:~p: Warning: Calling undefined function ~p:~p/~p~n", 971 | [CallerFile, CallerLine, M, F, A]) 972 | end || {Caller, {M, F, A}} <- lists:reverse(UndefFuns)], 973 | ok. 974 | 975 | %%------------------------------------------------------------------------------ 976 | %% @doc Given a MFA, find the file and LOC where it's defined. 977 | %% 978 | %% Note that xref doesn't work if there is no abstract_code, so we can avoid 979 | %% being too paranoid here. 980 | %% 981 | %% This function was copied from rebar's source code: 982 | %% https://github.com/basho/rebar/blob/117c0f7e698f735acfa73b116f9e38c5c54036dc/src/rebar_xref.erl 983 | %% 984 | %% @end 985 | %%------------------------------------------------------------------------------ 986 | -spec find_mfa_source(MFA) -> Result when 987 | MFA :: mfa(), 988 | Result :: {FileName :: string(), 989 | LineNumber :: integer()}. 990 | find_mfa_source({M, F, A}) -> 991 | {M, Bin, _} = code:get_object_code(M), 992 | AbstractCode = beam_lib:chunks(Bin, [abstract_code]), 993 | {ok, {M, [{abstract_code, {raw_abstract_v1, Code}}]}} = AbstractCode, 994 | 995 | %% Extract the original source filename from the abstract code 996 | [{attribute, 1, file, {Source, _}} | _] = Code, 997 | 998 | %% Extract the line number for a given function def 999 | Fn = [E || E <- Code, 1000 | safe_element(1, E) == function, 1001 | safe_element(3, E) == F, 1002 | safe_element(4, E) == A], 1003 | 1004 | case Fn of 1005 | [{function, Line, F, _, _}] -> 1006 | {Source, Line}; 1007 | [] -> 1008 | %% Do not crash if functions are exported, even though they are not 1009 | %% in the source. Parameterized modules add new/1 and instance/1 for 1010 | %% example. 1011 | {Source, 1} 1012 | end. 1013 | 1014 | %%------------------------------------------------------------------------------ 1015 | %% @doc Load the given module if the `--load' option was specified. 1016 | %% @end 1017 | %%------------------------------------------------------------------------------ 1018 | -spec maybe_load(ModName) -> ok | error when 1019 | ModName :: module(). 1020 | maybe_load(ModName) -> 1021 | case get(load) of 1022 | {LongOrShortNames, MyNodeName, TargetNodeName} -> 1023 | Cookie = get(cookie), 1024 | load(LongOrShortNames, MyNodeName, TargetNodeName, Cookie, ModName); 1025 | _ -> 1026 | ok 1027 | end. 1028 | 1029 | %%------------------------------------------------------------------------------ 1030 | %% @doc Load the given module into the given node. 1031 | %% @end 1032 | %%------------------------------------------------------------------------------ 1033 | -spec load(LongOrShortNames, MyNodeName, TargetNodeName, Cookie, 1034 | ModName) -> Result when 1035 | LongOrShortNames :: shortnames | longnames, 1036 | MyNodeName :: node(), 1037 | TargetNodeName :: node(), 1038 | Cookie :: atom(), 1039 | ModName :: module(), 1040 | Result :: ok | error. 1041 | load(LongOrShortNames, MyNodeName, TargetNodeName, Cookie, ModName) -> 1042 | case code:get_object_code(ModName) of 1043 | {ModName, BinaryMod, FileName} -> 1044 | net_kernel:start([MyNodeName, LongOrShortNames]), 1045 | case Cookie of 1046 | undefined -> 1047 | ok; 1048 | Cookie -> 1049 | erlang:set_cookie(TargetNodeName, Cookie) 1050 | end, 1051 | load_with_rpc(TargetNodeName, ModName, FileName, BinaryMod); 1052 | error -> 1053 | log_error("Failed to find object code for module ~p.~n", [ModName]), 1054 | error 1055 | end. 1056 | 1057 | %%------------------------------------------------------------------------------ 1058 | %% @doc Load the given binary module into the given node. 1059 | %% @end 1060 | %%------------------------------------------------------------------------------ 1061 | -spec load_with_rpc(Node, ModName, FileName, BinaryMod) -> Result when 1062 | Node :: node(), 1063 | ModName :: atom(), 1064 | FileName :: string(), 1065 | BinaryMod :: binary(), 1066 | Result :: ok | error. 1067 | load_with_rpc(Node, ModName, FileName, BinaryMod) -> 1068 | case rpc(Node, code, purge, [ModName]) of 1069 | {ok, _} -> 1070 | case rpc(Node, code, load_binary, [ModName, FileName, BinaryMod]) of 1071 | {ok, {module, ModName}} -> 1072 | log("ModName ~p is reloaded~n", [ModName]), 1073 | ok; 1074 | {ok, {error, Reason}} -> 1075 | log_error("Failed to load the module into node ~p: ~p~n", 1076 | [Node, Reason]), 1077 | error; 1078 | {error, Reason} -> 1079 | log_error("RPC towards node ~p failed: ~p~n", 1080 | [Node, Reason]), 1081 | error 1082 | end; 1083 | {error, Reason} -> 1084 | log_error("RPC towards node ~p failed: ~p~n", [Node, Reason]), 1085 | error 1086 | end. 1087 | 1088 | %%------------------------------------------------------------------------------ 1089 | %% @doc Copy the given module to module files with the same name if the --copy 1090 | %% option was specified. 1091 | %% @end 1092 | %%------------------------------------------------------------------------------ 1093 | -spec maybe_copy(BeamFileRoot, ModName) -> Result when 1094 | BeamFileRoot :: string(), 1095 | ModName :: module(), 1096 | Result :: ok | error. 1097 | maybe_copy(BeamFileRoot, ModName) -> 1098 | case get(copy) of 1099 | TargetDir when is_list(TargetDir) -> 1100 | copy(BeamFileRoot, ModName, TargetDir); 1101 | _ -> 1102 | ok 1103 | end. 1104 | 1105 | %%------------------------------------------------------------------------------ 1106 | %% @doc Copy the given module to module files with the same name in the target 1107 | %% directory. 1108 | %% @end 1109 | %%------------------------------------------------------------------------------ 1110 | -spec copy(BeamFileRoot, ModName, TargetDir) -> Result when 1111 | BeamFileRoot :: string(), 1112 | ModName :: module(), 1113 | TargetDir :: string(), 1114 | Result :: ok. 1115 | copy(BeamFileRoot, ModName, TargetDir) -> 1116 | BeamFileBase = atom_to_list(ModName) ++ ".beam", 1117 | SourceBeamFile = BeamFileRoot ++ ".beam", 1118 | TargetBeamFiles = filelib:wildcard( 1119 | filename:join([TargetDir, "**", BeamFileBase])), 1120 | [ case file:copy(SourceBeamFile, TargetBeamFile) of 1121 | {ok, _} -> 1122 | ok; 1123 | Error -> 1124 | log_error("Error when copying: ~p -> ~p: ~p~n", 1125 | [SourceBeamFile, TargetBeamFile, Error]) 1126 | end || TargetBeamFile <- TargetBeamFiles ], 1127 | ok. 1128 | 1129 | %%------------------------------------------------------------------------------ 1130 | %% @doc Try to compile the given escript, print the warnings and errors, and 1131 | %% return whether there were errors. 1132 | %% @end 1133 | %%------------------------------------------------------------------------------ 1134 | -spec check_escript(File) -> ok | error when 1135 | File :: string(). 1136 | check_escript(File) -> 1137 | case command("escript -s " ++ File, [{print_output, true}]) of 1138 | 0 -> 1139 | ok; 1140 | _Other -> 1141 | error 1142 | end. 1143 | 1144 | %%%============================================================================= 1145 | %%% Utility functions (in alphabetical order) 1146 | %%%============================================================================= 1147 | 1148 | %%------------------------------------------------------------------------------ 1149 | %% @doc Return the absolute name of the file which is in the given directory. 1150 | %% 1151 | %% Example: 1152 | %% 1153 | %% - cwd = "/home/my" 1154 | %% - Dir = "projects/erlang" 1155 | %% - Filename = "rebar.config" 1156 | %% - Result: "/home/my/projects/erlang/rebar.config" 1157 | %% @end 1158 | %%------------------------------------------------------------------------------ 1159 | -spec absname(Dir, Filename) -> Result when 1160 | Dir :: string(), 1161 | Filename :: string(), 1162 | Result :: string(). 1163 | absname(Dir, Filename) -> 1164 | filename:absname(filename:join(Dir, Filename)). 1165 | 1166 | %%------------------------------------------------------------------------------ 1167 | %% @doc Execute the given OS command. 1168 | %% 1169 | %% The command's output is printed, and its exit code is returned. 1170 | %% 1171 | %% Original code from 1172 | %% http://erlang.org/pipermail/erlang-questions/2007-February/025210.html 1173 | %% @end 1174 | %%------------------------------------------------------------------------------ 1175 | -spec command(Cmd, Options) -> Result when 1176 | Cmd :: string(), 1177 | Options :: [{print_output, boolean()}], 1178 | ExitCode :: integer(), 1179 | Output :: string(), 1180 | Result :: {ExitCode, Output}. 1181 | command(Cmd, Options) -> 1182 | PortOptions = [stream, exit_status, use_stdio, stderr_to_stdout, in, eof], 1183 | Port = open_port({spawn, Cmd}, PortOptions), 1184 | command_loop(Port, Options, []). 1185 | 1186 | %%------------------------------------------------------------------------------ 1187 | %% @doc Read the output of an OS command started via a port. 1188 | %% @end 1189 | %%------------------------------------------------------------------------------ 1190 | -spec command_loop(Port, Options, OutputAcc) -> Result when 1191 | Port :: port(), 1192 | Options :: [{print_output, boolean()}], 1193 | OutputAcc :: [string()], 1194 | ExitCode :: integer(), 1195 | Output :: string(), 1196 | Result :: {ExitCode, Output}. 1197 | command_loop(Port, Options, OutputAcc) -> 1198 | receive 1199 | {Port, {data, Data}} -> 1200 | case proplists:get_value(print_output, Options, false) of 1201 | true -> 1202 | io:format(user, "~s", [Data]); 1203 | false -> 1204 | ok 1205 | end, 1206 | command_loop(Port, Options, [Data|OutputAcc]); 1207 | {Port, eof} -> 1208 | port_close(Port), 1209 | receive 1210 | {Port, {exit_status, ExitCode}} -> 1211 | Output = lists:append(lists:reverse(OutputAcc)), 1212 | {ExitCode, Output} 1213 | end 1214 | end. 1215 | 1216 | %%------------------------------------------------------------------------------ 1217 | %% @doc Print the given error reason in a Vim-friendly and human-friendly way. 1218 | %% @end 1219 | %%------------------------------------------------------------------------------ 1220 | -spec file_error(File, Error) -> ok when 1221 | File :: string(), 1222 | Error :: {format, Format, Data} | 1223 | {file_error, FileError}, 1224 | Format :: io:format(), 1225 | Data :: [term()], 1226 | FileError :: file:posix() | badarg | terminated | system_limit | 1227 | {Line :: integer(), Mod :: module(), Term :: term()}. 1228 | file_error(File, Error) -> 1229 | 1230 | LineNumber = 1231 | case Error of 1232 | {file_error, {LineNumber0, _, _}} -> 1233 | LineNumber0; 1234 | _ -> 1235 | % The error doesn't belong to a specific line, but Vim shows it 1236 | % only if it has a line number, so let's assign line 1 to it. 1237 | 1 1238 | end, 1239 | 1240 | FileRel = relatizive_path_maybe(File), 1241 | io:format(standard_io, "~s:~p: ", [FileRel, LineNumber]), 1242 | 1243 | case Error of 1244 | {file_error, FileError} -> 1245 | io:format(standard_io, "~s~n", [file:format_error(FileError)]); 1246 | {format, Format, Data} -> 1247 | io:format(standard_io, Format, Data) 1248 | end. 1249 | 1250 | %%------------------------------------------------------------------------------ 1251 | %% @doc Find the first file matching one of the filenames in the given path. 1252 | %% @end 1253 | %%------------------------------------------------------------------------------ 1254 | -spec find_file(Path, Files) -> Result when 1255 | Path :: string(), 1256 | Files :: [string()], 1257 | Result :: [string()]. 1258 | find_file(_Path, []) -> 1259 | []; 1260 | find_file(Path, [File|Rest]) -> 1261 | AbsFile = absname(Path, File), 1262 | case filelib:is_regular(AbsFile) of 1263 | true -> 1264 | log("Found build file: [~p] ~p~n", [Path, AbsFile]), 1265 | % Return file and continue searching in parent directory. 1266 | [AbsFile]; 1267 | false -> 1268 | find_file(Path, Rest) 1269 | end. 1270 | 1271 | %%------------------------------------------------------------------------------ 1272 | %% @doc Recursively search upward through the path tree and returns the absolute 1273 | %% path to all files matching the given filenames. 1274 | %% @end 1275 | %%------------------------------------------------------------------------------ 1276 | -spec find_files(Path, Files) -> Result when 1277 | Path :: string(), 1278 | Files :: [string()], 1279 | Result :: [string()]. 1280 | find_files("/", Files) -> 1281 | find_file("/", Files); 1282 | find_files([_|":/"] = Path, Files) -> 1283 | % E.g. "C:/". This happens on Windows. 1284 | find_file(Path, Files); 1285 | find_files(Path, Files) -> 1286 | ParentPath = filename:dirname(Path), 1287 | find_file(Path, Files) ++ 1288 | find_files(ParentPath, Files). 1289 | 1290 | %%------------------------------------------------------------------------------ 1291 | %% @doc Log the given entry if we are in verbose mode. 1292 | %% @end 1293 | %%------------------------------------------------------------------------------ 1294 | -spec log(Format) -> ok when 1295 | Format :: io:format(). 1296 | log(Format) -> 1297 | log(Format, []). 1298 | 1299 | %%------------------------------------------------------------------------------ 1300 | %% @doc Log the given entry if we are in verbose mode. 1301 | %% @end 1302 | %%------------------------------------------------------------------------------ 1303 | -spec log(Format, Data) -> ok when 1304 | Format :: io:format(), 1305 | Data :: [term()]. 1306 | log(Format, Data) -> 1307 | case get(verbose) of 1308 | true -> 1309 | io:format(Format, Data); 1310 | _ -> 1311 | ok 1312 | end. 1313 | 1314 | %%------------------------------------------------------------------------------ 1315 | %% @doc Log the given error. 1316 | %% @end 1317 | %%------------------------------------------------------------------------------ 1318 | -spec log_error(Format) -> ok when 1319 | Format :: io:format(). 1320 | log_error(Format) -> 1321 | io:format(standard_error, Format, []). 1322 | 1323 | %%------------------------------------------------------------------------------ 1324 | %% @doc Log the given error. 1325 | %% @end 1326 | %%------------------------------------------------------------------------------ 1327 | -spec log_error(Format, Data) -> ok when 1328 | Format :: io:format(), 1329 | Data :: [term()]. 1330 | log_error(Format, Data) -> 1331 | io:format(standard_error, Format, Data). 1332 | 1333 | %%------------------------------------------------------------------------------ 1334 | %% @doc Try to convert a path to a relative path. 1335 | %% @end 1336 | %%------------------------------------------------------------------------------ 1337 | -spec relatizive_path_maybe(Path) -> Path when 1338 | Path :: file:filename(). 1339 | relatizive_path_maybe(Path) -> 1340 | {ok, Cwd} = file:get_cwd(), 1341 | 1342 | case lists:prefix(Cwd, Path) of 1343 | true -> 1344 | % Example: 1345 | % Cwd = "/home/my" 1346 | % Path = "/home/my/dir/my_file.erl" 1347 | % ^ length(Cwd) 1348 | % ^ Start 1349 | % <-------------> Len 1350 | % FileRel = "dir/my_file.erl" 1351 | Start = length(Cwd) + 2, 1352 | Len = length(Path) - length(Cwd) - 1, 1353 | lists:sublist(Path, Start, Len); 1354 | false -> 1355 | % The path is not below the current directory, so let's keep it 1356 | % absolute. 1357 | Path 1358 | end. 1359 | 1360 | %%------------------------------------------------------------------------------ 1361 | %% @doc Perform a remote call towards the given node. 1362 | %% @end 1363 | %%------------------------------------------------------------------------------ 1364 | -spec rpc(Node, M, F, A) -> Result when 1365 | Node :: node(), 1366 | M :: module(), 1367 | F :: atom(), 1368 | A :: integer(), 1369 | Result :: {ok, term()} | 1370 | {error, Reason :: {badrpc, term()}}. 1371 | rpc(Node, M, F, A) -> 1372 | case rpc:call(Node, M, F, A) of 1373 | {badrpc, _Reason} = Error -> 1374 | {error, Error}; 1375 | Other -> 1376 | {ok, Other} 1377 | end. 1378 | 1379 | %%------------------------------------------------------------------------------ 1380 | %% @doc Extract an element from a tuple, or undefined if N > tuple size. 1381 | %% 1382 | %% This function was copied from rebar's source code: 1383 | %% https://github.com/basho/rebar/blob/117c0f7e698f735acfa73b116f9e38c5c54036dc/src/rebar_xref.erl 1384 | %% 1385 | %% @end 1386 | %%------------------------------------------------------------------------------ 1387 | -spec safe_element(N, Tuple) -> term() when 1388 | N :: number(), 1389 | Tuple :: tuple(). 1390 | safe_element(N, Tuple) -> 1391 | case catch(element(N, Tuple)) of 1392 | {'EXIT', {badarg, _}} -> 1393 | undefined; 1394 | Value -> 1395 | Value 1396 | end. 1397 | -------------------------------------------------------------------------------- /doc/vim-erlang-compiler.txt: -------------------------------------------------------------------------------- 1 | *vim-erlang-compiler.txt* Erlang compiler and flymake 2 | 3 | CONTENTS *vim-erlang-compiler* 4 | 5 | 1. Introduction........................|vim-erlang-compiler-intro| 6 | 2. Commands............................|vim-erlang-compiler-commands| 7 | 3. Configuration.......................|vim-erlang-compiler-config| 8 | 3.1 Configuration Rebar3.............|vim-erlang-compiler-config-rebar3| 9 | 4. Credits.............................|vim-erlang-compiler-credits| 10 | 5. Contributing........................|vim-erlang-compiler-contributing| 11 | 12 | ============================================================================== 13 | INTRODUCTION *vim-erlang-compiler-intro* 14 | 15 | vim-erlang-compiler is an Erlang compiler and flymake. 16 | 17 | The plugin's behaviour is the following by default: 18 | 19 | 1. Each time an Erlang source file is saved, it is checked with the Erlang 20 | compiler, and errors and warnings are shown using |signs|. No beam file is 21 | generated. 22 | 23 | 2. When |:make| is used on an Erlang module, in case of successful compilation 24 | the beam file is generated next to the Erlang module. 25 | 26 | If you use rebar, the plugin will find the rebar.config file corresponding to 27 | the module being compiled, and uses the compiler options and directories in 28 | rebar.config. The plugin does not support rebar.script files though. 29 | 30 | The plugin has a basic support for |rebar3|. It is aware of rebar3 when a 31 | rebar.lock is found inside the project. In the moment of syntax check, the 32 | plugin discovers all the dependencies invoking the "rebar3 as default path" 33 | command in background. The rebar executable is looked up inside the root 34 | directory of the project and, if it's not found, through the current execution 35 | path. 36 | 37 | ============================================================================== 38 | COMMANDS *vim-erlang-compiler-commands* 39 | 40 | *:ErlangDisableShowErrors* 41 | :ErlangDisableShowErrors 42 | Disable syntax check when saving .erl files. 43 | 44 | The action performed as syntax check can be configured with the 45 | |g:erlang_flymake_options| and |g:erlang_flymake_options_rules| 46 | variables. 47 | 48 | *:ErlangEnableShowErrors* 49 | :ErlangEnableShowErrors 50 | Enable syntax check when saving .erl files. 51 | 52 | *:ErlangToggleShowErrors* 53 | :ErlangToggleShowErrors 54 | Toggle between the two modes above. 55 | 56 | ============================================================================== 57 | CONFIGURATION *vim-erlang-compiler-config* 58 | 59 | CONFIGURATION OPTIONS *vim-erlang-configuration-options* 60 | 61 | The vim-erlang-compiler plugin's behaviour can be configured by setting the 62 | global variables below from the |vimrc| file and either restarting Vim. (Just 63 | modifying a variable without restarting Vim is often not enough, but the 64 | buffers with Erlang files will store information calculated from these 65 | options. If you don't want to restart Vim, you can also unload these buffers 66 | with |:bdelete|.) 67 | 68 | g:erlang_show_errors *g:erlang_show_errors* 69 | Determines whether the automatic syntax check on writing .erl files 70 | should be enabled or disabled when Vim starts. If `1`, it is enabled, 71 | if `0`, it is disabled. The default value is `1.` 72 | 73 | g:erlang_make_options *g:erlang_make_options* 74 | Options to be used when using |:make| to compile a module. The default 75 | value is "--outdir .". See all options at 76 | |vim-erlang-compilation-options|. 77 | 78 | The following setting makes xref to be executed when doing |:make|: > 79 | 80 | let g:erlang_make_options = '--outdir . --xref' 81 | 82 | < Loading and reloading: see |g:erlang_make_options_rules|. 83 | 84 | g:erlang_make_options_rules *g:erlang_make_options_rules* 85 | Rules to determine which options to use when using |:make| to compile 86 | a module. The default value is []. For example: > 87 | 88 | let g:erlang_make_options_rules = 89 | \ [{'path_re': 'project_a', 'options': '--outdir . --xref'}, 90 | \ {'path_re': 'project_b', 'options': '--outdir ../ebin'}] 91 | 92 | < Loading: When an .erl file is opened, the first rule whose "path_re" 93 | regexp matches the full path of the Erlang file is used to set the 94 | compilation options. If no rule matches, |g:erlang_make_options| is 95 | used. 96 | 97 | Reloading: If you change the value of |g:erlang_make_options_rules| or 98 | |g:erlang_make_options|, it will not automatically take effect in the 99 | already opened Erlang buffers. Type the following command to refresh 100 | the compiler settings of an existing Erlang buffer: > 101 | 102 | :set compiler=erlang 103 | 104 | < Example: The following example is a convenient setting for a typical 105 | project that uses rebar. It will load the compiled module into the 106 | running node and replace the old versions of the module in the release 107 | directory: > 108 | 109 | let g:erlang_make_options_rules = 110 | \ [{'path_re': '/path/to/myproj', 111 | \ 'options': 112 | \ ' --outdir ../ebin --xref' . 113 | \ ' --load longnames vimerlang@127.0.0.1 myproj@127.0.0.1' . 114 | \ ' --copy /path/to/myproj/rel'}] 115 | 116 | < The above example uses |g:erlang_make_options_rules| instead of 117 | |g:erlang_make_options| in order to avoid affecting .erl files outside 118 | of this project. 119 | 120 | g:erlang_flymake_options *g:erlang_flymake_options* 121 | Similar to |g:erlang_make_options|, but used when performing 122 | on-the-fly syntax check (a.k.a. flymake). The default value is "". 123 | 124 | Loading and reloading: This variable is read directly before it is 125 | used. This means that after it is modified, the new value immediately 126 | takes effect. 127 | 128 | Example: The following setting makes xref to be executed when 129 | performing a syntax check. > 130 | 131 | let g:erlang_flymake_options = '--outdir /var/tmp --xref' 132 | 133 | < Setting an outdir directory is necessary, because xref needs the BEAM 134 | file to analyze the module. 135 | 136 | g:erlang_flymake_options_rules *g:erlang_flymake_options_rules* 137 | Similar to |g:erlang_make_options_rules|, but used when performing 138 | on-the-fly syntax check (a.k.a. flymake). The default value is []. 139 | 140 | Loading and reloading: similar to |g:erlang_flymake_options|. 141 | 142 | g:erlang_quickfix_support *g:erlang_quickfix_support* 143 | Enable |quickfix| support instead of |location-list|. If `1`, 144 | it is enabled, if `0`, it is disabled. The default value is `0`. 145 | 146 | COMPILATION OPTIONS *vim-erlang-compilation-options* 147 | 148 | --outdir DIR 149 | Put the compiled beam file into the given directory. It is relative to 150 | directory containing the file to compile. 151 | 152 | --xref 153 | Execute xref on the beam file and print undefined functions. (Other 154 | xref warnings are not printed, because those should be also printed by 155 | the compiler.) Works only if --outdir is specified. 156 | 157 | --load NODE_NAME_TYPE MY_NODE_NAME TARGET_NODE_NAME 158 | After successful compilation, start a node with MY_NODE_NAME and load 159 | the module into the target node. NODE_NAME_TYPE must be either 160 | "shortnames" or "longnames". Works only if --outdir is specified. See 161 | an example at |g:erlang_make_options_rules|. 162 | 163 | --cookie COOKIE 164 | When --load is used, this option can be used to set the cookie to be 165 | used towards the TARGET_NODE_NAME. 166 | 167 | --copy DIR 168 | After successful compilation, all beam files with the same number 169 | (recursively) under DIR will be overwritten with the newly generated 170 | beam file. Works only with Erlang R16 and above. 171 | 172 | ============================================================================== 173 | CONFIGURATION REBAR3 *vim-erlang-compiler-config-rebar3* 174 | 175 | If you want to make the syntax check of the current project under a different 176 | profile (see rebar3 profiles), you can easily instruct rebar3 by adding a 177 | special configuration item in your rebar.config file. 178 | 179 | By default, if nothing is specified, the plugin will assume you chose the 180 | |default| profile. 181 | 182 | E.g. to use the "test" profile: > 183 | 184 | {vim_erlang_compiler, [ 185 | {profile, "test"} 186 | ]}. 187 | < 188 | ============================================================================== 189 | CREDITS *vim-erlang-compiler-credits* 190 | 191 | Developed by the vim-erlang community. Distributed under Vim's |license|. 192 | 193 | vim-erlang-compiler's original source code comes from vimerl 194 | (https://github.com/jimenezrick/vimerl). 195 | 196 | Author: Pawel 'kTT' Salata 197 | Contributors: Ricardo Catalinas Jiménez 198 | Jesse Gumm 199 | Adam Rutkowski 200 | Michael Coles 201 | James Fish 202 | Csaba Hoch 203 | Krister Svanlund 204 | Leonardo Rossi 205 | License: Vim License (see |license|) 206 | 207 | ============================================================================== 208 | CONTRIBUTING *vim-erlang-compiler-contributing* 209 | 210 | Bug reports, suggestions and improvements encouraged at the project's github: 211 | 212 | https://github.com/vim-erlang/vim-erlang-compiler 213 | 214 | ============================================================================== 215 | -------------------------------------------------------------------------------- /ftplugin/erlang.vim: -------------------------------------------------------------------------------- 1 | if exists('b:erlang_compiler_loaded') 2 | finish 3 | else 4 | let b:erlang_compiler_loaded = 1 5 | endif 6 | 7 | compiler erlang 8 | -------------------------------------------------------------------------------- /plugin/erlang_compiler.vim: -------------------------------------------------------------------------------- 1 | " vim-erlang-compiler file 2 | " Language: Erlang 3 | " Author: Pawel 'kTT' Salata 4 | " Contributors: Ricardo Catalinas Jiménez 5 | " James Fish 6 | " License: Vim license 7 | 8 | if exists('g:loaded_erlang_compiler') 9 | finish 10 | endif 11 | 12 | let g:loaded_erlang_compiler = 1 13 | 14 | if !exists("g:erlang_show_errors") || g:erlang_show_errors 15 | call erlang_compiler#EnableShowErrors() 16 | endif 17 | 18 | if !exists("g:erlang_make_options") 19 | let g:erlang_make_options = '--outdir .' 20 | endif 21 | 22 | if !exists("g:erlang_flymake_options") 23 | let g:erlang_flymake_options = '' 24 | endif 25 | 26 | if !exists("g:erlang_make_options_rules") 27 | let g:erlang_make_options_rules = [] 28 | endif 29 | 30 | if !exists("g:erlang_flymake_options_rules") 31 | let g:erlang_flymake_options_rules = [] 32 | endif 33 | 34 | if !exists("g:erlang_quickfix_support") 35 | let g:erlang_quickfix_support = 0 36 | endif 37 | 38 | command ErlangDisableShowErrors call erlang_compiler#DisableShowErrors() 39 | command ErlangEnableShowErrors call erlang_compiler#EnableShowErrors() 40 | command ErlangToggleShowErrors call erlang_compiler#ToggleShowErrors() 41 | --------------------------------------------------------------------------------