Skip to content

Commit

Permalink
Merge remote-tracking branch 'canonical/next'
Browse files Browse the repository at this point in the history
  • Loading branch information
ericbmerritt committed May 16, 2012
2 parents 4d44153 + 9cd6dbc commit b0df568
Show file tree
Hide file tree
Showing 38 changed files with 1,181 additions and 766 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -2,4 +2,5 @@ _build
erl_crash.dump
*.pyc
doc/_site
.pc
.pc
*.beam
1 change: 1 addition & 0 deletions INSTALL.md
Expand Up @@ -21,6 +21,7 @@ Dependencies
* [cucumberl](https://github.com/membase/cucumberl)
* [getopt](https://github.com/jcomellas/getopt)
* [proper](https://github.com/manopapad/proper)
* [joxa](https://github.com/erlware/joxa)

The versions of these OTP Applications that are required are detailed
in the sinan.config located in the top level of the sinan project. If
Expand Down
16 changes: 13 additions & 3 deletions Makefile
@@ -1,7 +1,10 @@
VSN=4.0.0
VSN=4.1.0
ERLC=/usr/bin/env erlc
ERL=/usr/bin/env erl
APPDIR= $(abspath ./_build/sinan/lib/sinan-$(VSN))

RELDIR=$(CURDIR)/_build/sinan
APPDIR=$(RELDIR)/lib/sinan-$(VSN)
LOGDIR=$(RELDIR)/logs
BINDIR=$(DESTDIR)/usr/bin
INSTALL_TARGET=$(DESTDIR)/usr/lib/erlang/lib/sinan-$(VSN)

Expand Down Expand Up @@ -45,7 +48,7 @@ build_behaviours: $(BEHAVIOURS)
+warn_obsolete_guard \
+warnings_as_errors +bin_opt_info +debug_info -W -o $(BEAMDIR) $(BEHAVIOURS)

main: setup build_behaviours ${ERL_OBJ} ${ERL_TEST_OBJ}
main: setup build_behaviours ${ERL_OBJ} ${ERL_TEST_OBJ}

$(BEAMDIR)/%.beam: %.erl
$(ERLC) -pa $(BEAMDIR) +warn_export_vars +warn_export_all \
Expand Down Expand Up @@ -81,6 +84,9 @@ smoketests: main
PYTHONPATH=$(PYPATH) python $$f ; \
done

ct: main $(LOGDIR)
ct_run -dir $(CURDIR)/test -logdir $(LOGDIR) -erl_args -pa $(BEAMDIR)

testall : cucumber proper eunit smoketests

gh-pages:
Expand Down Expand Up @@ -112,6 +118,10 @@ update-version:
git commit -m "Version bump $(VSN)"
git tag v$(VSN)

$(LOGDIR):
mkdir -p $(LOGDIR)


##
## Debian packaging support for sinan
##
Expand Down
2 changes: 1 addition & 1 deletion ebin/sinan.app
Expand Up @@ -2,7 +2,7 @@

{application, sinan,
[{description, "Build system for erlang"},
{vsn, "4.0.0"},
{vsn, "4.1.0"},
{modules, []},
{registered, [sin_sup]},
{applications, [kernel, stdlib, compiler, erlware_commons,
Expand Down
24 changes: 21 additions & 3 deletions include/sinan.hrl
Expand Up @@ -14,8 +14,25 @@
path :: string(),
type :: runtime | compiletime,
project :: boolean(),
modules :: sin_file_info:mod(),
sources :: sin_file_info:mod()}).
%% Everything below will only be populated if the
%% project field above is true otherwise they will be
%% undefined.
id="" :: string() | list(),
maxP=infinity :: integer() | infinity,
maxT=infinity :: integer() | infinity,
env=[] :: list(),
start_phases = undefined :: term() | undefined,
basedir :: string() | undefined,
description="" :: string() | list(),
registered :: [atom()] | undefined,
applications :: [atom()] | undefined,
included_applications :: [atom()] | undefined,
dep_constraints :: term() | undefined,
mod :: term() | undefined,
dotapp :: string() | undefined,
deps :: [atom()] | undefined,
properties=[] :: proplists:proplist(),
modules :: [atom()] | undefined}).

%% @doc describes a module in the system
%% - name: is the name of the module
Expand All @@ -24,7 +41,8 @@
path :: string(),
module_deps :: [module()],
includes :: [atom()],
include_timestamps :: [{string(), sin_file_info:date_time()}],
include_timestamps :: [{string(),
sin_file_info:date_time()}],
tags :: [atom()],
called_modules :: [atom()],
changed :: sin_file_info:date_time(),
Expand Down
2 changes: 1 addition & 1 deletion sinan.config
@@ -1,5 +1,5 @@
{project_name, sinan}.
{project_vsn, "4.0.0"}.
{project_vsn, "4.1.0"}.

{escript, [{include_apps,
[erlware_commons, cucumberl, proper,
Expand Down
244 changes: 206 additions & 38 deletions src/sin_app_meta.erl
Expand Up @@ -12,57 +12,225 @@
-include_lib("sinan/include/sinan.hrl").

%% API
-export([populate/2,
-export([build_meta/3,
populate_modules/1,
write_app_file/1,
format_exception/1]).

%% SLASH_RE was create by doing re:compile("\\/").
-define(SLASH_RE,
{re_pattern,0,0,
<<69,82,67,80,62,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,93,0,5,27,47,83,0,5,27,92,
84,0,10,0>>}).

%%====================================================================
%% API
%%====================================================================
write_app_file(#app{name=AppName,vsn=Vsn,path=Path,
description=Desc,registered=Reg,
applications=Appls,included_applications=IA,
mod=Mod,modules=AppModules,
id=Id,maxP=MaxP,maxT=MaxT,env=Env,
start_phases=StartPhases}) ->
MetaDataPath = filename:join([Path, "ebin",
erlang:atom_to_list(AppName) ++
".app"]),
MetaData = {application, AppName,
[{description, Desc},
{id, Id},
{vsn, Vsn},
{modules, AppModules},
{maxP, MaxP},
{maxT, MaxT},
{registered, Reg},
{included_applications, IA},
{applications, Appls},
{env, Env},
{mod, Mod},
{start_phases, StartPhases}]},
file:write_file(MetaDataPath,
io_lib:format("~p.\n",
[MetaData])).

%% @doc do the system preparation
-spec populate(sin_config:matcher(), sin_state:state()) -> sin_state:state().
populate(_Config, State0) ->
ProjectApps = sin_state:get_value(project_apps, State0),
lists:foldl(fun(App, State1) ->
prepare_app(State1, App)
end, State0, ProjectApps).

-spec prepare_app(sin_state:state(), string()) ->
sin_config:config().
prepare_app(State0, #app{name=AppName, path=AppBuildDir}) ->

BaseDetails = populate_modules(State0, AppName),

DotApp = filename:join([AppBuildDir, "ebin",
erlang:atom_to_list(AppName) ++ ".app"]),
populate_modules(App=#app{path=Path}) ->
EbinDir = filename:join(Path, "ebin"),
Modules =
filelib:fold_files(EbinDir,
".+\.beam",
true,
fun(File, Acc) ->
PathName = filename:rootname(File, ".beam"),
Name = string:substr(PathName,
erlang:length(EbinDir) + 2),
ModuleName =
erlang:list_to_atom(erlang:binary_to_list(
erlang:iolist_to_binary(
re:replace(Name,
?SLASH_RE,
".",
[global])))),
[ModuleName | Acc]
end,
[]),
App#app{modules=Modules}.

ok = file:write_file(DotApp,
io_lib:format("~p.\n",
[{application, AppName,
BaseDetails}])),
State0.
build_meta(Config, State0, AppDir) ->
AppFile = get_app_file(State0, AppDir),
case file:consult(AppFile) of
{ok, [Detail={application, _, _Details}]} ->
build_out_app_description(Config, State0, AppDir, AppFile, Detail);
{error, {_, Module, Desc}} ->
Error = Module:format_error(Desc),
?SIN_RAISE(State0, {invalid_app_file, AppFile, Error});
{error, Error} ->
?SIN_RAISE(State0,
{no_app_file_available, AppFile, Error})
end.

%% @doc Format an exception thrown by this module
-spec format_exception(sin_exceptions:exception()) ->
string().
string().
format_exception(?SIN_EXEP_UNPARSE(_, {unable_to_read_config,
AppFilePath, Error})) ->
io_lib:format("unable parse *.app file ~s:~s.",
[AppFilePath, file:format_error(Error)]);
format_exception(?SIN_EXEP_UNPARSE(_, {invalid_app_file, bad_name,
AppFile, AppName, WrongAppName})) ->
io_lib:format("~s file has wrong app name. Should be ~p,"
" but is ~p.", [AppFile, AppName, WrongAppName]);
format_exception(?SIN_EXEP_UNPARSE(_, {invalid_app_file, AppFile,
_AppName, Error})) ->
io_lib:format("Unable to parse *.app file ~s due to ~p.",
[AppFile, Error]);
format_exception(?SIN_EXEP_UNPARSE(_, {no_app_file_available,
AppFile, _AppName, Error})) ->
io_lib:format("Unable to access file ~s due to ~p.", [AppFile, Error]);
format_exception(Exception) ->
sin_exceptions:format_exception(Exception).

%%====================================================================
%% Internal functions
%%====================================================================
populate_modules(State, AppName) ->
Details = sin_state:get_value({apps, AppName, base}, State),

SourceModuleList = lists:map(fun({_, Module, _, _, _}) ->
Module
end,
sin_state:get_value({apps, AppName, src_modules_detail},
State)),

lists:reverse(
lists:foldl(fun(Element = {vsn, _}, Acc) ->
[{modules, SourceModuleList}, Element | Acc];
(Element, Acc) ->
[Element | Acc]
end, [], lists:keydelete(modules, 1, Details))).
rewrite_vsn(Config, State, AppDir, BaseDetails) ->
RWVSN = fun({vsn, VsnSpec}) ->
{vsn, vcs_vsn(Config, State, VsnSpec, AppDir)};
(El) -> El
end,
[RWVSN(Detail) || Detail <- BaseDetails].

build_out_app_description(Config, State,
AppDir, AppFile,
{application, AppName, Details0}) ->
Details1 = rewrite_vsn(Config, State, AppDir, Details0),
Applications =
case proplists:get_value(applications, Details1) of
undefined ->
[];
AppList ->
AppList
end,
IncludedApps =
case proplists:get_value(included_applications, Details1) of
undefined ->
[];
IncAppList ->
IncAppList
end,
DepConstraints =
case proplists:get_value(dep_constraints, Details1) of
undefined ->
[];
DepCons ->
DepCons
end,
Vsn = proplists:get_value(vsn, Details1),
#app{name=AppName,
vsn=Vsn,
id = proplists:get_value(id, Details1, ""),
maxP = proplists:get_value(maxP, Details1, infinity),
maxT = proplists:get_value(maxT, Details1, infinity),
env = proplists:get_value(env, Details1, []),
start_phases = proplists:get_value(start_phases,
Details1, undefined),
basedir = AppDir,
description = proplists:get_value(description, Details1),
registered =
case proplists:get_value(registered, Details1) of
undefined ->
[];
Value ->
Value
end,
applications = Applications,
included_applications = IncludedApps,
mod = proplists:get_value(mod, Details1),
dotapp=AppFile,
dep_constraints = DepConstraints,
deps=Applications ++ IncludedApps}.

get_app_file(State, AppDir) ->
EbinDir = filename:join([AppDir, "ebin"]),
SrcDir = filename:join([AppDir, "src"]),
case sin_utils:get_file_with_ext(EbinDir, "app") of
false ->
case sin_utils:get_file_with_ext(SrcDir, "app.src") of
false ->
%% This shouldn't occur
?SIN_RAISE(State, unable_to_find_app_file);
SrcAppFile ->
SrcAppFile
end;
AppFile ->
AppFile
end.

vcs_vsn(Config, State, Vcs, Dir) ->
case vcs_vsn_cmd(Vcs) of
{rm_v, Cmd} ->
strip_v(get_vsn(Config, State, Cmd, Dir));
Cmd ->
get_vsn(Config, State, Cmd, Dir)
end.

strip_v([$v | Vsn]) ->
Vsn;
strip_v([$V | Vsn]) ->
Vsn;
strip_v(Vsn) ->
Vsn.

get_vsn(_Config, _State, {explicit_vsn, VsnString}, _Dir) ->
VsnString;
get_vsn(Config, State, {cmd, CmdString}, Dir) ->
vcs_vsn_invoke(Config, State, CmdString, Dir);
get_vsn(Config, State, Cmd, Dir) ->
vcs_vsn_invoke(Config, State, Cmd, Dir).

vcs_vsn_cmd({rm_v, Vcs}) ->
{rm_v, vcs_vsn_cmd(Vcs)};
vcs_vsn_cmd(git) ->
%% Explicitly git-describe a committish to accommodate for projects
%% in subdirs which don't have a GIT_DIR. In that case we will
%% get a description of the last commit that touched the subdir.
case os:type() of
{win32,nt} ->
"FOR /F \"usebackq tokens=* delims=\" %i in "
"(`git log -n 1 \"--pretty=format:%h\" .`) do "
"@git describe --always --tags %i";
_ ->
"git describe --always --tags `git log -n 1 --pretty=format:%h .`"
end;
vcs_vsn_cmd(hg) -> "hg identify -i";
vcs_vsn_cmd(bzr) -> "bzr revno";
vcs_vsn_cmd(svn) -> "svnversion";
vcs_vsn_cmd({cmd, _Cmd}=Custom) -> Custom;
vcs_vsn_cmd(Version) -> {explicit_vsn, Version}.

vcs_vsn_invoke(Config, State, Cmd, Dir) ->
case sin_sh:sh(Config, Cmd, [{cd, Dir}]) of
{ok, VsnString} ->
string:strip(VsnString, right, $\n);
{error, Error} ->
?SIN_RAISE(State, {error_executing, Cmd, Error})
end.
1 change: 1 addition & 0 deletions src/sin_compile_jxa.erl
Expand Up @@ -25,6 +25,7 @@ get_target(BuildDir, File, ".jxa") ->
sin_file_info:mod(), [term()], string()) ->
{module(), sin_config:config()}.
build_file(Config, State, Module=#module{path=File}, Options, _Target) ->
sin_task:ensure_started(proper),
sin_log:verbose(Config, "Building ~s", [File]),
case joxa.compiler:'do-compile'(File, Options) of
Ctx when is_tuple(Ctx), element(1, Ctx) == context ->
Expand Down
2 changes: 1 addition & 1 deletion src/sin_dep_solver.erl
Expand Up @@ -246,7 +246,7 @@ new_limits() -> [].
add_limit(Config, State, Source, AppsLimits, NewLimit) ->
case is_valid_limit(NewLimit) of
false ->
sin_config:normal(Config, "Invalid constraint ~p found originating at ~p",
sin_log:normal(Config, "Invalid constraint ~p found originating at ~p",
[NewLimit, Source]),
?SIN_RAISE(State, {invalid_constraint, NewLimit, Source});
true ->
Expand Down

0 comments on commit b0df568

Please sign in to comment.