@@ -4,140 +4,171 @@ defmodule ElixirLS.LanguageServer.Build do
4
4
require Logger
5
5
6
6
def build ( parent , root_path , opts ) when is_binary ( root_path ) do
7
- spawn_monitor ( fn ->
8
- with_build_lock ( fn ->
9
- { us , result } =
10
- :timer . tc ( fn ->
11
- Logger . info ( "Starting build with MIX_ENV: #{ Mix . env ( ) } MIX_TARGET: #{ Mix . target ( ) } " )
12
-
13
- # read cache before cleaning up mix state in reload_project
14
- cached_deps = read_cached_deps ( )
15
- mixfile = SourceFile.Path . absname ( MixfileHelpers . mix_exs ( ) , root_path )
16
-
17
- case reload_project ( mixfile , root_path ) do
18
- { :ok , mixfile_diagnostics } ->
19
- { deps_result , deps_raw_diagnostics } =
20
- with_diagnostics ( [ log: true ] , fn ->
21
- try do
22
- # this call can raise
23
- current_deps = Mix.Dep . load_on_environment ( [ ] )
24
-
25
- purge_changed_deps ( current_deps , cached_deps )
26
-
27
- if Keyword . get ( opts , :fetch_deps? ) and current_deps != cached_deps do
28
- fetch_deps ( current_deps )
7
+ build_pid_reference =
8
+ spawn_monitor ( fn ->
9
+ with_build_lock ( fn ->
10
+ { us , result } =
11
+ :timer . tc ( fn ->
12
+ Logger . info ( "Starting build with MIX_ENV: #{ Mix . env ( ) } MIX_TARGET: #{ Mix . target ( ) } " )
13
+
14
+ # read cache before cleaning up mix state in reload_project
15
+ cached_deps = read_cached_deps ( )
16
+ mixfile = SourceFile.Path . absname ( MixfileHelpers . mix_exs ( ) , root_path )
17
+
18
+ case reload_project ( mixfile , root_path ) do
19
+ { :ok , mixfile_diagnostics } ->
20
+ { deps_result , deps_raw_diagnostics } =
21
+ with_diagnostics ( [ log: true ] , fn ->
22
+ try do
23
+ # this call can raise
24
+ current_deps =
25
+ if Version . match? ( System . version ( ) , "< 1.16.0-dev" ) do
26
+ Mix.Dep . load_on_environment ( [ ] )
27
+ else
28
+ Mix.Dep.Converger . converge ( [ ] )
29
+ end
30
+
31
+ purge_changed_deps ( current_deps , cached_deps )
32
+
33
+ if Keyword . get ( opts , :fetch_deps? ) and current_deps != cached_deps do
34
+ fetch_deps ( current_deps )
35
+ end
36
+
37
+ state = % {
38
+ get: Mix.Project . get ( ) ,
39
+ # project_file: Mix.Project.project_file(),
40
+ config: Mix.Project . config ( ) ,
41
+ # config_files: Mix.Project.config_files(),
42
+ config_mtime: Mix.Project . config_mtime ( ) ,
43
+ umbrella?: Mix.Project . umbrella? ( ) ,
44
+ apps_paths: Mix.Project . apps_paths ( ) ,
45
+ # deps_path: Mix.Project.deps_path(),
46
+ # deps_apps: Mix.Project.deps_apps(),
47
+ # deps_scms: Mix.Project.deps_scms(),
48
+ deps_paths: Mix.Project . deps_paths ( ) ,
49
+ # build_path: Mix.Project.build_path(),
50
+ manifest_path: Mix.Project . manifest_path ( )
51
+ }
52
+
53
+ ElixirLS.LanguageServer.MixProject . store ( state )
54
+
55
+ :ok
56
+ catch
57
+ kind , err ->
58
+ { payload , stacktrace } = Exception . blame ( kind , err , __STACKTRACE__ )
59
+ { :error , kind , payload , stacktrace }
60
+ end
61
+ end )
62
+
63
+ deps_diagnostics =
64
+ deps_raw_diagnostics
65
+ |> Enum . map ( & Diagnostics . code_diagnostic / 1 )
66
+
67
+ case deps_result do
68
+ :ok ->
69
+ if Keyword . get ( opts , :compile? ) do
70
+ { status , compile_diagnostics } =
71
+ run_mix_compile ( Keyword . get ( opts , :force? , false ) )
72
+
73
+ compile_diagnostics =
74
+ Diagnostics . normalize ( compile_diagnostics , root_path , mixfile )
75
+
76
+ Server . build_finished (
77
+ parent ,
78
+ { status , mixfile_diagnostics ++ deps_diagnostics ++ compile_diagnostics }
79
+ )
80
+
81
+ :"mix_compile_#{ status } "
82
+ else
83
+ Server . build_finished (
84
+ parent ,
85
+ { :ok , mixfile_diagnostics ++ deps_diagnostics }
86
+ )
87
+
88
+ :mix_compile_disabled
29
89
end
30
90
31
- state = % {
32
- get: Mix.Project . get ( ) ,
33
- # project_file: Mix.Project.project_file(),
34
- config: Mix.Project . config ( ) ,
35
- # config_files: Mix.Project.config_files(),
36
- config_mtime: Mix.Project . config_mtime ( ) ,
37
- umbrella?: Mix.Project . umbrella? ( ) ,
38
- apps_paths: Mix.Project . apps_paths ( ) ,
39
- # deps_path: Mix.Project.deps_path(),
40
- # deps_apps: Mix.Project.deps_apps(),
41
- # deps_scms: Mix.Project.deps_scms(),
42
- deps_paths: Mix.Project . deps_paths ( ) ,
43
- # build_path: Mix.Project.build_path(),
44
- manifest_path: Mix.Project . manifest_path ( )
45
- }
46
-
47
- ElixirLS.LanguageServer.MixProject . store ( state )
48
-
49
- :ok
50
- catch
51
- kind , err ->
52
- { payload , stacktrace } = Exception . blame ( kind , err , __STACKTRACE__ )
53
- { :error , kind , payload , stacktrace }
54
- end
55
- end )
56
-
57
- deps_diagnostics =
58
- deps_raw_diagnostics
59
- |> Enum . map ( & Diagnostics . code_diagnostic / 1 )
60
-
61
- case deps_result do
62
- :ok ->
63
- if Keyword . get ( opts , :compile? ) do
64
- { status , compile_diagnostics } = run_mix_compile ( )
65
-
66
- compile_diagnostics =
67
- Diagnostics . normalize ( compile_diagnostics , root_path , mixfile )
68
-
91
+ { :error , kind , err , stacktrace } ->
92
+ # TODO get path from exception message
69
93
Server . build_finished (
70
94
parent ,
71
- { status , mixfile_diagnostics ++ deps_diagnostics ++ compile_diagnostics }
95
+ { :error ,
96
+ mixfile_diagnostics ++
97
+ deps_diagnostics ++
98
+ [
99
+ Diagnostics . error_to_diagnostic (
100
+ kind ,
101
+ err ,
102
+ stacktrace ,
103
+ mixfile ,
104
+ root_path
105
+ )
106
+ ] }
72
107
)
73
108
74
- :"mix_compile_#{ status } "
75
- else
76
- Server . build_finished (
77
- parent ,
78
- { :ok , mixfile_diagnostics ++ deps_diagnostics }
79
- )
109
+ :deps_error
110
+ end
80
111
81
- :mix_compile_disabled
82
- end
83
-
84
- { :error , kind , err , stacktrace } ->
85
- # TODO get path from exception message
86
- Server . build_finished (
87
- parent ,
88
- { :error ,
89
- mixfile_diagnostics ++
90
- deps_diagnostics ++
91
- [
92
- Diagnostics . error_to_diagnostic (
93
- kind ,
94
- err ,
95
- stacktrace ,
96
- mixfile ,
97
- root_path
98
- )
99
- ] }
100
- )
112
+ { :error , mixfile_diagnostics } ->
113
+ Server . build_finished ( parent , { :error , mixfile_diagnostics } )
114
+ :mixfile_error
101
115
102
- :deps_error
103
- end
116
+ :no_mixfile ->
117
+ Server . build_finished ( parent , { :no_mixfile , [ ] } )
118
+ :no_mixfile
119
+ end
120
+ end )
104
121
105
- { :error , mixfile_diagnostics } ->
106
- Server . build_finished ( parent , { :error , mixfile_diagnostics } )
107
- :mixfile_error
122
+ if Keyword . get ( opts , :compile? ) do
123
+ Tracer . save ( )
124
+ Logger . info ( "Compile took #{ div ( us , 1000 ) } milliseconds" )
125
+ else
126
+ Logger . info ( "Mix project load took #{ div ( us , 1000 ) } milliseconds" )
127
+ end
108
128
109
- :no_mixfile ->
110
- Server . build_finished ( parent , { :no_mixfile , [ ] } )
111
- :no_mixfile
112
- end
113
- end )
129
+ JsonRpc . telemetry ( "build" , % { "elixir_ls.build_result" => result } , % {
130
+ "elixir_ls.build_time" => div ( us , 1000 )
131
+ } )
132
+ end )
133
+ end )
114
134
115
- if Keyword . get ( opts , :compile? ) do
116
- Tracer . save ( )
117
- Logger . info ( "Compile took #{ div ( us , 1000 ) } milliseconds" )
118
- else
119
- Logger . info ( "Mix project load took #{ div ( us , 1000 ) } milliseconds" )
120
- end
135
+ spawn ( fn ->
136
+ Process . monitor ( parent )
137
+ { build_process , _ref } = build_pid_reference
138
+ Process . monitor ( build_process )
121
139
122
- JsonRpc . telemetry ( "build" , % { "elixir_ls.build_result" => result } , % {
123
- "elixir_ls.build_time" => div ( us , 1000 )
124
- } )
125
- end )
140
+ receive do
141
+ { :DOWN , _ref , _ , ^ build_process , _reason } ->
142
+ :ok
143
+
144
+ { :DOWN , _ref , _ , ^ parent , _reason } ->
145
+ Process . exit ( build_process , :kill )
146
+ end
126
147
end )
148
+
149
+ build_pid_reference
127
150
end
128
151
129
- def clean ( clean_deps? \\ false ) do
152
+ def clean ( root_path , clean_deps? \\ false ) when is_binary ( root_path ) do
130
153
with_build_lock ( fn ->
131
- Mix.Task . clear ( )
132
- run_mix_clean ( clean_deps? )
154
+ mixfile = SourceFile.Path . absname ( MixfileHelpers . mix_exs ( ) , root_path )
155
+
156
+ case reload_project ( mixfile , root_path ) do
157
+ { :ok , _ } ->
158
+ Mix.Task . clear ( )
159
+ run_mix_clean ( clean_deps? )
160
+
161
+ other ->
162
+ other
163
+ end
133
164
end )
134
165
end
135
166
136
167
def with_build_lock ( func ) do
137
168
:global . trans ( { __MODULE__ , self ( ) } , func )
138
169
end
139
170
140
- def reload_project ( mixfile , root_path ) do
171
+ defp reload_project ( mixfile , root_path ) do
141
172
if File . exists? ( mixfile ) do
142
173
if module = Mix.Project . get ( ) do
143
174
build_path = Mix.Project . config ( ) [ :build_path ]
@@ -321,7 +352,7 @@ defmodule ElixirLS.LanguageServer.Build do
321
352
end
322
353
end
323
354
324
- defp run_mix_compile do
355
+ defp run_mix_compile ( force? ) do
325
356
opts = [
326
357
"--return-errors" ,
327
358
"--ignore-module-conflict" ,
@@ -335,6 +366,13 @@ defmodule ElixirLS.LanguageServer.Build do
335
366
opts ++ [ "--all-warnings" ]
336
367
end
337
368
369
+ opts =
370
+ if force? do
371
+ opts ++ [ "--force" ]
372
+ else
373
+ opts
374
+ end
375
+
338
376
case Mix.Task . run ( "compile" , opts ) do
339
377
{ status , diagnostics } when status in [ :ok , :error , :noop ] and is_list ( diagnostics ) ->
340
378
{ status , diagnostics }
0 commit comments