@@ -92,6 +92,13 @@ defmodule Mix.Tasks.Compile.App do
92
92
* `--compile-path` - where to find `.beam` files and write the
93
93
resulting `.app` file, defaults to `Mix.Project.compile_path/0`
94
94
95
+ ## Configuration
96
+
97
+ * `:reliable_dir_mtime` - this task relies on the operating system
98
+ changing the mtime on a directory whenever a file is added or removed.
99
+ You can set this option to false if your system does not provide
100
+ reliable mtimes. Defaults to false on Windows.
101
+
95
102
## Phases
96
103
97
104
Applications provide a start phases mechanism which will be called,
@@ -130,50 +137,58 @@ defmodule Mix.Tasks.Compile.App do
130
137
@ impl true
131
138
def run ( args ) do
132
139
{ opts , _ , _ } = OptionParser . parse ( args , switches: [ force: :boolean , compile_path: :string ] )
133
-
134
140
project = Mix.Project . get! ( )
135
141
config = Mix.Project . config ( )
136
142
137
143
app = Keyword . get ( config , :app )
138
144
version = Keyword . get ( config , :version )
145
+ validate_app! ( app )
146
+ validate_version! ( version )
139
147
140
- validate_app ( app )
141
- validate_version ( version )
148
+ compile_path = Keyword . get_lazy ( opts , :compile_path , & Mix.Project . compile_path / 0 )
149
+ target = Path . join ( compile_path , " #{ app } .app" )
142
150
143
- path = Keyword . get_lazy ( opts , :compile_path , & Mix.Project . compile_path / 0 )
144
- modules = modules_from ( path ) |> Enum . sort ( )
145
-
146
- target = Path . join ( path , "#{ app } .app" )
147
-
148
- # We mostly depend on the project_file through the def application function,
149
- # but it doesn't hurt to also include config_mtime.
151
+ # If configurations changed, we may have changed compile_env.
152
+ # If compile_path changed, we may have added or removed files.
153
+ # If the project changed, we may have changed other properties.
150
154
new_mtime =
151
- max ( Mix.Project . config_mtime ( ) , Mix.Utils . last_modified ( Mix.Project . project_file ( ) ) )
155
+ Mix.Project . config_mtime ( )
156
+ |> max ( Mix.Utils . last_modified ( Mix.Project . project_file ( ) ) )
157
+ |> max ( Mix.Utils . last_modified ( compile_path ) )
152
158
153
159
current_properties = current_app_properties ( target )
154
- compile_env = load_compile_env ( current_properties )
155
- old_mtime = Keyword . get ( current_properties , :config_mtime , 0 )
156
160
157
- if opts [ :force ] || new_mtime > old_mtime ||
158
- app_changed? ( current_properties , modules , compile_env ) do
161
+ { changed? , modules } =
162
+ cond do
163
+ opts [ :force ] || new_mtime > Mix.Utils . last_modified ( target ) ->
164
+ { true , nil }
165
+
166
+ Keyword . get ( config , :reliable_dir_mtime , fn -> not match? ( { :win32 , _ } , :os . type ( ) ) end ) ->
167
+ { false , nil }
168
+
169
+ true ->
170
+ modules = modules_from ( compile_path )
171
+ { modules != Keyword . get ( current_properties , :modules , [ ] ) , modules }
172
+ end
173
+
174
+ if changed? do
159
175
properties =
160
176
[
161
177
description: to_charlist ( config [ :description ] || app ) ,
162
- modules: modules ,
163
178
registered: [ ] ,
164
179
vsn: to_charlist ( version )
165
180
]
166
181
|> merge_project_application ( project )
167
- |> validate_properties! ( )
168
182
|> handle_extra_applications ( config )
169
- |> add_compile_env ( compile_env )
183
+ |> add_compile_env ( current_properties )
184
+ |> add_modules ( modules , compile_path )
170
185
171
- properties = [ config_mtime: new_mtime ] ++ properties
172
186
contents = :io_lib . format ( "~p.~n" , [ { :application , app , properties } ] )
173
187
:application . load ( { :application , app , properties } )
174
188
175
189
Mix.Project . ensure_structure ( )
176
190
File . write! ( target , IO . chardata_to_string ( contents ) )
191
+ File . touch! ( target , new_mtime )
177
192
Mix . shell ( ) . info ( "Generated #{ app } app" )
178
193
{ :ok , [ ] }
179
194
else
@@ -189,27 +204,15 @@ defmodule Mix.Tasks.Compile.App do
189
204
end
190
205
end
191
206
192
- defp load_compile_env ( current_properties ) do
193
- case Mix.ProjectStack . compile_env ( nil ) do
194
- nil -> Keyword . get ( current_properties , :compile_env , [ ] )
195
- list -> list
196
- end
197
- end
198
-
199
- defp app_changed? ( properties , mods , compile_env ) do
200
- Keyword . get ( properties , :modules , [ ] ) != mods or
201
- Keyword . get ( properties , :compile_env , [ ] ) != compile_env
202
- end
207
+ defp validate_app! ( app ) when is_atom ( app ) , do: :ok
203
208
204
- defp validate_app ( app ) when is_atom ( app ) , do: :ok
205
-
206
- defp validate_app ( app ) do
207
- ensure_present ( :app , app )
209
+ defp validate_app! ( app ) do
210
+ ensure_present! ( :app , app )
208
211
Mix . raise ( "Expected :app to be an atom, got: #{ inspect ( app ) } " )
209
212
end
210
213
211
- defp validate_version ( version ) do
212
- ensure_present ( :version , version )
214
+ defp validate_version! ( version ) do
215
+ ensure_present! ( :version , version )
213
216
214
217
if not ( is_binary ( version ) and match? ( { :ok , _ } , Version . parse ( version ) ) ) do
215
218
Mix . raise (
@@ -218,18 +221,20 @@ defmodule Mix.Tasks.Compile.App do
218
221
end
219
222
end
220
223
221
- defp ensure_present ( name , nil ) do
224
+ defp ensure_present! ( name , nil ) do
222
225
Mix . raise ( "Please ensure mix.exs file has the #{ inspect ( name ) } in the project definition" )
223
226
end
224
227
225
- defp ensure_present ( _name , _val ) , do: :ok
228
+ defp ensure_present! ( _name , _val ) , do: :ok
226
229
227
230
defp modules_from ( path ) do
228
231
case File . ls ( path ) do
229
232
{ :ok , entries } ->
230
- for entry <- entries ,
231
- String . ends_with? ( entry , ".beam" ) ,
232
- do: entry |> binary_part ( 0 , byte_size ( entry ) - 5 ) |> String . to_atom ( )
233
+ Enum . sort (
234
+ for entry <- entries ,
235
+ String . ends_with? ( entry , ".beam" ) ,
236
+ do: entry |> binary_part ( 0 , byte_size ( entry ) - 5 ) |> String . to_atom ( )
237
+ )
233
238
234
239
{ :error , _ } ->
235
240
[ ]
@@ -246,7 +251,7 @@ defmodule Mix.Tasks.Compile.App do
246
251
)
247
252
end
248
253
249
- Keyword . merge ( best_guess , project_application )
254
+ Keyword . merge ( best_guess , validate_properties! ( project_application ) )
250
255
else
251
256
best_guess
252
257
end
@@ -357,8 +362,21 @@ defmodule Mix.Tasks.Compile.App do
357
362
defp typed_app? ( { app , type } ) when is_atom ( app ) and type in [ :required , :optional ] , do: true
358
363
defp typed_app? ( _ ) , do: false
359
364
360
- defp add_compile_env ( properties , [ ] ) , do: properties
361
- defp add_compile_env ( properties , compile_env ) , do: [ compile_env: compile_env ] ++ properties
365
+ defp add_compile_env ( properties , current_properties ) do
366
+ # If someone calls compile.elixir and then compile.app across two
367
+ # separate OS calls, then the compile_env won't be properly reflected.
368
+ # This is ok because compile_env is not used for correctness. It is
369
+ # simply to catch possible errors early.
370
+ case Mix.ProjectStack . compile_env ( nil ) do
371
+ nil -> Keyword . take ( current_properties , [ :compile_env ] ) ++ properties
372
+ [ ] -> properties
373
+ compile_env -> Keyword . put ( properties , :compile_env , compile_env )
374
+ end
375
+ end
376
+
377
+ defp add_modules ( properties , modules , compile_path ) do
378
+ Keyword . put_new_lazy ( properties , :modules , fn -> modules || modules_from ( compile_path ) end )
379
+ end
362
380
363
381
defp handle_extra_applications ( properties , config ) do
364
382
{ extra , properties } = Keyword . pop ( properties , :extra_applications , [ ] )
0 commit comments