├── .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 |
--------------------------------------------------------------------------------