1 %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
2 %%%---------------------------------------------------------------------------
3 %%% @author Eric Merritt
5 %%% Checks the dependencies in the system. Pulls down latest dependencies if
8 %%% @copyright (C) 2007-2011 Erlware
9 %%%---------------------------------------------------------------------------
10 -module(sin_task_depends
).
14 -include_lib("sinan/include/sinan.hrl").
17 -export([description
/0,
21 -define(TASK
, depends
).
24 %%====================================================================
26 %%====================================================================
28 %% @doc provide a description of the system for the caller
29 -spec
description() -> sin_task:task_description().
36 This task analyzes all of the dependencies in the project and
37 provides that information to the build state for use by other
40 Dependencies are taken from three areas.
42 1. Dependencies specified in the `applications` and `included_applications`
43 element in each OTP app metadata file.
44 2. Dependency constraints specified in the projects `sinan.config`
45 3. Dependency constraints in a `dep_constraints` element in the OTP App metadata.
47 `dep_constraints` is a Sinan only addition to the metadata that OTP ignores.
50 Constraining Dependency Versions
51 --------------------------------
53 To constrain the versions of set of dependencies that you rely on you
54 must add a `dep_constraints` tuple to either the [[OTPApplication]]
55 dotapp file or the `sinan.config`. The `dep_constraints` tuple
56 contains a list of dependency constraints for the [[OTPApplication]]
57 in the case of the dotapp or for the entire project in the case of the
58 `sinan.config`. An individual dependency constraint may look as follows.
60 {<app-name>, <version>, <type>}
64 {<app-name>, <version 1>, <version 2>, <type>}
68 {<app-name>, <version>}
70 In the above case type may be one of the following
72 * gte: greater than or equal
73 * lte: less than or equal
77 * between: between two versions
79 `between` is the specifier that takes two versions. So if we depended
80 on my_app between versions 0.1 and 5.0 we would have the entry:
82 {my_foo, \"0.1\", \"5.0\", between}
84 that would provide the constraints we need.
86 The form `{<app-name>, <version>}` is exactly equivalent to
87 `{<app-name>, <version>, eql}` and is provided as a convenience.
89 Lets look at a complete example used in sinan itself. This is from
90 sinan's `sinan.config`.
94 [{cucumberl, \"0.0.4\", gte},
95 {erlware_commons, \"0.6.0\", gte},
96 {getopt, \"0.0.1\", gte}]}.
98 This is specifying the versions for the entire sinan project.
105 short_desc
= "Dependency resolution for the project",
110 %% @doc gather all the dependencies for the system
111 -spec
do_task(sin_config:config(), sin_state:state()) ->
113 do_task(Config
, State0
) ->
114 ProjectApps
= sin_state:get_value(project_applist
, State0
),
115 {State1
, {ReleaseApps
, RuntimeDeps0
, CompiletimeDeps
}} =
116 solve_deps(Config
, State0
, ProjectApps
),
118 lists:foreach(fun(#app
{path
=Path
}) ->
119 Ebin
= filename:join(Path
, "ebin"),
120 ec_file:mkdir_path(Ebin
),
121 true
= code:add_patha(Ebin
)
122 end, CompiletimeDeps
),
124 MergedDeps
= RuntimeDeps0
++ CompiletimeDeps
,
127 format_app(Config
, App
)
129 sin_log:verbose(Config
, "~ncompile time dependencies:~n"),
130 lists:foreach(FA
, CompiletimeDeps
),
132 sin_log:verbose(Config
, "~nruntime dependencies:~n"),
133 lists:foreach(FA
, RuntimeDeps0
),
135 sin_log:verbose(Config
, "~nproject applications:~n"),
136 lists:foreach(FA
, ReleaseApps
),
138 sin_state:store([{release_runtime_deps
, RuntimeDeps0
},
139 {release_compile_deps
, CompiletimeDeps
},
140 {release_deps
, MergedDeps
},
141 {project_apps
, ReleaseApps
},
142 {release_apps
, MergedDeps
++ ReleaseApps
}],
145 -spec
solve_deps(sin_config:config(), sin_state:state(), [atom()]) ->
146 {sin_state:state(), term()}.
147 solve_deps(Config
, State0
, ProjectApps
) ->
148 DefaultConstraints
= Config:match(dep_constraints
, []),
150 {Apps
, ActualSpecs
} = remove_excluded(ProjectApps
,
153 ResolverState0
= sin_dep_resolver:new(sin_fs_resolver
, Config
, State0
),
154 SolverState0
= sin_dep_solver:new(ResolverState0
),
156 {SolverState1
, RuntimeDeps0
} =
157 get_runtime_deps(Config
, State0
, SolverState0
, Apps
, ActualSpecs
),
158 {SolverState2
, CompiletimeDeps0
} =
159 get_compiletime_deps(Config
, State0
, SolverState1
, DefaultConstraints
),
161 ResolverState1
= sin_dep_solver:extract_resolver_state(SolverState2
),
162 {ReleaseApps1
, RuntimeDeps2
} =
163 seperate_resolve_deps(ResolverState1
, RuntimeDeps0
, ProjectApps
),
166 lists:foldl(fun({AppName
, Vsn
}, Acc
) ->
167 case in_runtime(AppName
, RuntimeDeps2
) of
172 sin_dep_resolver:resolve(ResolverState1
,
174 [#app
{name
=AppName
, vsn
=Vsn
, path
=Path
,
175 type
=compiletime
, project
=false
} |
178 end, [], CompiletimeDeps0
),
182 sin_sig:save_sig_info(?MODULE
,
183 {ReleaseApps1
, RuntimeDeps2
, CompiletimeDeps1
},
186 {State1
, {ReleaseApps1
, RuntimeDeps2
, CompiletimeDeps1
}}.
188 seperate_resolve_deps(ResolverState1
, RuntimeDeps0
, ProjectApps
) ->
189 lists:foldl(fun({AppName
, Vsn
}, {ReleaseApps0
, RuntimeDeps1
}) ->
191 sin_dep_resolver:resolve(ResolverState1
,
193 case get_project_app(AppName
, ProjectApps
) of
195 {[PApp#app
{path
=Path
,
197 project
=true
} | ReleaseApps0
],
201 [#app
{name
=AppName
, vsn
=Vsn
, path
=Path
,
203 project
=false
} | RuntimeDeps1
]}
205 end, {[], []}, RuntimeDeps0
).
207 get_project_app(AppName
, ProjectApps
) ->
208 ec_lists:search(fun(PApp
=#app
{name
=PAppName
}) ->
209 case PAppName
== AppName
of
218 in_runtime(App0
, RuntimeDeps
) ->
219 lists:any(fun(#app
{name
=AppName
})
220 when App0
== AppName
->
226 -spec
format_app(sin_config:config(), sinan:app()) -> ok
.
227 format_app(Config
, #app
{name
=Name0
, vsn
=Vsn0
, path
=Path
}) ->
228 Name1
= string:left(erlang:atom_to_list(Name0
), 25),
229 Vsn1
= string:left(Vsn0
, 10),
230 sin_log:verbose(Config
, " ~s ~s : ~s", [Name1
, Vsn1
, Path
]).
233 %% @doc Format an exception thrown by this module
234 -spec
format_exception(sin_exceptions:exception()) ->
236 format_exception(?
SIN_EXEP_UNPARSE(_
, {failed
, {possible_culprit
, SpecList1
}})) ->
237 ["Unable to resolve compile time dependencies, probably due "
238 "to the following constraints:~n",
239 lists:map(fun({AppName
, LimitSet
, Sources
}) ->
240 io_lib:format(" constraint on ~p with constraints ~p "
241 "originating from these application(s) ~p",
242 [AppName
, sets:to_list(LimitSet
),
243 sets:to_list(Sources
)]);
244 (AppName
) when is_atom(AppName
) ->
245 [" application", erlang:atom_to_list(AppName
), " in the project "]
247 format_exception(Exception
) ->
248 sin_exceptions:format_exception(Exception
).
250 %%====================================================================
251 %% Internal functions
252 %%====================================================================
253 -spec
get_compiletime_deps(sin_config:config(),
255 sin_dep_solver:state(),
256 [sin_dep_solver:spec()]) ->
257 {sin_dep_solver:state(),
258 [sin_dep_solver:spec()]}.
259 get_compiletime_deps(Config
, State0
, SolverState0
, DefaultSpecs
) ->
260 CompiletimeApps0
= Config:match(compile_deps
, []),
262 {CompiletimeApps1
, CompileSpecs
} = remove_excluded(CompiletimeApps0
,
265 case sin_dep_solver:all_deps(Config
, SolverState0
,
268 Error1
= {failed
, {possible_culprit
, _SpecList1
}} ->
269 ?
SIN_RAISE(State0
, Error1
);
271 lists:filter(fun({excluded
, _
}) ->
277 {SolverState0
, CompiletimeDeps0
}.
280 get_runtime_deps(Config
, State0
, SolverState0
, Apps
, Specs
) ->
281 case sin_dep_solver:all_deps(Config
, SolverState0
, Apps
,
283 Error1
= {failed
, {possible_culprit
, _SpecList1
}} ->
284 ?
SIN_RAISE(State0
, Error1
);
285 {SolverState1
, Deps0
} ->
287 lists:filter(fun({excluded
, _
}) ->
294 remove_excluded(Apps0
, Constraints
) ->
295 Apps1
= lists:filter(fun(#app
{name
=AppName
}) ->
296 not
lists:member({exclude
, AppName
},
300 [{AppName
, Vsn
} || #app
{name
=AppName
, vsn
=Vsn
} <- Apps1
],
302 {[AppName
|| #app
{name
=AppName
} <- Apps1
], DefaultSpecs
++ Constraints
}.