---
ebin/sinan.app | 1 +
features/sin_app_src.feature | 28 +++++++
src/sin_discover.erl | 185 ++++++++++++++++++++++++------------------
src/sin_task.erl | 1 +
src/sin_task_build.erl | 30 +++-----
src/sin_task_prepare.erl | 90 ++++++++++++++++++++
test/sin_app_src.erl | 120 +++++++++++++++++++++++++++
7 files changed, 355 insertions(+), 100 deletions(-)
create mode 100644 features/sin_app_src.feature
create mode 100644 src/sin_task_prepare.erl
create mode 100644 test/sin_app_src.erl
diff --git a/ebin/sinan.app b/ebin/sinan.app
index cdfea50..31dbdad 100644
--- a/ebin/sinan.app
+++ b/ebin/sinan.app
@@ -32,6 +32,7 @@
sin_task_test,
sin_task_xref,
sin_task_erts,
+ sin_task_prepare,
sin_compile_yrl,
sin_compile_erl,
sin_exceptions,
diff --git a/features/sin_app_src.feature b/features/sin_app_src.feature
new file mode 100644
index 0000000..a007062
--- /dev/null
+++ b/features/sin_app_src.feature
@@ -0,0 +1,28 @@
+Feature: Support app.src files in the src directory
+ In order to more obvious what are actually source files (any kind of source
file)
+ As an Erlang Developer
+ I want to be able to put an <app-name>.app.src file in my src directory
+ and have it be copied into the correct place
+
+ Scenario: Have an app.src handled correctly when no ebin exists
+ Given a generated project that contains an app.src
+ And does not contain an ebin/app
+ When a build step is run on this project
+ Then sinan should put the app file in ebin/.app
+ And build the app normally
+
+ Scenario: Have an ebin/app handled correctly when no app.src exists
+ Given a generated project that contains an ebin/app
+ And does not contain an app.src
+ When a build step is run on this project
+ Then sinan should put the app file in ebin/.app
+ And build the app normally
+
+ Scenario: Have a build errors when an ebin/app and src/app.src exists
+ Given a generated project that contains an app.src
+ And contains an ebin/app
+ When a build step is run on this project
+ Then sinan should warn the user that both an ebin/app and app.src exists
+ And the build should fail
+
+
diff --git a/src/sin_discover.erl b/src/sin_discover.erl
index d7ac1eb..31e12b5 100644
--- a/src/sin_discover.erl
+++ b/src/sin_discover.erl
@@ -115,16 +115,39 @@ process_raw_config(ProjectDir, Config, Override) ->
intuit_build_config(ProjectDir, Override) ->
%% App name is always the same as the name of the project dir
AppName = filename:basename(ProjectDir),
- case file:consult(filename:join([ProjectDir,
- "ebin",
- AppName ++ ".app"])) of
- {error, enoent} ->
- intuit_from_override(Override);
- {error, _} ->
- ?SIN_RAISE(Override, unable_to_intuit_config,
- "Unable to generate a project config");
- {ok, [{application, _, Rest}]} ->
- build_out_intuited_config(AppName, Rest)
+ try
+ case file:consult(get_app_file(ProjectDir, AppName, Override)) of
+ {error, _} ->
+ ?SIN_RAISE(Override, unable_to_intuit_config,
+ "Unable to generate a project config");
+ {ok, [{application, _, Rest}]} ->
+ build_out_intuited_config(AppName, Rest)
+ end
+ catch
+ throw:no_app_metadata_at_top_level ->
+ intuit_from_override(Override)
+ end.
+
+
+%% @doc the app file could be in ebin or src/app.src. we need to
+%% check for both
+-spec get_app_file(string(), string(), sin_config:config()) ->
+ string().
+get_app_file(ProjectDir, AppName, Config) ->
+ EbinDir = filename:join([ProjectDir,
+ "ebin",
+ AppName ++ ".app"]),
+ SrcDir = filename:join([ProjectDir,
+ "src",
+ AppName ++ ".app.src"]),
+ case {sin_utils:file_exists(Config, EbinDir),
+ sin_utils:file_exists(Config, SrcDir)} of
+ {_, true} ->
+ SrcDir;
+ {true, _} ->
+ EbinDir;
+ _ ->
+ throw(no_app_metadata_at_top_level)
end.
%% @doc Given information from the app dir that was found, create a new full
@@ -247,17 +270,18 @@ parent_dir([H | T], Acc) ->
Acc::list()) ->
Config::sin_config:config().
build_app_info(Config, [H|T], Acc) ->
- AppName = filename:basename(H),
- AppFile = filename:join([H, "ebin", string:concat(AppName, ".app")]),
+ {AppName, AppFile, AppDir} = get_app_info(H),
case file:consult(AppFile) of
{ok, [{application, Name, Details}]} ->
- Config2 = source_details(H, AppName,
+ Config2 = source_details(AppDir, AppName,
process_details("apps." ++ AppName ++ ".",
[{"name", Name},
{"dotapp", AppFile},
- {"basedir", H} | Details], Config))
- ,
- build_app_info(Config2, T, [AppName | Acc]);
+ {"basedir", AppDir} | Details],
+ Config)),
+ Config3 = sin_config:store(Config2, "apps." ++ AppName ++ ".base",
+ Details),
+ build_app_info(Config3, T, [AppName | Acc]);
{error, {_, Module, Desc}} ->
Error = Module:format_error(Desc),
?SIN_RAISE(Config, {invalid_app_file, Error},
@@ -271,6 +295,16 @@ build_app_info(Config, [H|T], Acc) ->
build_app_info(Config, [], Acc) ->
sin_config:store(Config, "project.applist", Acc).
+-spec get_app_info({ebin | appsrc, string()}) -> {string(), string()}.
+get_app_info({ebin, Dir}) ->
+ AppName = filename:basename(Dir),
+ AppFile = filename:join([Dir, "ebin", string:concat(AppName, ".app")]),
+ {AppName, AppFile, Dir};
+get_app_info({appsrc, Dir}) ->
+ AppName = filename:basename(Dir),
+ AppFile = filename:join([Dir, "src", string:concat(AppName, ".app.src")]),
+ {AppName, AppFile, Dir}.
+
-spec source_details(string(), string(), sin_config:config()) ->
sin_config:config().
source_details(Dir, AppName, Config) ->
@@ -309,8 +343,8 @@ look_for_app_dirs(Config, BuildDir, ProjectDir) ->
{ok, Value} -> Value;
_ -> []
end,
- case look_for_app_dirs(Config, BuildDir, ProjectDir, "",
- Ignorables, []) of
+ case process_possible_app_dir(Config, BuildDir, ProjectDir,
+ Ignorables, []) of
[] ->
?SIN_RAISE(Config, no_app_directories,
"Unable to find any application directories."
@@ -319,76 +353,67 @@ look_for_app_dirs(Config, BuildDir, ProjectDir) ->
Else
end.
-look_for_app_dirs(_, BuildDir, _Parent, BuildDir, _Ignore, Acc) ->
- Acc;
-look_for_app_dirs(Config, BuildDir, Parent, Sub, Ignorables, Acc) ->
- case sin_utils:is_dir_ignorable(Sub, Ignorables) or
- sin_utils:is_dir_ignorable(filename:join([Parent, Sub]), Ignorables) of
- true ->
- Acc;
- false ->
- process_app_dir(Config, BuildDir, Parent, Sub, Ignorables, Acc)
- end.
-
%% @doc Process the app dir to see if it is an application directory.
--spec process_app_dir(Config::sin_config:config(),
- BuildDir::string(), Parent::string(), Sub::string(),
- Ignorables::[string()], Acc::list()) ->
- ListOfDirs::[string()].
-process_app_dir(Config, BuildDir, Parent, Sub, Ignorables, Acc) ->
- Pwd = filename:join([Parent, Sub]),
- case filelib:is_dir(Pwd) of
+-spec process_possible_app_dir(Config::sin_config:config(),
+ BuildDir::string(),
+ TargetDir::string(),
+ Ignorables::[string()], Acc::list()) ->
+ ListOfDirs::[{ebin | appsrc, string()}].
+process_possible_app_dir(Config, BuildDir, TargetDir, Ignorables, Acc) ->
+ PossibleAppName = filename:basename(TargetDir),
+ case filelib:is_dir(TargetDir) andalso not
+ sin_utils:is_dir_ignorable(TargetDir, Ignorables) of
true ->
- {ok, Dirs} = file:list_dir(Pwd),
- Res = lists:foldl(fun(F, AccIn) ->
- File = filename:join([Pwd, F]),
- process_dirs(File, F, AccIn)
- end, none, Dirs),
- case {Res, Dirs} of
- {both, _} ->
- [Pwd | Acc];
- {_, []} ->
- Acc;
- {_, _} ->
- lists:foldl(fun(Elem, NAcc) ->
- look_for_app_dirs(Config,
- BuildDir,
- Pwd, Elem,
- Ignorables,
- NAcc)
+ {ok, Dirs} = file:list_dir(TargetDir),
+ Ebin = has_src_ebin_dotapp(Config, PossibleAppName,
+ TargetDir, Dirs),
+ AppSrc = has_src_appsrc(Config, PossibleAppName,
+ TargetDir, Dirs),
+ case {AppSrc, Ebin} of
+ {true, true} ->
+ ?SIN_RAISE(Config,
+ "conflict: ~s has both an ebin/*.app "
+ "and a src/*.app.src ", [TargetDir]);
+ {true, _} ->
+ [{appsrc, TargetDir} | Acc];
+ {_, true} ->
+ [{ebin, TargetDir} | Acc];
+ _ ->
+ lists:foldl(fun(Sub, NAcc) ->
+ Dir = filename:join([TargetDir, Sub]),
+ process_possible_app_dir(Config,
+ BuildDir,
+ Dir,
+
+ Ignorables,
+ NAcc)
end, Acc, Dirs)
end;
false ->
Acc
end.
-%% @doc Given a directory checks of the name is src or ebin, compares against
-%% its state and returns an indicator if the parent is a app dir.
--spec process_dirs(File::string(), FinateState::string(), src | ebin) ->
- src | ebin.
-process_dirs(File, F, ebin) ->
- case {filelib:is_dir(File), F} of
- {true, "src"} ->
- both;
- _ ->
- ebin
- end;
-process_dirs(File, F, src) ->
- case {filelib:is_dir(File), F} of
- {true, "ebin"} ->
- both;
- _ ->
- src
- end;
-process_dirs(File, F, Type) ->
- case {filelib:is_dir(File), F} of
- {true, "ebin"} ->
- ebin;
- {true, "src"} ->
- src;
- _ ->
- Type
- end.
+-spec has_src_ebin_dotapp(sin_config:config(),
+ string(), string(), [string()]) ->
+ boolean().
+has_src_ebin_dotapp(Config, BaseName, BaseDir, SubDirs) ->
+ lists:member("ebin", SubDirs) andalso
+ lists:member("src", SubDirs) andalso
+ sin_utils:file_exists(Config,
+ filename:join([BaseDir, "ebin",
+ BaseName ++
+ ".app"])).
+
+-spec has_src_appsrc(sin_config:config(),
+ string(), string(), [string()]) ->
+ boolean().
+has_src_appsrc(Config, BaseName, BaseDir, SubDirs) ->
+ lists:member("src", SubDirs) andalso
+ sin_utils:file_exists(Config,
+ filename:join([BaseDir, "src",
+ BaseName ++
+ ".app.src"])).
+
%% @doc Gather the list of modules that currently may need to be built.
gather_modules(SrcDir) ->
diff --git a/src/sin_task.erl b/src/sin_task.erl
index 7a4bc9c..5e65392 100644
--- a/src/sin_task.erl
+++ b/src/sin_task.erl
@@ -50,6 +50,7 @@ get_task_list(Config, TaskName) ->
-spec get_tasks() -> [record(task)].
get_tasks() ->
[sin_task_depends:description(),
+ sin_task_prepare:description(),
sin_task_version:description(),
sin_task_test:description(),
sin_task_shell:description(),
diff --git a/src/sin_task_build.erl b/src/sin_task_build.erl
index 4a928af..aa4a21e 100644
--- a/src/sin_task_build.erl
+++ b/src/sin_task_build.erl
@@ -31,7 +31,7 @@
-define(SIGNS, "moddeps").
-define(TASK, build).
--define(DEPS, [depends]).
+-define(DEPS, [prepare, depends]).
%%====================================================================
%% API
@@ -186,43 +186,33 @@ build_apps(BuildRef, BuildSupInfo, AppList, Args) ->
%% @doc Build an individual otp application.
build_app(BuildRef, Cache, Env, AppName, Args) ->
- AppVsn = sin_config:get_value(BuildRef, "apps." ++ AppName ++ ".vsn"),
+ AppBuildDir =
+ sin_config:get_value(BuildRef, "apps." ++ AppName ++ ".builddir"),
AppDir = sin_config:get_value(BuildRef, "apps." ++ AppName
- ++ ".basedir"),
- BuildTarget = lists:flatten([AppName, "-", AppVsn]),
- AppBuildDir = filename:join([Env#env.apps_build_dir, BuildTarget]),
- BuildRef2 = sin_config:store(BuildRef, "apps." ++ AppName ++ ".builddir",
- AppBuildDir),
- Target = filename:join([AppBuildDir, "ebin"]),
-
- Ignorables = sin_config:get_value(BuildRef2, "ignore_dirs", []),
-
- % Ignore the build dir when copying or we will create a deep monster in a
- % few builds
- BuildDir = sin_config:get_value(BuildRef2, "build_dir"),
- sin_utils:copy_dir(BuildRef2, AppBuildDir, AppDir, "", [BuildDir |
Ignorables]),
+ ++ ".basedir"),
+ Target = filename:join([AppBuildDir, "ebin"]),
- {EbinPaths, Includes} = setup_code_path(BuildRef2, Env, AppName),
+ {EbinPaths, Includes} = setup_code_path(BuildRef, Env, AppName),
code:add_patha(Target),
- {NewCache, NewFileList} = process_source_files(BuildRef2,
+ {NewCache, NewFileList} = process_source_files(BuildRef,
Cache,
Env#env.build_dir,
Target,
Includes,
gather_modules(BuildRef,
AppName)),
- BuildRef3 = sin_config:store(BuildRef2,
+ BuildRef2 = sin_config:store(BuildRef,
[{"apps." ++ AppName ++ ".code_paths",
[Target | EbinPaths]},
{"apps." ++ AppName ++ ".file_list",
NewFileList}]),
- build_sources(BuildRef3, NewFileList,
+ build_sources(BuildRef2, NewFileList,
Includes, Args, AppDir, Target),
- {NewCache, BuildRef3}.
+ {NewCache, BuildRef2}.
%% @doc go through each source file building with the correct build module.
-spec build_sources(sin_config:config(), [tuple()], [string()],
diff --git a/src/sin_task_prepare.erl b/src/sin_task_prepare.erl
new file mode 100644
index 0000000..215ef68
--- /dev/null
+++ b/src/sin_task_prepare.erl
@@ -0,0 +1,90 @@
+%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
+%%%---------------------------------------------------------------------------
+%%% @author Eric Merritt
+%%% @doc
+%%% Prepare the apps for building in the build area
+%%% @end
+%%% @copyright (C) 2011 Erlware, LLC.
+%%%---------------------------------------------------------------------------
+-module(sin_task_prepare).
+
+-behaviour(sin_task).
+
+-include("internal.hrl").
+
+%% API
+-export([description/0,
+ do_task/1,
+ format_exception/1]).
+
+-define(TASK, prepare).
+-define(DEPS, [depends]).
+
+%%====================================================================
+%% API
+%%====================================================================
+
+%% @doc provide a description of the system for the caller
+-spec description() -> sin_task:task_description().
+description() ->
+ Desc = "Prepares the build area for the rest of the tasks "
+ "that occur in the system",
+ #task{name = ?TASK,
+ task_impl = ?MODULE,
+ bare = false,
+ example = "prepare",
+ short_desc = "build area preparation",
+ deps = ?DEPS,
+ desc = Desc,
+ opts = []}.
+
+%% @doc do the system preparation
+-spec do_task(sin_config:config()) -> sin_config:config().
+do_task(BuildRef) ->
+ BuildDir = sin_config:get_value(BuildRef, "build.dir"),
+ Ignorables = sin_config:get_value(BuildRef, "ignore_dirs", []),
+
+ ProjectApps = sin_config:get_value(BuildRef, "project.allapps", []),
+ lists:foldl(fun(App, Config1) ->
+ prepare_app(Config1, BuildDir, App, Ignorables)
+ end, BuildRef, ProjectApps).
+
+-spec prepare_app(sin_config:config(), string(), string(),
+ [string()]) ->
+ sin_config:config().
+prepare_app(BuildRef0, BuildDir, AppInfo, Ignorables) ->
+ {AppName, _AppVsn, _Deps, AppBuildDir} = AppInfo,
+
+ AppStrName = erlang:atom_to_list(AppName),
+ AppDir = sin_config:get_value(BuildRef0, "apps." ++ AppStrName
+ ++ ".basedir"),
+
+ %% Ignore the build dir when copying or we will create a deep monster in a
+ %% few builds
+ sin_utils:copy_dir(BuildRef0, AppBuildDir, AppDir, "",
+ [BuildDir | Ignorables]),
+
+ BuildRef1 = sin_config:store(BuildRef0, "apps." ++ AppStrName ++
".builddir",
+ AppBuildDir),
+
+ BaseDetails = sin_config:get_value(BuildRef1,
+ "apps." ++ AppStrName ++ ".base"),
+
+ DotApp = filename:join([AppBuildDir, "ebin", AppStrName ++ ".app"]),
+
+
+ ok = file:write_file(DotApp,
+ io_lib:format("~p.\n",
+ [{application, AppName,
+ BaseDetails}])),
+ BuildRef1.
+
+%% @doc Format an exception thrown by this module
+-spec format_exception(sin_exceptions:exception()) ->
+ string().
+format_exception(Exception) ->
+ sin_exceptions:format_exception(Exception).
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
diff --git a/test/sin_app_src.erl b/test/sin_app_src.erl
new file mode 100644
index 0000000..e85c7dc
--- /dev/null
+++ b/test/sin_app_src.erl
@@ -0,0 +1,120 @@
+-module(sin_app_src).
+
+-include_lib("eunit/include/eunit.hrl").
+
+-export([given/3, 'when'/3, then/3]).
+
+given([a, generated, project, that, contains, an, 'ebin/app'],
+ _State, _) ->
+ {ok, BaseDir} = ewl_file:create_tmp_dir("/tmp"),
+ ProjectName = "super_foo",
+ {ProjectDir, _} =
+ sin_test_project_gen:single_app_project(BaseDir, ProjectName),
+ {ok, {ProjectDir, ProjectName}};
+given([a,generated,project,that,contains,an,'app.src'], _State, _) ->
+ {ok, BaseDir} = ewl_file:create_tmp_dir("/tmp"),
+ ProjectName = "super_foo",
+ {ProjectDir, _} =
+ sin_test_project_gen:single_app_project(BaseDir, ProjectName),
+ AppSrcPath = filename:join([ProjectDir, "src",
+ ProjectName ++ ".app.src"]),
+ ?assertMatch(ok, file:write_file(AppSrcPath, app_src(ProjectName))),
+ {ok, {ProjectDir, ProjectName}};
+given([does,'not',contain,an,'ebin/app'],
+ State = {ProjectDir, ProjectName}, _) ->
+ AppSrc = filename:join([ProjectDir, "ebin", ProjectName ++ ".app"]),
+ ?assertMatch(ok, delete_if_exists(AppSrc)),
+ {ok, State};
+given([does,'not',contain,an,'app.src'],
+ State = {ProjectDir, ProjectName}, _) ->
+ AppSrc = filename:join([ProjectDir, "src", ProjectName ++ ".app.src"]),
+ ?assertMatch(ok, delete_if_exists(AppSrc)),
+ {ok, State};
+given([contains,an,'ebin/app'],
+ State = {ProjectDir, ProjectName}, _) ->
+ %% Generated by default so lets just make sure it exists
+ AppSrc = filename:join([ProjectDir, "ebin", ProjectName ++ ".app"]),
+ ?assertMatch(true,
+ sin_utils:file_exists(sin_config:new(), AppSrc)),
+ {ok, State}.
+
+'when'([a, build, step, is, run, on, this, project],
+ {ProjectDir, ProjectName}, _) ->
+ Ret = sinan:run_sinan(["-s", ProjectDir, "build"]),
+ ?assertMatch({_, _}, Ret),
+ {_, TrueRet} = Ret,
+ {ok, {ProjectDir, ProjectName, TrueRet}}.
+
+then([build, the, app, normally], State = {_, _, BuildState}, _) ->
+ ?assertMatch([], sin_config:get_run_errors(BuildState)),
+ {ok, State};
+then([the, build, should, fail], State = {_, _, BuildState}, _) ->
+ ?assertMatch(1, erlang:length(sin_config:get_run_errors(BuildState))),
+ {ok, State};
+then([sinan, should, put, the, app, file, in, 'ebin/.app'],
+ State = {_, ProjectName, BuildState}, _) ->
+ verify_ebin_app(ProjectName, BuildState),
+ {ok, State};
+then([sinan, should, warn, the, user, that,
+ both, an, 'ebin/app', 'and', 'app.src', exists],
+ State = {ProjectDir, _, BuildState}, _) ->
+ ?assertMatch(
+ [{sin_discover,
+ {sin_discover, _,
+ {"conflict: ~s has both an ebin/*.app and a src/*.app.src ",
+ [ProjectDir]}}}],
+ sin_config:get_run_errors(BuildState)),
+ {ok, State};
+then([warn, the, user, that, the,
+ 'ebin/app', is, being, ignored],
+ State = {_, _, BuildState}, _) ->
+ Warnings = sin_config:get_run_warnings(BuildState),
+ ?assertMatch(true,
+ lists:any(fun({sin_discover, Warning}) ->
+ case Warning of
+ "Unexpected ebin/.app overriding with
src/app.src" ->
+ true;
+ _ ->
+ false
+ end;
+ (_) ->
+ false
+ end, Warnings)),
+ {ok, State}.
+
+app_src(Name) ->
+ ["%% This is the application resource file (.app file) for the app2,\n"
+ "%% application.\n"
+ "{application, ", Name, ",\n"
+ "[{description, \"Your Desc HERE\"},\n"
+ " {vsn, \"0.1.0\"},\n"
+ " {modules, [", Name, "_app,\n"
+ " ", Name, "_sup]},\n"
+ " {registered,[", Name, "_sup]},\n"
+ " {applications, [kernel, stdlib]},\n"
+ " {mod, {", Name, "_app,[]}}, \n"
+ " {start_phases, []}]}.\n"].
+
+delete_if_exists(Path) ->
+ case file:delete(Path) of
+ ok ->
+ ok;
+ {error, enoent} ->
+ ok;
+ Error ->
+ throw(Error)
+ end.
+
+verify_ebin_app(ProjectName, BuildState) ->
+ BaseDir = sin_config:get_value(BuildState,
+ "apps." ++ ProjectName ++ ".builddir"),
+ BasePath = filename:join([BaseDir, "ebin", ProjectName ++
+ ".app"]),
+ ?assertMatch(true,
+ sin_utils:file_exists(sin_config:new(), BasePath)),
+ AppContents = file:consult(BasePath),
+ AtomName = erlang:list_to_atom(ProjectName),
+ ?assertMatch({ok, [{application, AtomName, _}]}, AppContents),
+ {ok, [{_, _, Details}]} = AppContents,
+ ?assertMatch({vsn, "0.1.0"}, lists:keyfind(vsn, 1, Details)),
+ Details.
--
1.7.5.2
--
You received this message because you are subscribed to the Google Groups
"erlware-dev" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/erlware-dev?hl=en.