├── debian ├── compat ├── source │ └── format ├── erlang-sinan.install ├── rules ├── copyright ├── changelog └── control ├── .gitignore ├── smoketests ├── smoketest └── tests │ ├── help_test.py │ ├── vsn_test.py │ ├── alpha_vsn_test.py │ ├── erts_vsn_test.py │ ├── fail.py │ ├── xref_test.py │ ├── echo_test.py │ ├── gen_single_app_test.py │ ├── hooks_test.py │ ├── gen_name_test.py │ ├── test_dir_test.py │ ├── configless_test.py │ └── release_test.py ├── doc ├── assets │ ├── images │ │ ├── github-logo.png │ │ ├── page-background.png │ │ └── github-ribbons │ │ │ ├── grey.png │ │ │ ├── red.png │ │ │ ├── black.png │ │ │ ├── green.png │ │ │ ├── orange.png │ │ │ └── white.png │ ├── javascript │ │ ├── lang-go.js │ │ ├── lang-lua.js │ │ ├── lang-ml.js │ │ ├── lang-sql.js │ │ ├── lang-tex.js │ │ ├── lang-vb.js │ │ ├── lang-vhdl.js │ │ ├── lang-wiki.js │ │ ├── lang-apollo.js │ │ ├── lang-scala.js │ │ ├── lang-proto.js │ │ ├── lang-yaml.js │ │ ├── lang-hs.js │ │ ├── lang-lisp.js │ │ ├── lang-css.js │ │ ├── lang-n.js │ │ ├── lang-clj.js │ │ └── html5.js │ └── css │ │ ├── skeleton │ │ ├── images │ │ │ ├── favicon.ico │ │ │ ├── apple-touch-icon.png │ │ │ ├── apple-touch-icon-114x114.png │ │ │ └── apple-touch-icon-72x72.png │ │ ├── robots.txt │ │ ├── javascripts │ │ │ └── tabs.js │ │ ├── 404.html │ │ ├── stylesheets │ │ │ └── layout.css │ │ └── index.html │ │ ├── prettify.css │ │ ├── code.css │ │ └── style.css ├── .dir-locals.el ├── _layouts │ └── default.html └── index.md ├── do-gh-pages ├── features ├── sint_jxa_build_support.feature ├── sint_multirun_support.feature ├── sint_remove_helper.feature ├── sint_default_help.feature ├── sint_sinan_as_escript.feature ├── sint_programatic_gen.feature ├── sint_sig_save.feature ├── sint_module_dependencies.feature ├── sint_testability.feature ├── sint_gh78.feature ├── sint_gen_app_src.feature ├── sint_gh50.feature ├── sint_app_src_mod_list.feature ├── sint_escript_exe.feature ├── sint_app_src.feature ├── sint_configurable_dependencies.feature ├── sint_cucumber_support.feature └── sint_gh52.feature ├── ebin └── sinan.app ├── sinan.config ├── test ├── sint_cuke_support_funs.erl ├── sint_default_help.erl ├── sint_sinan_as_escript.erl ├── sint_remove_helper.erl ├── sint_sig_save.erl ├── sint_testability.erl ├── sint_programatic_gen.erl ├── sint_jxa_build_support.erl ├── sint_gh78.erl ├── sint_gen_app_src.erl ├── sint_module_dependencies.erl ├── sint_yrl_support.erl ├── sint_gh50.erl ├── sint_multirun_support.erl ├── sint_gh52.erl ├── sint_app_src_mod_list.erl ├── sint_escript_exe.erl ├── sint_app_src.erl └── sint_test_project_gen.erl ├── src ├── sin_test_resolver.erl ├── sin_exceptions.erl ├── sin_jxa_info.erl ├── sin_log.erl ├── sin_task_erts.erl ├── sin_task_clean.erl ├── sin_compile_jxa.erl ├── sin_task_version.erl ├── sin_dep_resolver.erl ├── sin_task_echo.erl ├── sin_matcher.erl ├── sin_release.erl ├── sin_compile_erl.erl ├── sin_task_shell.erl ├── sin_compile_yrl.erl ├── sin_sh.erl ├── sin_task_doc.erl ├── sin_task_eqc.erl ├── sin_task_proper.erl ├── sin_task_help.erl ├── sin_task_eunit.erl ├── sin_task_dist.erl ├── sin_topo.erl ├── sin_deps.erl ├── sin_hooks.erl ├── sin_file_info.erl └── sin_state.erl ├── COPYING ├── README.md ├── overview.edoc ├── INSTALL.md ├── Makefile └── include └── sinan.hrl /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/erlang-sinan.install: -------------------------------------------------------------------------------- 1 | /usr/bin 2 | /usr/lib/erlang/lib -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | erl_crash.dump 3 | *.pyc 4 | doc/_site 5 | .pc 6 | *.beam -------------------------------------------------------------------------------- /smoketests/smoketest: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # run nose in the tests directory 4 | nosetests -s $1 5 | -------------------------------------------------------------------------------- /doc/assets/images/github-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/images/github-logo.png -------------------------------------------------------------------------------- /doc/assets/javascript/lang-go.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/javascript/lang-go.js -------------------------------------------------------------------------------- /doc/assets/javascript/lang-lua.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/javascript/lang-lua.js -------------------------------------------------------------------------------- /doc/assets/javascript/lang-ml.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/javascript/lang-ml.js -------------------------------------------------------------------------------- /doc/assets/javascript/lang-sql.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/javascript/lang-sql.js -------------------------------------------------------------------------------- /doc/assets/javascript/lang-tex.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/javascript/lang-tex.js -------------------------------------------------------------------------------- /doc/assets/javascript/lang-vb.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/javascript/lang-vb.js -------------------------------------------------------------------------------- /doc/assets/javascript/lang-vhdl.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/javascript/lang-vhdl.js -------------------------------------------------------------------------------- /doc/assets/javascript/lang-wiki.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/javascript/lang-wiki.js -------------------------------------------------------------------------------- /doc/assets/javascript/lang-apollo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/javascript/lang-apollo.js -------------------------------------------------------------------------------- /doc/assets/javascript/lang-scala.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/javascript/lang-scala.js -------------------------------------------------------------------------------- /doc/assets/images/page-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/images/page-background.png -------------------------------------------------------------------------------- /doc/assets/images/github-ribbons/grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/images/github-ribbons/grey.png -------------------------------------------------------------------------------- /doc/assets/images/github-ribbons/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/images/github-ribbons/red.png -------------------------------------------------------------------------------- /doc/assets/css/skeleton/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/css/skeleton/images/favicon.ico -------------------------------------------------------------------------------- /doc/assets/images/github-ribbons/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/images/github-ribbons/black.png -------------------------------------------------------------------------------- /doc/assets/images/github-ribbons/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/images/github-ribbons/green.png -------------------------------------------------------------------------------- /doc/assets/images/github-ribbons/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/images/github-ribbons/orange.png -------------------------------------------------------------------------------- /doc/assets/images/github-ribbons/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/images/github-ribbons/white.png -------------------------------------------------------------------------------- /doc/.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil . ((eval . (setq 2 | wdoc-directories 3 | (list (expand-file-name 4 | (file-name-directory (buffer-file-name))))))))) 5 | -------------------------------------------------------------------------------- /doc/assets/css/skeleton/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/css/skeleton/images/apple-touch-icon.png -------------------------------------------------------------------------------- /doc/assets/css/skeleton/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | # www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 3 | 4 | User-agent: * 5 | 6 | -------------------------------------------------------------------------------- /doc/assets/css/skeleton/images/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/css/skeleton/images/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /doc/assets/css/skeleton/images/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erlware-deprecated/sinan/HEAD/doc/assets/css/skeleton/images/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /do-gh-pages: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PARENT_SHA=$(git show-ref -s refs/heads/gh-pages) 4 | DOC_SHA=$(git ls-tree -d HEAD doc | awk '{print $3}') 5 | NEW_COMMIT=$(echo "Auto-update docs." | git commit-tree $DOC_SHA -p $PARENT_SHA) 6 | git update-ref refs/heads/gh-pages $NEW_COMMIT 7 | -------------------------------------------------------------------------------- /doc/assets/javascript/lang-proto.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.sourceDecorator({keywords:"bytes,default,double,enum,extend,extensions,false,group,import,max,message,option,optional,package,repeated,required,returns,rpc,service,syntax,to,true",types:/^(bool|(double|s?fixed|[su]?int)(32|64)|float|string)\b/,cStyleComments:!0}),["proto"]); 2 | -------------------------------------------------------------------------------- /smoketests/tests/help_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import pexpect 3 | import sin_testing as st 4 | 5 | class TestHelp(st.SmokeTest): 6 | 7 | @st.sinan("help") 8 | def test_help(self, child): 9 | child.expect("for more information run 'sinan help '") 10 | child.expect(pexpect.EOF) 11 | 12 | if __name__ == '__main__': 13 | unittest.main() 14 | -------------------------------------------------------------------------------- /features/sint_jxa_build_support.feature: -------------------------------------------------------------------------------- 1 | Feature: Sinan should support building joxa source 2 | In order to ensure sinan correctly builds joxa 3 | As an Erlang Developer 4 | I want to sinan to be able to be able to build joxa source 5 | 6 | Scenario: Build joxa source 7 | Given a generated project that contains joxa modules 8 | When a build step is run on this project 9 | Then build the app normally 10 | -------------------------------------------------------------------------------- /doc/assets/javascript/lang-yaml.js: -------------------------------------------------------------------------------- 1 | var a=null; 2 | PR.registerLangHandler(PR.createSimpleLexer([["pun",/^[:>?|]+/,a,":|>?"],["dec",/^%(?:YAML|TAG)[^\n\r#]+/,a,"%"],["typ",/^&\S+/,a,"&"],["typ",/^!\S*/,a,"!"],["str",/^"(?:[^"\\]|\\.)*(?:"|$)/,a,'"'],["str",/^'(?:[^']|'')*(?:'|$)/,a,"'"],["com",/^#[^\n\r]*/,a,"#"],["pln",/^\s+/,a," \t\r\n"]],[["dec",/^(?:---|\.\.\.)(?:[\n\r]|$)/],["pun",/^-/],["kwd",/^\w+:[\n\r ]/],["pln",/^\w+/]]),["yaml","yml"]); 3 | -------------------------------------------------------------------------------- /features/sint_multirun_support.feature: -------------------------------------------------------------------------------- 1 | Feature: Support Multiple Concurrent Runs in Sinan 2 | In order to make it easier to do test sinan 3 | As a Sinan Developer 4 | I want to be able to run multiple instances of sinan concurrently 5 | 6 | Scenario: Run sinan twice correctly 7 | Given two generated projects 8 | When a build step is run on each project concurrently 9 | Then sinan should build both projects without a problem 10 | -------------------------------------------------------------------------------- /ebin/sinan.app: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 75; comment-column: 50; -*- 2 | 3 | {application, sinan, 4 | [{description, "Build system for erlang"}, 5 | {vsn, "4.1.1"}, 6 | {modules, []}, 7 | {registered, [sin_sup]}, 8 | {applications, [kernel, stdlib, compiler, erlware_commons, 9 | edoc, syntax_tools, eunit, proper, tools, 10 | xmerl, mnesia, erlware_commons, 11 | cucumberl, joxa, 12 | parsetools, getopt]}]}. 13 | -------------------------------------------------------------------------------- /features/sint_remove_helper.feature: -------------------------------------------------------------------------------- 1 | Feature: Remove no longer useful helpers 2 | In order to make sinan code less redundant 3 | As an Sinan Developer 4 | I want sinan do not want sinan to generate the erlware helper and bin script 5 | 6 | Scenario: Have sinan not generate the erlware helper and bin script 7 | Given an empty directory 8 | When a project is generated 9 | Then sinan should not generate the erlware helper 10 | And should not generate the exe script 11 | 12 | 13 | -------------------------------------------------------------------------------- /features/sint_default_help.feature: -------------------------------------------------------------------------------- 1 | Feature: Make sinan more approachable 2 | In order to make sinan much easier to start using 3 | As an Erlang Developer 4 | I want to make sinan help the default command instead of sinan build 5 | 6 | Scenario: Have sinan display help if no command is given 7 | Given a generated project 8 | When a step is run on the project with an empty arglist 9 | Then sinan should run normally 10 | And display a list of commands and help usage 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /features/sint_sinan_as_escript.feature: -------------------------------------------------------------------------------- 1 | Feature: Make sinan distributable as an escript 2 | In order to make sinan distributable as an escript 3 | As an Erlang Developer 4 | I want sinan to not rely on anything in its priv directory 5 | 6 | Scenario: Have sinan gen run completely without relying on the priv dir 7 | Given a project generated by gen 8 | And sinan has nothing in its priv dir 9 | When a build step is run on this project 10 | Then sinan should build the app normally 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /features/sint_programatic_gen.feature: -------------------------------------------------------------------------------- 1 | Feature: Make the gen task of sinan more scriptable 2 | In order to make it easier to test sinan from sinan 3 | As an Erlang Developer 4 | I want sinan to be able to run a project gen task pragmatically, 5 | from erlang, without user input. 6 | 7 | Scenario: Run gen without user input 8 | Given an empty temp directory with no project 9 | When gen is called pragmatically with out available user input 10 | And a build is run 11 | Then sinan should build the project normally 12 | -------------------------------------------------------------------------------- /features/sint_sig_save.feature: -------------------------------------------------------------------------------- 1 | Feature: Make sinan build faster 2 | In order to make sinan build faster and to reduce file churn 3 | As an Erlang Developer 4 | I want sinan to only save its durable state when sinan terminates 5 | 6 | Scenario: Have sinan save its durable state only on termination 7 | Given a generated project 8 | When a build step is run on this project 9 | Then sinan should build the app normally 10 | And not save intermediate state 11 | And save state to a single file on close 12 | 13 | 14 | -------------------------------------------------------------------------------- /features/sint_module_dependencies.feature: -------------------------------------------------------------------------------- 1 | Feature: Correctly support module dependencies during build 2 | In order to correctly build complex projects 3 | As an Erlang Developer 4 | I want sinan to build all file dependencies 5 | before building the file that depends on them 6 | 7 | Scenario: Run sinan on dependent files first 8 | Given an erlang project that contains a behaviour 9 | And a module the depends on that behaviour 10 | When a build step is run on the project 11 | Then sinan should build the project correctly 12 | -------------------------------------------------------------------------------- /sinan.config: -------------------------------------------------------------------------------- 1 | {project_name, sinan}. 2 | {project_vsn, "4.1.1"}. 3 | 4 | {escript, [{include_apps, 5 | [erlware_commons, cucumberl, proper, 6 | getopt, joxa]}]}. 7 | 8 | {dep_constraints, 9 | [{cucumberl, "0.0.4", gte}, 10 | {erlware_commons, "0.6.0", gte}, 11 | {getopt, "0.0.1", gte}, 12 | {joxa, "0.0.7a", gte}]}. 13 | 14 | {dialyzer_ignore, [erlware_commons]}. 15 | 16 | {ignore_dirs, ["_", 17 | ".", 18 | "debian", 19 | "smoketests", 20 | "features"]}. 21 | -------------------------------------------------------------------------------- /features/sint_testability.feature: -------------------------------------------------------------------------------- 1 | Feature: Make sinan more testable 2 | In order to make it easier to test sinan from sinan 3 | As an Erlang Developer 4 | I want sinan to be able to pass the start directory to a sinan 5 | call instead of having it inferred. 6 | 7 | Scenario: Pass the start dir to a sinan project 8 | Given a generated project in a different location then the CWD 9 | When a build step is run on this project 10 | And a start dir is passed to the build 11 | Then sinan should build the project in the location specified by the start dir 12 | -------------------------------------------------------------------------------- /features/sint_gh78.feature: -------------------------------------------------------------------------------- 1 | Feature: Include header files within the same app 2 | For modules within the same app includes via -include("X.hrl"). should work. 3 | (https://github.com/erlware/sinan/issues/78) 4 | 5 | Scenario: Build a syntem that 'includes' (not include_lib) from an include directory in the same app 6 | Given a generated project 7 | And that project an includes a header in the include directory 8 | And that project contains an erl file that includes that header 9 | When a build step is run on this project 10 | Then build the app normally 11 | -------------------------------------------------------------------------------- /doc/assets/javascript/lang-hs.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t-\r ]+/,null,"\t\n \r "],["str",/^"(?:[^\n\f\r"\\]|\\[\S\s])*(?:"|$)/,null,'"'],["str",/^'(?:[^\n\f\r'\\]|\\[^&])'?/,null,"'"],["lit",/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)/i,null,"0123456789"]],[["com",/^(?:--+[^\n\f\r]*|{-(?:[^-]|-+[^}-])*-})/],["kwd",/^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^\d'A-Za-z]|$)/, 2 | null],["pln",/^(?:[A-Z][\w']*\.)*[A-Za-z][\w']*/],["pun",/^[^\d\t-\r "'A-Za-z]+/]]),["hs"]); 3 | -------------------------------------------------------------------------------- /features/sint_gen_app_src.feature: -------------------------------------------------------------------------------- 1 | Feature: sinan gen should support generating *.app.src instead of ebin/app 2 | In order to make a sinan generated project 'correct' 3 | As an Erlang Developer 4 | I want sinan to generate application metadata into src/.app.src 5 | instead of ebin/.app 6 | 7 | Scenario: Generate app.src into the src directory 8 | Given an empty temp directory with no project 9 | When the sinan gen task is called 10 | And a build is run 11 | Then sinan should generate an app.src into the src directory 12 | And build the project normally 13 | -------------------------------------------------------------------------------- /smoketests/tests/vsn_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sin_testing as st 3 | 4 | class TestVsn(st.SmokeTest): 5 | 6 | def test_vsn(self): 7 | self.do_run(st.AppDesc(user_name = "Smoke Test User", 8 | email = "noreply@erlware.org", 9 | copyright_holder = "Smoke Test Copy, LLC.", 10 | project_name = "smprj", 11 | project_version = "0.21.0.0", 12 | app_names = ["app1", "app2", "app3"])) 13 | 14 | if __name__ == '__main__': 15 | unittest.main() 16 | -------------------------------------------------------------------------------- /smoketests/tests/alpha_vsn_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sin_testing as st 3 | 4 | class TestAlphaVsn(st.SmokeTest): 5 | 6 | def test_alpha_vsn(self): 7 | self.do_run(st.AppDesc(user_name = "Smoke Test User", 8 | email = "noreply@erlware.org", 9 | copyright_holder = "Smoke Test Copy, LLC.", 10 | project_name = "smprj_version1", 11 | project_version = "V1922A", 12 | app_names = ["app1"])) 13 | 14 | if __name__ == '__main__': 15 | unittest.main() 16 | -------------------------------------------------------------------------------- /smoketests/tests/erts_vsn_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sin_testing as st 3 | 4 | class TestErtsVsn(st.SmokeTest): 5 | 6 | def test_erts_vsn(self): 7 | self.do_run(st.AppDesc(user_name = "Smoke Test User", 8 | email = "noreply@erlware.org", 9 | copyright_holder = "Smoke Test Copy, LLC.", 10 | project_name = "smprj_version2", 11 | project_version = "R12B3-122", 12 | app_names = ["app1", "fubyap1"])) 13 | 14 | if __name__ == '__main__': 15 | unittest.main() 16 | -------------------------------------------------------------------------------- /smoketests/tests/fail.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sin_testing as st 3 | 4 | class TestFail(st.SmokeTest): 5 | 6 | def test_fail(self): 7 | self.do_run(st.AppDesc(user_name = "Smoke Test User", 8 | email = "noreply@erlware.org", 9 | copyright_holder = "Smoke Test Copy, LLC.", 10 | project_name = "reltest", 11 | project_version = "0.134.0.0", 12 | app_names = ["app1", "app2", "app3", "app3", "app4"])) 13 | 14 | 15 | 16 | if __name__ == '__main__': 17 | unittest.main() 18 | -------------------------------------------------------------------------------- /features/sint_gh50.feature: -------------------------------------------------------------------------------- 1 | Feature: Sinan does not correctly parse_transform dependencies 2 | In order to ensure sinan correctly builds projects with parse transforms 3 | As an Erlang Developer 4 | I want to sinan to be able to be able to detect the existance of a parse transform 5 | and build the modules in the correct order 6 | 7 | Scenario: Build a parse_transform included project in the correct order 8 | Given a generated project 9 | And that project contains a parse transform 10 | And that project contains a module that depends on that parse transform 11 | When a build step is run on this project 12 | Then build the app normally 13 | -------------------------------------------------------------------------------- /doc/assets/css/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | # Sample debian/rules that uses debhelper. 4 | # This file was originally written by Joey Hess and Craig Small. 5 | # As a special exception, when this file is copied by dh-make into a 6 | # dh-make output file, you may use that output file without restriction. 7 | # This special exception was added by Craig Small in version 0.37 of dh-make. 8 | 9 | # Uncomment this to turn on verbose mode. 10 | #export DH_VERBOSE=1 11 | 12 | %: 13 | dh $@ 14 | 15 | override_dh_auto_build: 16 | $(MAKE) build 17 | $(MAKE) escript 18 | 19 | override_dh_auto_install: 20 | $(MAKE) install-deb DESTDIR=$$(pwd) 21 | 22 | override_dh_auto_test: 23 | $(MAKE) testall -------------------------------------------------------------------------------- /doc/assets/javascript/lang-lisp.js: -------------------------------------------------------------------------------- 1 | var a=null; 2 | PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(+/,a,"("],["clo",/^\)+/,a,")"],["com",/^;[^\n\r]*/,a,";"],["pln",/^[\t\n\r \xa0]+/,a,"\t\n\r \xa0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,a,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/,a], 3 | ["lit",/^[+-]?(?:[#0]x[\da-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[de][+-]?\d+)?)/i],["lit",/^'(?:-*(?:\w|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?)?/],["pln",/^-*(?:[_a-z]|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?/i],["pun",/^[^\w\t\n\r "'-);\\\xa0]+/]]),["cl","el","lisp","scm"]); 4 | -------------------------------------------------------------------------------- /test/sint_cuke_support_funs.erl: -------------------------------------------------------------------------------- 1 | -module(sint_cuke_support_funs). 2 | 3 | -export([app_src/1, delete_if_exists/1]). 4 | 5 | app_src(Name) -> 6 | ["%% This is the application resource file (.app file) for the app2,\n" 7 | "%% application.\n" 8 | "{application, ", Name, ",\n" 9 | "[{description, \"Your Desc HERE\"},\n" 10 | " {vsn, \"0.1.0\"},\n" 11 | " {modules, []},\n" 12 | " {registered,[", Name, "_sup]},\n" 13 | " {applications, [kernel, stdlib]},\n" 14 | " {mod, {", Name, "_app,[]}}, \n" 15 | " {start_phases, []}]}.\n"]. 16 | 17 | delete_if_exists(Path) -> 18 | case file:delete(Path) of 19 | ok -> 20 | ok; 21 | {error, enoent} -> 22 | ok; 23 | Error -> 24 | throw(Error) 25 | end. 26 | -------------------------------------------------------------------------------- /features/sint_app_src_mod_list.feature: -------------------------------------------------------------------------------- 1 | Feature: Support automatically populating module list in app 2 | In order to reduce the amount of redundant information in a project 3 | As an Erlang Developer 4 | I want to sinan to be able to automatically populate the modules part 5 | of the app metadata 6 | 7 | Scenario: Have an app.src automatically populated 8 | Given a generated project that contains an app.src 9 | When a build step is run on this project 10 | Then build the app normally 11 | And sinan should put the populate the module list 12 | 13 | Scenario: Have an ebin/app automatically populated 14 | Given a generated project that contains an ebin/app 15 | When a build step is run on this project 16 | Then build the app normally 17 | And sinan should put the populate the module list 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/sin_test_resolver.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Eric Merritt 3 | %%% @copyright (C) 2011, Erlware, LLC 4 | %%% @doc 5 | %%% A resolver used only for testing 6 | %%% @end 7 | %%% Created : 17 Sep 2011 by Eric Merritt <> 8 | %%%------------------------------------------------------------------- 9 | -module(sin_test_resolver). 10 | 11 | -behaviour(sin_dep_resolver). 12 | 13 | -export([new/2, app_dependencies/3, app_versions/2, resolve/3]). 14 | 15 | new(Config, _State) -> 16 | Config. 17 | 18 | app_dependencies(Config, App, Ver) -> 19 | {Config,Config:match({dependencies, App, Ver})}. 20 | 21 | app_versions(Config, App) -> 22 | {Config, Config:match({versions, App})}. 23 | 24 | %% Not used for testing 25 | resolve(Config, _App, _Version) -> 26 | {Config, ""}. 27 | -------------------------------------------------------------------------------- /doc/assets/javascript/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /features/sint_escript_exe.feature: -------------------------------------------------------------------------------- 1 | Feature: Support generating an escript from a project 2 | In order to make it easier to distribute certain kinds of projects 3 | As an Erlang Developer 4 | I want to be able to generate an escript from my project 5 | 6 | Scenario: Generate a complex escript 7 | Given a generated project 8 | And a project name named module with a main function 9 | When an escript step is run on this project 10 | Then build the project normally 11 | And produce an executable escript 12 | 13 | Scenario: Generate a complex escript with script 14 | Given a generated project 15 | And a escript directive in the build config 16 | And a escript file that should become the base 17 | When an escript step is run on this project 18 | Then build the project normally 19 | And produce an executable escript 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /smoketests/tests/xref_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import pexpect 3 | import os 4 | import sin_testing as st 5 | 6 | class TestXref(st.SmokeTest): 7 | 8 | @st.sinan("xref") 9 | def do_xref(self, child): 10 | child.expect("Calls to Deprecated Functions") 11 | child.expect(pexpect.EOF) 12 | 13 | def test_xref(self): 14 | app_desc = st.AppDesc(user_name = "Smoke Test User", 15 | email = "noreply@erlware.org", 16 | copyright_holder = "Smoke Test Copy, LLC.", 17 | project_name = "smprj", 18 | project_version = "0.21.0.0", 19 | app_names = ["app1", "app2", "app3"]) 20 | 21 | 22 | self.do_run(app_desc) 23 | builddir = os.path.join("_build", app_desc.project_name) 24 | 25 | self.do_xref() 26 | 27 | 28 | 29 | if __name__ == '__main__': 30 | unittest.main() 31 | -------------------------------------------------------------------------------- /features/sint_app_src.feature: -------------------------------------------------------------------------------- 1 | Feature: Support app.src files in the src directory 2 | In order to more obvious what are actually source files (any kind of source file) 3 | As an Erlang Developer 4 | I want to be able to put an .app.src file in my src directory 5 | and have it be copied into the correct place 6 | 7 | Scenario: Have an app.src handled correctly when no ebin exists 8 | Given a generated project that contains an app.src 9 | And does not contain an ebin/app 10 | When a build step is run on this project 11 | Then sinan should put the app file in ebin/.app 12 | And build the app normally 13 | 14 | Scenario: Have an ebin/app handled correctly when no app.src exists 15 | Given a generated project that contains an ebin/app 16 | And does not contain an app.src 17 | When a build step is run on this project 18 | Then sinan should put the app file in ebin/.app 19 | And build the app normally 20 | 21 | 22 | -------------------------------------------------------------------------------- /smoketests/tests/echo_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import pexpect 3 | import sin_testing as st 4 | import os 5 | 6 | class TestEcho(st.SmokeTest): 7 | 8 | @st.sinan("echo paths includes") 9 | def do_echo(self, child): 10 | child.expect("-pa") 11 | child.expect("-I") 12 | child.expect(pexpect.EOF) 13 | 14 | 15 | def test_echo(self): 16 | app_desc = st.AppDesc(user_name = "Smoke Test User", 17 | email = "noreply@erlware.org", 18 | copyright_holder = "Smoke Test Copy, LLC.", 19 | project_name = "smprj", 20 | project_version = "0.21.0.0", 21 | app_names = ["app1", "app2", "app3"]) 22 | 23 | 24 | self.do_run(app_desc) 25 | builddir = os.path.join("_build", app_desc.project_name) 26 | 27 | self.do_echo() 28 | 29 | 30 | if __name__ == '__main__': 31 | unittest.main() 32 | -------------------------------------------------------------------------------- /features/sint_configurable_dependencies.feature: -------------------------------------------------------------------------------- 1 | Feature: Support dependency configuration 2 | In order to allow the erlang user more control over individual dependencies 3 | As a Sinan User 4 | I want to be able to put a set of version specifications in my sinan.config 5 | and have sinan use those configurations to drive the dependencies 6 | 7 | Scenario: Have sinan parse dependencies specifications 8 | Given a generated project that contains a dependency spec 9 | When a build step is run on this project 10 | Then sinan builds the app normally 11 | And correctly figures out the constrained dependencies 12 | 13 | Scenario: Have sinan parse dependencies specifications, for releases 14 | Given a generated project that contains a dependency spec 15 | And has multiple releases 16 | When a build step is run on this project on each release 17 | Then sinan builds the app normally each time 18 | And correctly figures out the constrained dependencies for each release 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /features/sint_cucumber_support.feature: -------------------------------------------------------------------------------- 1 | Feature: Support BDD with sinan 2 | In order to make it easier to do behaviour driven development in sinan 3 | As an Erlang Developer 4 | I want to be able to put features in a features directory in the 5 | project root and have those features be run by cumberl when I run a 6 | "cucumber" command. 7 | 8 | Scenario: Run cucumber on passing tests 9 | Given a generated project 10 | And a feature file in the features directory of that project 11 | And an implementation of that feature that passes 12 | When a cucumber step is run on this project 13 | Then then sinan should run cucumberl on the features in the features directory 14 | And report the build as passing 15 | 16 | Scenario: Run cucumber on failing tests 17 | Given a generated project 18 | And a feature file in the features directory of that project 19 | And an implementation of that feature that fails 20 | When a cucumber step is run on this project 21 | 22 | And report the build as failing 23 | -------------------------------------------------------------------------------- /doc/assets/css/skeleton/javascripts/tabs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V1.1 3 | * Copyright 2011, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 8/17/2011 8 | */ 9 | 10 | 11 | $(document).ready(function() { 12 | 13 | /* Tabs Activiation 14 | ================================================== */ 15 | 16 | var tabs = $('ul.tabs'); 17 | 18 | tabs.each(function(i) { 19 | 20 | //Get all tabs 21 | var tab = $(this).find('> li > a'); 22 | tab.click(function(e) { 23 | 24 | //Get Location of tab's content 25 | var contentLocation = $(this).attr('href'); 26 | 27 | //Let go if not a hashed one 28 | if(contentLocation.charAt(0)=="#") { 29 | 30 | e.preventDefault(); 31 | 32 | //Make Tab Active 33 | tab.removeClass('active'); 34 | $(this).addClass('active'); 35 | 36 | //Show Tab Content & add active class 37 | $(contentLocation).show().addClass('active').siblings().hide().removeClass('active'); 38 | 39 | } 40 | }); 41 | }); 42 | }); -------------------------------------------------------------------------------- /test/sint_default_help.erl: -------------------------------------------------------------------------------- 1 | -module(sint_default_help). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | -export([given/3, 'when'/3, then/3]). 6 | 7 | given([a,generated,project], _State, _) -> 8 | sint_test_project_gen:a_generated_project(). 9 | 10 | 'when'([a, step, is, run, on, the, 11 | project, with, an, empty, arglist], {ProjectDir, ProjectName}, _) -> 12 | Ret = sinan:run_sinan(["-s", ProjectDir]), 13 | ?assertMatch({_, _}, Ret), 14 | {ok, {ProjectDir, ProjectName, Ret}}. 15 | 16 | then([sinan, should, run, normally], 17 | {ProjectDir, ProjectName, Ret}, _) -> 18 | ?assertMatch({ok, _}, Ret), 19 | {ok, BuildState} = Ret, 20 | ?assertMatch([], sin_state:get_run_errors(BuildState)), 21 | {ok, {ProjectDir, ProjectName, BuildState}}; 22 | then([display, a, list, 'of', 23 | commands, 'and', help, usage], 24 | State = {_, _, BuildState}, _) -> 25 | DisplayedCommands = sin_state:get_value(help_displayed, BuildState), 26 | ?assertMatch({command_list, _}, DisplayedCommands), 27 | {ok, State}. 28 | -------------------------------------------------------------------------------- /test/sint_sinan_as_escript.erl: -------------------------------------------------------------------------------- 1 | -module(sint_sinan_as_escript). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | -export([given/3, 'when'/3, then/3]). 6 | 7 | given([sinan, has, nothing, in, its, priv, dir], State, _) -> 8 | %% The current directory should be the sinan project root 9 | ?assertMatch(false, sin_utils:file_exists(sin_state:new(), "./priv")), 10 | {ok, State}; 11 | given([a, project, generated, by, gen], _State, _) -> 12 | sint_test_project_gen:a_generated_project(). 13 | 14 | 'when'([a,build,step,is,run,on,this,project], 15 | {ProjectDir, ProjectName}, _) -> 16 | Ret = sinan:run_sinan(["-s", ProjectDir, "build"]), 17 | ?assertMatch({_, _}, Ret), 18 | {ok, {ProjectDir, ProjectName, Ret}}. 19 | 20 | then([sinan,should,build,the,app,normally], 21 | {ProjectDir, ProjectName, Ret}, _) -> 22 | ?assertMatch({ok, _}, Ret), 23 | {ok, BuildState} = Ret, 24 | ?assertMatch([], sin_state:get_run_errors(BuildState)), 25 | sint_test_project_gen:validate_single_app_project(ProjectDir, ProjectName), 26 | {ok, {ProjectDir, ProjectName, BuildState}}. 27 | -------------------------------------------------------------------------------- /features/sint_gh52.feature: -------------------------------------------------------------------------------- 1 | Feature: Sinan should all configuration at the $HOME dir level 2 | In order to allow a developer to set cross project defaults (and defaults not in the project config) 3 | As an Erlang Developer 4 | I want to sinan to be able to look in the home directory for a config that provides defaults 5 | 6 | Scenario: Allow setting of the home directory 7 | Given a generated project 8 | And a non-standard home directory 9 | When a build step is run on this project 10 | And a home directory is specified on the command line 11 | Then build the app normally 12 | And have the home directory correctly specified in the build state 13 | 14 | Scenario: Allow config in the home directory 15 | Given a generated project 16 | And a non-standard home directory 17 | And a sinan config in that home directory 18 | When a build step is run on this project 19 | And a home directory is specified on the command line 20 | Then sinan uses the config in the alternate home directory 21 | And builds the app normally 22 | And have the home directory correctly specified in the build state 23 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2010 Erlware 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit 9 | persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall 13 | be included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /test/sint_remove_helper.erl: -------------------------------------------------------------------------------- 1 | -module(sint_remove_helper). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | -export([given/3, 'when'/3, then/3]). 6 | 7 | given([an, empty, directory], _State, _) -> 8 | BaseDir = ec_file:mkdtemp(), 9 | {ok, BaseDir}. 10 | 11 | 'when'([a, project, is, generated], BaseDir, _) -> 12 | ProjectName = "super_foo", 13 | {ProjectDir, _} = 14 | sint_test_project_gen:single_app_project(BaseDir, ProjectName), 15 | {ok, {ProjectDir, ProjectName}}. 16 | 17 | then([should, 'not', generate, the, exe, script], 18 | State = {ProjectDir, _}, _) -> 19 | HelperFile = filename:join([ProjectDir, "bin", 20 | "erlware_release_start_helper"]), 21 | ?assertMatch(false, 22 | sin_utils:file_exists(sin_state:new(), HelperFile)), 23 | {ok, State}; 24 | then([sinan, should, 'not', generate, the, erlware,helper], 25 | State = {ProjectDir, ProjectName}, _) -> 26 | BinFile = filename:join([ProjectDir, "bin", 27 | ProjectName]), 28 | ?assertMatch(false, 29 | sin_utils:file_exists(sin_state:new(), BinFile)), 30 | {ok, State}. 31 | -------------------------------------------------------------------------------- /test/sint_sig_save.erl: -------------------------------------------------------------------------------- 1 | -module(sint_sig_save). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | -export([given/3, 'when'/3, then/3]). 6 | 7 | given([a,generated,project], _State, _) -> 8 | sint_test_project_gen:a_generated_project(). 9 | 10 | 'when'([a,build,step,is,run,on,this,project], 11 | {ProjectDir, ProjectName}, _) -> 12 | Ret = sinan:run_sinan(["-s", ProjectDir, "build"]), 13 | ?assertMatch({_, _}, Ret), 14 | {ok, {ProjectDir, ProjectName, Ret}}. 15 | 16 | then([sinan, should, build, the, app, normally], 17 | {ProjectDir, ProjectName, Ret}, _) -> 18 | ?assertMatch({ok, _}, Ret), 19 | {ok, BuildState} = Ret, 20 | ?assertMatch([], sin_state:get_run_errors(BuildState)), 21 | {ok, {ProjectDir, ProjectName, BuildState}}; 22 | then(['not',save, intermediate, state], State, _) -> 23 | %% There isnt really any way to test this 24 | {ok, State}; 25 | then([save,state,to,a,single,file,on,close], 26 | State = {_, _, BuildState}, _) -> 27 | %% We can check for a .sig file in the build dir 28 | BuildDir = sin_state:get_value(build_dir, BuildState), 29 | SigFile = filename:join(BuildDir, ".sig"), 30 | ?assertMatch(true, sin_utils:file_exists(sin_state:new(), SigFile)), 31 | {ok, State}. 32 | -------------------------------------------------------------------------------- /test/sint_testability.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @copyright (C) 2011, Erlware, LLC. 3 | %%% @doc 4 | %%% Test the ability to specify a start directory 5 | %%% @end 6 | %%% Created : 5 Sep 2011 by Eric Merritt 7 | %%%------------------------------------------------------------------- 8 | -module(sint_testability). 9 | 10 | -export([given/3, 'when'/3, then/3]). 11 | 12 | % Step definitions for the sample calculator Addition feature. 13 | 14 | given([a, generated, project, in, a, different, location, then, the, cwd], 15 | _, _) -> 16 | sint_test_project_gen:a_generated_project(). 17 | 18 | 'when'([a, build, step, is, run, on, this, project], 19 | Ret = {ProjectDir, _}, _) -> 20 | sinan:run_sinan(["-s", ProjectDir, "build"]), 21 | {ok, Ret}; 22 | 'when'([a, start, dir, is, passed, to, the, build], Ret, _) -> 23 | %% Nothing really to be done here since the start dir is passed when 24 | %% The build is run. 25 | {ok, Ret}. 26 | 27 | then([sinan, should, build, the, project, in, the, location, 28 | specified, by, the, start, dir], {ProjectDir, ProjectName}, _) -> 29 | sint_test_project_gen:validate_single_app_project(ProjectDir, ProjectName), 30 | ok. 31 | -------------------------------------------------------------------------------- /test/sint_programatic_gen.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @copyright (C) 2011, Erlware, LLC. 3 | %%% @doc 4 | %%% Test the ability to generate a project programatically 5 | %%% @end 6 | %%% Created : 5 Sep 2011 by Eric Merritt 7 | %%%------------------------------------------------------------------- 8 | -module(sint_programatic_gen). 9 | 10 | -export([given/3, 'when'/3, then/3]). 11 | 12 | % Step definitions for the sample calculator Addition feature. 13 | 14 | given([an, empty, temp, directory, with, no, project], _State, 15 | _) -> 16 | BaseDir = ec_file:mkdtemp(), 17 | {ok, BaseDir}. 18 | 19 | 'when'([gen, is, called, pragmatically, with, out, 20 | available, user, input], BaseDir, _) -> 21 | ProjectName = "super_foo", 22 | {ProjectDir, _} = 23 | sint_test_project_gen:single_app_project(BaseDir, ProjectName), 24 | {ok, {ProjectDir, ProjectName}}; 25 | 'when'([a, build, is, run], State = {ProjectDir, _ProjectName}, _) -> 26 | sinan:run_sinan(["-s", ProjectDir, "build"]), 27 | {ok, State}. 28 | 29 | then([sinan, should, build, the, project, normally], 30 | {ProjectDir, ProjectName}, _) -> 31 | sint_test_project_gen:validate_single_app_project(ProjectDir, ProjectName). 32 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://dep.debian.net/deps/dep5 2 | Upstream-Name: sinan 3 | Source: https://github.com/erlware/sinan 4 | 5 | Files: * 6 | Copyright: 2006-2012 Erlware, LLC. 7 | 8 | License: MIT 9 | Copyright (c) 2006-2012 Erlware 10 | 11 | Permission is hereby granted, free of charge, to any 12 | person obtaining a copy of this software and associated 13 | documentation files (the "Software"), to deal in the 14 | Software without restriction, including without limitation 15 | the rights to use, copy, modify, merge, publish, distribute, 16 | sublicense, and/or sell copies of the Software, and to permit 17 | persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall 21 | be included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 25 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 27 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 28 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 30 | OTHER DEALINGS IN THE SOFTWARE. 31 | 32 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | erlang-sinan (3.0.1a) oneiric; urgency=low 2 | 3 | * New release. 4 | 5 | -- Eric B Merritt Fri, 23 Mar 2012 18:44:00 -0600 6 | 7 | erlang-sinan (3.0.0a) oneiric; urgency=low 8 | 9 | * New release. 10 | 11 | -- Eric B Merritt Fri, 2 Mar 2012 14:54:00 -0600 12 | 13 | erlang-sinan (2.1.3) oneiric; urgency=low 14 | 15 | * New release. 16 | 17 | -- Eric B Merritt Thu, 27 Feb 2012 16:11:00 -0600 18 | 19 | erlang-sinan (2.1.2-afiniate1) oneiric; urgency=low 20 | 21 | * New release. 22 | 23 | -- Eric B Merritt Thu, 23 Feb 2012 11:29:03 -0600 24 | 25 | erlang-sinan (2.1.1-afiniate1) oneiric; urgency=low 26 | 27 | * New upstream release. 28 | 29 | -- Eric B Merritt Thu, 23 Feb 2012 11:29:03 -0600 30 | 31 | erlang-sinan (2.1.0-afiniate1) oneiric; urgency=low 32 | 33 | * New upstream release. 34 | 35 | -- Eric B Merritt Wed, 22 Feb 2012 11:44:40 -0600 36 | 37 | erlang-sinan (2.0.29a-afiniate1) oneiric; urgency=low 38 | 39 | * New upstream release. 40 | 41 | -- Eric B Merritt Wed, 22 Feb 2012 10:27:24 -0600 42 | 43 | erlang-sinan (2.0.28a-afiniate1) oneiric; urgency=low 44 | 45 | * New upstream release. 46 | 47 | -- Eric B Merritt Fri, 20 Feb 2012 21:14:48 -0600 48 | -------------------------------------------------------------------------------- /doc/assets/javascript/lang-n.js: -------------------------------------------------------------------------------- 1 | var a=null; 2 | PR.registerLangHandler(PR.createSimpleLexer([["str",/^(?:'(?:[^\n\r'\\]|\\.)*'|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,a,'"'],["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,a,"#"],["pln",/^\s+/,a," \r\n\t\xa0"]],[["str",/^@"(?:[^"]|"")*(?:"|$)/,a],["str",/^<#[^#>]*(?:#>|$)/,a],["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,a],["com",/^\/\/[^\n\r]*/,a],["com",/^\/\*[\S\s]*?(?:\*\/|$)/, 3 | a],["kwd",/^(?:abstract|and|as|base|catch|class|def|delegate|enum|event|extern|false|finally|fun|implements|interface|internal|is|macro|match|matches|module|mutable|namespace|new|null|out|override|params|partial|private|protected|public|ref|sealed|static|struct|syntax|this|throw|true|try|type|typeof|using|variant|virtual|volatile|when|where|with|assert|assert2|async|break|checked|continue|do|else|ensures|for|foreach|if|late|lock|new|nolate|otherwise|regexp|repeat|requires|return|surroundwith|unchecked|unless|using|while|yield)\b/, 4 | a],["typ",/^(?:array|bool|byte|char|decimal|double|float|int|list|long|object|sbyte|short|string|ulong|uint|ufloat|ulong|ushort|void)\b/,a],["lit",/^@[$_a-z][\w$@]*/i,a],["typ",/^@[A-Z]+[a-z][\w$@]*/,a],["pln",/^'?[$_a-z][\w$@]*/i,a],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,a,"0123456789"],["pun",/^.[^\s\w"-$'./@`]*/,a]]),["n","nemerle"]); 5 | -------------------------------------------------------------------------------- /src/sin_exceptions.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @author Eric Merritt 4 | %%% @doc 5 | %%% A utility to help format sinan exceptions 6 | %%% @end 7 | %%% @copyright 2011 Erlware 8 | %%%--------------------------------------------------------------------------- 9 | -module(sin_exceptions). 10 | 11 | %% API 12 | -export([format_exception/1]). 13 | -export_type([reason/0, exception/0]). 14 | 15 | 16 | %%==================================================================== 17 | %% Public Types 18 | %%==================================================================== 19 | 20 | -type reason() :: {atom(), string()} | atom(). 21 | -type exception() :: {module(), non_neg_integer(), reason()}. 22 | 23 | %%==================================================================== 24 | %% API 25 | %%==================================================================== 26 | 27 | %% @doc a helper function to format sinan formated exceptions 28 | -spec format_exception(exception()) -> string(). 29 | format_exception({pe, _, {Module, Line, {Reason, Description}}}) 30 | when is_list(Reason) -> 31 | io_lib:format("~s:~p [~p] ~s", [Module, Line, Reason, 32 | lists:flatten(Description)]); 33 | format_exception({pe, _, {Module, Line, Reason}}) -> 34 | io_lib:format("~s:~p [~p]", [Module, Line, Reason]). 35 | -------------------------------------------------------------------------------- /doc/assets/javascript/lang-clj.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2011 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | var a=null; 17 | PR.registerLangHandler(PR.createSimpleLexer([["opn",/^[([{]+/,a,"([{"],["clo",/^[)\]}]+/,a,")]}"],["com",/^;[^\n\r]*/,a,";"],["pln",/^[\t\n\r \xa0]+/,a,"\t\n\r \xa0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,a,'"']],[["kwd",/^(?:def|if|do|let|quote|var|fn|loop|recur|throw|try|monitor-enter|monitor-exit|defmacro|defn|defn-|macroexpand|macroexpand-1|for|doseq|dosync|dotimes|and|or|when|not|assert|doto|proxy|defstruct|first|rest|cons|defprotocol|deftype|defrecord|reify|defmulti|defmethod|meta|with-meta|ns|in-ns|create-ns|import|intern|refer|alias|namespace|resolve|ref|deref|refset|new|set!|memfn|to-array|into-array|aset|gen-class|reduce|map|filter|find|nil?|empty?|hash-map|hash-set|vec|vector|seq|flatten|reverse|assoc|dissoc|list|list?|disj|get|union|difference|intersection|extend|extend-type|extend-protocol|prn)\b/,a], 18 | ["typ",/^:[\dA-Za-z-]+/]]),["clj"]); 19 | -------------------------------------------------------------------------------- /src/sin_jxa_info.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @copyright Erlware, LLC 4 | %%% @author Eric Merritt 5 | %%% @end 6 | %%%------------------------------------------------------------------- 7 | -module(sin_jxa_info). 8 | 9 | %% API 10 | -export([process_file/3, format_exception/1]). 11 | 12 | -include_lib("sinan/include/sinan.hrl"). 13 | 14 | %%==================================================================== 15 | %% API 16 | %%==================================================================== 17 | -spec process_file(sin_state:state(), string(), [string()]) -> 18 | sinan:mod(). 19 | process_file(State0, Path0, _Includes) -> 20 | case joxa.compiler:info(Path0, []) of 21 | {ModName, Deps} when is_list(Deps) -> 22 | Mod0 = sin_file_info:initialize(Path0, jxa), 23 | Mod1 = lists:foldl(fun(DepModule, 24 | Mod1=#module{module_deps=Modules}) -> 25 | Mod1#module{module_deps=sets:add_element(DepModule, Modules)} 26 | end, 27 | Mod0#module{name=ModName}, 28 | Deps), 29 | {State0, Mod1, erlang:phash2(file:read_file(Path0))}; 30 | Error -> 31 | ?SIN_RAISE(State0, {unable_to_parse_file, Path0, Error}) 32 | end. 33 | 34 | format_exception(Exception) -> 35 | sin_exceptions:format_exception(Exception). 36 | 37 | -------------------------------------------------------------------------------- /smoketests/tests/gen_single_app_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sin_testing as st 3 | import pexpect 4 | 5 | class TestFail(st.SmokeTest): 6 | 7 | @st.sinan("gen foo") 8 | def run_custom_gen(self, child, appdesc): 9 | child.expect("your name> ") 10 | child.sendline(appdesc.user_name) 11 | child.expect("your email> ") 12 | child.sendline(appdesc.email) 13 | child.expect('copyright holder \("%s"\)> ' % appdesc.user_name) 14 | child.sendline() 15 | child.expect('project version> ') 16 | child.sendline(appdesc.project_version) 17 | child.expect('Please specify the ERTS version \(".*"\)> ') 18 | child.sendline() 19 | child.expect('Is this a single application project \("n"\)> ') 20 | child.sendline("y") 21 | child.expect('Would you like a build config\? \("y"\)> ') 22 | child.sendline() 23 | child.expect("Project was created, you should be good to go!") 24 | child.expect(pexpect.EOF) 25 | 26 | 27 | def test_gen_name(self): 28 | appdesc = st.AppDesc(user_name = "Smoke Test Gen", 29 | email = "noreply@erlware.org", 30 | copyright_holder = "Smoke Test Copy, LLC.", 31 | # This needs to match the gen name since 32 | # we are overriding it 33 | project_name = "foo", 34 | project_version = "0.134.0.0") 35 | 36 | self.run_custom_gen(appdesc) 37 | 38 | 39 | if __name__ == '__main__': 40 | unittest.main() 41 | -------------------------------------------------------------------------------- /test/sint_jxa_build_support.erl: -------------------------------------------------------------------------------- 1 | -module(sint_jxa_build_support). 2 | 3 | -export([given/3, 'when'/3, then/3]). 4 | -include_lib("eunit/include/eunit.hrl"). 5 | 6 | given([a,generated,project,that,contains,joxa,modules], _State, _) -> 7 | Result = {ok, {ProjectDir, _}} = sint_test_project_gen:a_generated_project(), 8 | io:format("~s~n", [ProjectDir]), 9 | BarFile = filename:join([ProjectDir, "src", 10 | "foo", "bar.jxa"]), 11 | filelib:ensure_dir(BarFile), 12 | ok = file:write_file(BarFile, jxa_contents()), 13 | 14 | BazFile = filename:join([ProjectDir, "src", 15 | "foo", "baz.jxa"]), 16 | filelib:ensure_dir(BazFile), 17 | ok = file:write_file(BazFile, jxa_dep()), 18 | Result. 19 | 20 | 'when'([a,build,step,is,run,on,this,project], {ProjectDir, ProjectName}, _) -> 21 | Ret = sinan:run_sinan(["-s", ProjectDir, "build"]), 22 | ?assertMatch({_, _}, Ret), 23 | {ok, {ProjectDir, ProjectName, Ret}}. 24 | 25 | then([build,the,app,normally], State = {ProjectDir, ProjectName, _Ret}, _) -> 26 | code:add_patha(filename:join([ProjectDir, "_build", ProjectName, "lib", 27 | ProjectName ++ "-0.1.0", "ebin"])), 28 | code:ensure_loaded(foo.bar), 29 | code:ensure_loaded(foo.baz), 30 | ?assertMatch('test-return', foo.bar:'test-return'()), 31 | {ok, State}. 32 | 33 | jxa_dep() -> 34 | "(module foo.baz) 35 | (defn+ test-return () 36 | :test-return)". 37 | 38 | jxa_contents() -> 39 | "(module foo.bar 40 | (require foo.baz)) 41 | 42 | (defn+ test-return () 43 | (foo.baz/test-return))". 44 | -------------------------------------------------------------------------------- /src/sin_log.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 79; comment-column: 70; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @author Eric Merritt 4 | %%% @doc 5 | %%% Provides log functionality to the system so that things that need to be 6 | %%% loged can be logged get a response at the correct verbosity level 7 | %%% @end 8 | %%% @copyright Erlware 2006-2011 9 | %%%--------------------------------------------------------------------------- 10 | -module(sin_log). 11 | 12 | %% API 13 | -export([verbose/2, 14 | verbose/3, 15 | normal/2, 16 | normal/3]). 17 | 18 | 19 | %%============================================================================ 20 | %% API 21 | %%============================================================================ 22 | 23 | %% @doc Outputs the line to the screen 24 | -spec verbose(sin_config:config(), string()) -> ok. 25 | verbose(Config, Say) -> 26 | case Config:match(verbose, false) of 27 | true -> 28 | ec_talk:say(Say); 29 | false -> 30 | ok 31 | end. 32 | 33 | -spec verbose(sin_config:config(), string(), [term()] | term()) -> ok. 34 | verbose(Config, Say, Args) -> 35 | case Config:match(verbose, false) of 36 | true -> 37 | ec_talk:say(Say, Args); 38 | false -> 39 | ok 40 | end. 41 | 42 | %% @doc Outputs the line to the screen 43 | -spec normal(sin_config:config(), string()) -> ok. 44 | normal(_Config, Say) -> 45 | ec_talk:say(Say). 46 | 47 | -spec normal(sin_config:config(), string(), [term()] | term()) -> ok. 48 | normal(_Config, Say, Args) -> 49 | ec_talk:say(Say, Args). 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: erlang-sinan 2 | Section: devel 3 | Priority: extra 4 | Maintainer: Eric Merritt 5 | Build-Depends: debhelper (>= 8.0.0), 6 | rsync, 7 | erlang-base, 8 | erlang-dev, 9 | erlang-tools, 10 | erlang-eunit, 11 | erlang-edoc, 12 | erlang-syntax-tools, 13 | erlang-xmerl, 14 | erlang-parsetools, 15 | erlang-mnesia, 16 | erlang-proper, 17 | erlang-cucumberl, 18 | erlang-erlware-commons, 19 | erlang-getopt, 20 | joxa, 21 | python-pexpect 22 | Standards-Version: 3.9.2 23 | Homepage: https://github.com/erlware/sinan 24 | Vcs-Git: https://github.com/erlware/sinan.git 25 | Vcs-Browser: https://github.com/erlware/sinan 26 | 27 | Package: erlang-sinan 28 | Architecture: all 29 | Depends: ${misc:Depends}, 30 | erlang-base, 31 | erlang-dev, 32 | erlang-tools, 33 | erlang-eunit, 34 | erlang-edoc, 35 | erlang-syntax-tools, 36 | erlang-xmerl, 37 | erlang-parsetools, 38 | erlang-mnesia, 39 | erlang-proper, 40 | erlang-cucumberl, 41 | erlang-erlware-commons, 42 | joxa, 43 | erlang-getopt 44 | Description: Sinan is a build tool designed to build Erlang/OTP Projects 45 | Sinan is a build tool designed to build Erlang/OTP Projects, Releases 46 | and Applications. Sinan leverages the metadata artifacts provided by 47 | OTP to do a good job building, testing, releasing, etc with very 48 | little or no additional input from the developer. 49 | 50 | -------------------------------------------------------------------------------- /src/sin_task_erts.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @author Eric Merritt 4 | %%% @doc 5 | %%% Return the erts version sinan is running on. 6 | %%% @end 7 | %%% @copyright (C) 2008-2011 Erlware 8 | %%%--------------------------------------------------------------------------- 9 | -module(sin_task_erts). 10 | 11 | -behaviour(sin_task). 12 | 13 | -include_lib("sinan/include/sinan.hrl"). 14 | 15 | %% API 16 | -export([description/0, do_task/2]). 17 | 18 | -define(TASK, erts). 19 | -define(DEPS, []). 20 | 21 | %%==================================================================== 22 | %% API 23 | %%==================================================================== 24 | %% @doc provides a description for this task 25 | -spec description() -> sin_task:task_description(). 26 | description() -> 27 | 28 | Desc = " 29 | erts Task 30 | ========= 31 | 32 | A very simple command that prints out the current version of the 33 | Erlang Runtime System that Sinan is running on", 34 | 35 | #task{name = ?TASK, 36 | task_impl = ?MODULE, 37 | bare = true, 38 | deps = ?DEPS, 39 | example = "erts", 40 | short_desc = "Display the erts version sinan is running on", 41 | desc = Desc, 42 | opts = []}. 43 | 44 | %% @doc Get the version of sinan that is currently running 45 | -spec do_task(sin_config:config(), sin_state:state()) -> sin_state:state(). 46 | do_task(Config, State) -> 47 | sin_log:normal(Config, "erts-~s (~s)", [erlang:system_info(version), 48 | erlang:system_info(otp_release)]), 49 | State. 50 | -------------------------------------------------------------------------------- /test/sint_gh78.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @copyright (C) 2011, Erlware, LLC. 3 | %%% @doc 4 | %%% Test the ability to correctly correctly handle parse_transform 5 | %%% dependencies 6 | %%% @end 7 | %%%------------------------------------------------------------------- 8 | -module(sint_gh78). 9 | 10 | -include_lib("eunit/include/eunit.hrl"). 11 | 12 | -export([given/3, 'when'/3, then/3]). 13 | 14 | given([a,generated,project], _State, _) -> 15 | sint_test_project_gen:a_generated_project(); 16 | given([that,project,an,includes,a,header,in,the,include,directory], 17 | State={ProjectDir, _}, _) -> 18 | io:format("~p~n", [ProjectDir]), 19 | File = filename:join([ProjectDir, "include", 20 | "test.hrl"]), 21 | ok = file:write_file(File, header_contents()), 22 | {ok, State}; 23 | given([that,project,contains,an,erl,file,that,includes,that,header], 24 | State={ProjectDir, _}, _) -> 25 | AFile = filename:join([ProjectDir, "src", "sint_gh78_includer.erl"]), 26 | ok = file:write_file(AFile, dependent_contents()), 27 | {ok, State}. 28 | 29 | 'when'([a,build,step,is,run,on,this,project], 30 | {ProjectDir, ProjectName}, _) -> 31 | Ret = sinan:run_sinan(["-s", ProjectDir, "build"]), 32 | ?assertMatch({_, _}, Ret), 33 | {_, TrueRet} = Ret, 34 | {ok, {ProjectDir, ProjectName, TrueRet}}. 35 | 36 | then([build,the,app,normally], 37 | State = {_, _, BuildState}, _) -> 38 | ?assertMatch([], sin_state:get_run_errors(BuildState)), 39 | {ok, State}. 40 | 41 | header_contents() -> 42 | "-define(FOO, ok). 43 | ". 44 | 45 | dependent_contents() -> 46 | "-module(sint_gh78_includer). 47 | -include(\"test.hrl\"). 48 | -export([new/2]). 49 | new(_, _) -> ?FOO. 50 | ". 51 | -------------------------------------------------------------------------------- /test/sint_gen_app_src.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @copyright (C) 2011, Erlware, LLC. 3 | %%% @doc 4 | %%% Test the ability to generate an app.src 5 | %%% @end 6 | %%% Created : 5 Sep 2011 by Eric Merritt 7 | %%%------------------------------------------------------------------- 8 | -module(sint_gen_app_src). 9 | 10 | -include_lib("eunit/include/eunit.hrl"). 11 | 12 | -export([given/3, 'when'/3, then/3]). 13 | 14 | % Step definitions for the sample calculator Addition feature. 15 | 16 | given([an, empty, temp, directory, with, no, project], _State, 17 | _) -> 18 | BaseDir = ec_file:mkdtemp(), 19 | {ok, BaseDir}. 20 | 21 | 'when'([the, sinan, gen, task, is, called], BaseDir, _) -> 22 | ProjectName = "super_foo", 23 | {ProjectDir, _} = 24 | sint_test_project_gen:single_app_project(BaseDir, ProjectName), 25 | {ok, {ProjectDir, ProjectName}}; 26 | 'when'([a, build, is, run], 27 | {ProjectDir, ProjectName}, _) -> 28 | Ret = sinan:run_sinan(["-s", ProjectDir, "build"]), 29 | {ok, {ProjectDir, ProjectName, Ret}}. 30 | 31 | 32 | then([sinan, should, generate, an, 'app.src', 33 | into, the, src, directory], 34 | State = {ProjectDir, ProjectName, _}, _) -> 35 | Path = filename:join([ProjectDir, "src", 36 | ProjectName ++ ".app.src"]), 37 | 38 | ?assertMatch(true, 39 | sin_utils:file_exists(sin_state:new(), Path)), 40 | 41 | {ok, State}; 42 | then([build, the, project, normally], 43 | State = {ProjectDir, ProjectName, BuildState}, _) -> 44 | ?assertMatch({ok, _}, BuildState), 45 | sint_test_project_gen:validate_single_app_project(ProjectDir, 46 | ProjectName), 47 | {ok, State}. 48 | -------------------------------------------------------------------------------- /smoketests/tests/hooks_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import pexpect 3 | import os 4 | import sin_testing as st 5 | 6 | class TestHooks(st.SmokeTest): 7 | 8 | def add_hooks(self): 9 | hooks_dir = os.path.join(self.project_dir, "_hooks") 10 | os.mkdir(hooks_dir) 11 | hook_file = os.path.join(hooks_dir, "pre_build") 12 | 13 | f = open(hook_file, "w") 14 | f.write("""#!/bin/sh 15 | echo HELLO WORLD!""") 16 | f.close() 17 | os.chmod(hook_file, 0777) 18 | 19 | @st.sinan("build -v ") 20 | def do_test_build(self, child, appdesc): 21 | child.expect("HELLO WORLD!") 22 | return self.build_validate(child, appdesc) 23 | 24 | def do_run(self, appdesc): 25 | self.current_app_desc = appdesc 26 | a = self.do_gen(appdesc) 27 | 28 | self.project_dir = os.path.join(self.smokedir, a.project_name) 29 | 30 | self.add_hooks() 31 | 32 | os.chdir(os.path.join(self.project_dir)) 33 | 34 | na = self.do_test_build(appdesc) 35 | 36 | self.do_apply(["do_clean", 37 | "do_build", 38 | "do_t", 39 | "do_release", 40 | "do_dist"], na) 41 | 42 | def test_hooks(self): 43 | app_desc = st.AppDesc(user_name = "Smoke Test User", 44 | email = "noreply@erlware.org", 45 | copyright_holder = "Smoke Test Copy, LLC.", 46 | project_name = "smprj", 47 | project_version = "0.21.0.0", 48 | app_names = ["app1", "app2", "app3"]) 49 | 50 | 51 | self.do_run(app_desc) 52 | builddir = os.path.join("_build", app_desc.project_name) 53 | 54 | 55 | 56 | if __name__ == '__main__': 57 | unittest.main() 58 | -------------------------------------------------------------------------------- /doc/assets/css/skeleton/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | Your Page Title Here :) 12 | 13 | 14 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 40 |
41 |

Sorry. Couldn't Find That Page!

42 |
43 | 44 | 46 | 47 | -------------------------------------------------------------------------------- /test/sint_module_dependencies.erl: -------------------------------------------------------------------------------- 1 | -module(sint_module_dependencies). 2 | 3 | -export([given/3, 'when'/3, then/3]). 4 | 5 | -include_lib("eunit/include/eunit.hrl"). 6 | 7 | given([a,module,the,depends,on,that,behaviour], State={ProjectDir,_}, _) -> 8 | File = filename:join([ProjectDir, "src", 9 | "sin_module_dependencies_a_some_module.erl"]), 10 | ok = file:write_file(File, dependent_contents()), 11 | {ok, State}; 12 | given([an,erlang,project,that,contains,a,behaviour], _State, _) -> 13 | Result = {ok, {ProjectDir, _}} = sint_test_project_gen:a_generated_project(), 14 | File = filename:join([ProjectDir, "src", 15 | "sin_module_dependencies_z_some_module.erl"]), 16 | ok = file:write_file(File, behaviour_contents()), 17 | Result. 18 | 19 | 'when'([a,build,step,is,run,on,the,project], {ProjectDir, ProjectName}, _) -> 20 | Ret = sinan:run_sinan(["-s", ProjectDir, "build"]), 21 | ?assertMatch({_, _}, Ret), 22 | {ok, {ProjectDir, ProjectName, Ret}}. 23 | 24 | then([sinan,should,build,the,project,correctly], 25 | {ProjectDir, ProjectName, Ret}, _) -> 26 | ?assertMatch({ok, _}, Ret), 27 | {ok, BuildState} = Ret, 28 | ?assertMatch([], sin_state:get_run_warnings(BuildState)), 29 | sint_test_project_gen:validate_single_app_project(ProjectDir, ProjectName), 30 | {ok, {ProjectDir, ProjectName, BuildState}}. 31 | 32 | 33 | behaviour_contents() -> 34 | "-module(sin_module_dependencies_z_some_module). 35 | -export([behaviour_info/1]). 36 | %% @doc define the behaviour for tasks. 37 | behaviour_info(callbacks) -> 38 | [{new, 2}]; 39 | behaviour_info(_) -> 40 | undefined. 41 | ". 42 | 43 | dependent_contents() -> 44 | "-module(sin_module_dependencies_a_some_module). 45 | -behaviour(sin_module_dependencies_z_some_module). 46 | -export([new/2]). 47 | new(_, _) -> ok. 48 | ". 49 | 50 | -------------------------------------------------------------------------------- /smoketests/tests/gen_name_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sin_testing as st 3 | import pexpect 4 | 5 | class TestFail(st.SmokeTest): 6 | 7 | @st.sinan("gen foo") 8 | def run_custom_gen(self, child, appdesc): 9 | child.expect("your name> ") 10 | child.sendline(appdesc.user_name) 11 | child.expect("your email> ") 12 | child.sendline(appdesc.email) 13 | child.expect('copyright holder \("%s"\)> ' % appdesc.user_name) 14 | child.sendline() 15 | child.expect('project version> ') 16 | child.sendline(appdesc.project_version) 17 | child.expect('Please specify the ERTS version \(".*"\)> ') 18 | child.sendline() 19 | child.expect('Is this a single application project \("n"\)> ') 20 | child.sendline() 21 | child.expect("app> ") 22 | child.sendline(appdesc.app_names[0]) 23 | for n in appdesc.app_names[1:]: 24 | child.expect('app \(""\)> ') 25 | child.sendline(n) 26 | 27 | child.expect('app \(""\)> ') 28 | child.sendline() 29 | child.expect('\("y"\)> ') 30 | child.sendline() 31 | child.expect("Project was created, you should be good to go!") 32 | child.expect(pexpect.EOF) 33 | 34 | 35 | def test_gen_name(self): 36 | appdesc = st.AppDesc(user_name = "Smoke Test User", 37 | email = "noreply@erlware.org", 38 | copyright_holder = "Smoke Test Copy, LLC.", 39 | # This needs to match the gen name since 40 | # we are overriding it 41 | project_name = "foo", 42 | project_version = "0.134.0.0", 43 | app_names = ["app1", "app2", "app3", "app3", "app4"]) 44 | self.run_custom_gen(appdesc) 45 | self.verify_gen(appdesc) 46 | 47 | 48 | 49 | if __name__ == '__main__': 50 | unittest.main() 51 | -------------------------------------------------------------------------------- /doc/assets/css/skeleton/stylesheets/layout.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V1.1 3 | * Copyright 2011, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 8/17/2011 8 | */ 9 | 10 | /* Table of Content 11 | ================================================== 12 | #Site Styles 13 | #Page Styles 14 | #Media Queries 15 | #Font-Face */ 16 | 17 | /* #Site Styles 18 | ================================================== */ 19 | 20 | /* #Page Styles 21 | ================================================== */ 22 | 23 | /* #Media Queries 24 | ================================================== */ 25 | 26 | /* Smaller than standard 960 (devices and browsers) */ 27 | @media only screen and (max-width: 959px) {} 28 | 29 | /* Tablet Portrait size to standard 960 (devices and browsers) */ 30 | @media only screen and (min-width: 768px) and (max-width: 959px) {} 31 | 32 | /* All Mobile Sizes (devices and browser) */ 33 | @media only screen and (max-width: 767px) {} 34 | 35 | /* Mobile Landscape Size to Tablet Portrait (devices and browsers) */ 36 | @media only screen and (min-width: 480px) and (max-width: 767px) {} 37 | 38 | /* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */ 39 | @media only screen and (max-width: 479px) {} 40 | 41 | 42 | /* #Font-Face 43 | ================================================== */ 44 | /* This is the proper syntax for an @font-face file 45 | Just create a "fonts" folder at the root, 46 | copy your FontName into code below and remove 47 | comment brackets */ 48 | 49 | /* @font-face { 50 | font-family: 'FontName'; 51 | src: url('../fonts/FontName.eot'); 52 | src: url('../fonts/FontName.eot?iefix') format('eot'), 53 | url('../fonts/FontName.woff') format('woff'), 54 | url('../fonts/FontName.ttf') format('truetype'), 55 | url('../fonts/FontName.svg#webfontZam02nTh') format('svg'); 56 | font-weight: normal; 57 | font-style: normal; } 58 | */ -------------------------------------------------------------------------------- /doc/assets/javascript/html5.js: -------------------------------------------------------------------------------- 1 | // html5shiv @rem remysharp.com/html5-enabling-script 2 | // iepp v1.6.2 @jon_neal iecss.com/print-protector 3 | // Dual licensed under the MIT or GPL Version 2 licenses 4 | /*@cc_on(function(a,b){function r(a){var b=-1;while(++b";return a.childNodes.length!==1}())){a.iepp=a.iepp||{};var c=a.iepp,d=c.html5elements||"abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",e=d.split("|"),f=e.length,g=new RegExp("(^|\\s)("+d+")","gi"),h=new RegExp("<(/*)("+d+")","gi"),i=/^\s*[\{\}]\s*$/,j=new RegExp("(^|[^\\n]*?\\s)("+d+")([^\\n]*)({[\\n\\w\\W]*?})","gi"),k=b.createDocumentFragment(),l=b.documentElement,m=l.firstChild,n=b.createElement("body"),o=b.createElement("style"),p=/print|all/,q;c.getCSS=function(a,b){if(a+""===undefined)return"";var d=-1,e=a.length,f,g=[];while(++d 6 | {ok, {ProjectDir, ProjectName}} = sint_test_project_gen:a_generated_project(), 7 | 8 | write_yrl(ProjectDir), 9 | 10 | Ret = sinan:run_sinan(["-s", ProjectDir, "build"]), 11 | {ok, BuildState} = Ret, 12 | ?assertMatch([], sin_state:get_run_errors(BuildState)), 13 | BinFile = filename:join([ProjectDir, "_build", ProjectName, "lib", ProjectName ++ "-0.1.0", 14 | "ebin", "test.beam"]), 15 | ?assertMatch(true, sin_utils:file_exists(sin_state:new(), BinFile)). 16 | 17 | 18 | write_yrl(ProjectDir) -> 19 | ?assertMatch(ok, 20 | ec_file:mkdir_path(filename:join([ProjectDir, "src"]))), 21 | ImplPath = filename:join([ProjectDir, "src", "test.yrl"]), 22 | file:write_file(ImplPath, get_yrl_contents()), 23 | ImplPath. 24 | 25 | get_yrl_contents() -> 26 | " 27 | Nonterminals 28 | predicates predicate list element elements. 29 | 30 | Terminals '(' ')' ',' 31 | atom var integer string set union intersection comparator. 32 | 33 | Rootsymbol predicates. 34 | 35 | predicates -> predicate : '$1'. 36 | predicates -> predicate union predicate : {union, '$1', '$3'}. 37 | predicates -> predicates union predicate : {union, '$1', '$3'}. 38 | 39 | predicates -> predicate intersection predicate : \ 40 | {intersection, '$1', '$3'}. 41 | 42 | predicate -> var set list : \ 43 | {predicate, {var, unwrap('$1')}, memberof, '$3'}. 44 | 45 | predicate -> var comparator element : \ 46 | {predicate, {var, unwrap('$1')}, unwrap('$2'), '$3'}. 47 | 48 | list -> '(' ')' : nil. 49 | list -> '(' elements ')' : {list,'$2'}. 50 | 51 | elements -> element : ['$1']. 52 | elements -> element ',' elements : ['$1'] ++ '$3'. 53 | element -> atom : '$1'. 54 | element -> var : unwrap('$1'). 55 | element -> integer : unwrap('$1'). 56 | element -> string : unwrap('$1'). 57 | 58 | Erlang code. 59 | 60 | unwrap({_,_,V}) -> V. 61 | ". 62 | -------------------------------------------------------------------------------- /src/sin_task_clean.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @author Eric Merritt 4 | %%% @doc 5 | %%% Deletes everything in the Build directory 6 | %%% @end 7 | %%% @copyright (C) 2006-2011 Erlware 8 | %%%--------------------------------------------------------------------------- 9 | -module(sin_task_clean). 10 | 11 | -behaviour(sin_task). 12 | 13 | -include_lib("sinan/include/sinan.hrl"). 14 | 15 | %% API 16 | -export([description/0, do_task/2]). 17 | 18 | -define(TASK, clean). 19 | -define(DEPS, []). 20 | 21 | %%==================================================================== 22 | %% API 23 | %%==================================================================== 24 | 25 | %% @doc return a description of this task to the call 26 | -spec description() -> sin_task:config(). 27 | description() -> 28 | 29 | Desc = " 30 | clean Task 31 | ========== 32 | 33 | This command removes *all* the build artifacts currently in the 34 | system. 35 | 36 | Basically this does the equivalent of: 37 | 38 | $> rm -rf _build 39 | ", 40 | 41 | #task{name = ?TASK, 42 | task_impl = ?MODULE, 43 | bare = false, 44 | deps = ?DEPS, 45 | example = "clean", 46 | short_desc = "Removes all build artifacts in the system", 47 | desc = Desc, 48 | opts = []}. 49 | 50 | %% @doc clean all sinan artifacts from the system 51 | -spec do_task(sin_config:config(), sin_state:state()) -> sin_state:state(). 52 | do_task(Config, State) -> 53 | sin_log:verbose(Config, "cleaning build artifacts"), 54 | BuildDir = sin_state:get_value(build_root, State), 55 | sin_log:verbose(Config, "Removing directories and contents in ~s", [BuildDir]), 56 | sin_utils:delete_dir(BuildDir), 57 | State. 58 | 59 | %%==================================================================== 60 | %%% Internal functions 61 | %%==================================================================== 62 | -------------------------------------------------------------------------------- /src/sin_compile_jxa.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Eric Merritt 3 | %%% @copyright (C) 2012, Erlware, LLC 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%%------------------------------------------------------------------- 8 | -module(sin_compile_jxa). 9 | 10 | %% API 11 | -export([build_file/5, 12 | get_target/3, 13 | format_exception/1]). 14 | 15 | -include_lib("sinan/include/sinan.hrl"). 16 | 17 | %%%=================================================================== 18 | %%% API 19 | %%%=================================================================== 20 | get_target(BuildDir, File, ".jxa") -> 21 | sin_task_build:get_target(File, ".jxa", BuildDir, ".beam"). 22 | 23 | %% @doc Do the actual compilation on the file. 24 | -spec build_file(sin_config:matcher(), sin_state:state(), 25 | sin_file_info:mod(), [term()], string()) -> 26 | {module(), sin_config:config()}. 27 | build_file(Config, State, Module=#module{path=File}, Options, _Target) -> 28 | sin_task:ensure_started(proper), 29 | sin_log:verbose(Config, "Building ~s", [File]), 30 | case joxa.compiler:'do-compile'(File, Options) of 31 | Ctx when is_tuple(Ctx), element(1, Ctx) == context -> 32 | case joxa.compiler:'has-errors?'(Ctx) of 33 | false -> 34 | {State, [Module]}; 35 | true -> 36 | ?SIN_RAISE(State, {build_error, error_building_jxa_file, File}) 37 | end; 38 | error -> 39 | ?SIN_RAISE(State, {build_error, error_building_jxa_file, File}) 40 | end. 41 | 42 | %% @doc Format an exception thrown by this module 43 | -spec format_exception(sin_exceptions:exception()) -> 44 | string(). 45 | format_exception(Exception) -> 46 | sin_exceptions:format_exception(Exception). 47 | %%%=================================================================== 48 | %%% Internal functions 49 | %%%=================================================================== 50 | -------------------------------------------------------------------------------- /test/sint_gh50.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @copyright (C) 2011, Erlware, LLC. 3 | %%% @doc 4 | %%% Test the ability to correctly correctly handle parse_transform 5 | %%% dependencies 6 | %%% @end 7 | %%%------------------------------------------------------------------- 8 | -module(sint_gh50). 9 | 10 | -include_lib("eunit/include/eunit.hrl"). 11 | 12 | -export([given/3, 'when'/3, then/3]). 13 | 14 | given([a,generated,project], _State, _) -> 15 | sint_test_project_gen:a_generated_project(); 16 | given([that,project,contains,a,parse,transform], State={ProjectDir, _}, _) -> 17 | File = filename:join([ProjectDir, "src", "sint_gh50_z_some_module.erl"]), 18 | ok = file:write_file(File, transform_contents()), 19 | {ok, State}; 20 | given([that,project,contains,a,module,that, 21 | depends,on,that,parse,transform], State={ProjectDir, _}, _) -> 22 | AFile = filename:join([ProjectDir, "src", "sint_gh50_a_some_module.erl"]), 23 | IncludeFile = filename:join([ProjectDir, "include", "sint_gh50_some_include.hrl"]), 24 | ok = file:write_file(AFile, dependent_contents()), 25 | ok = file:write_file(IncludeFile, include_contents()), 26 | {ok, State}. 27 | 28 | 'when'([a,build,step,is,run,on,this,project], 29 | {ProjectDir, ProjectName}, _) -> 30 | Ret = sinan:run_sinan(["-s", ProjectDir, "build"]), 31 | ?assertMatch({_, _}, Ret), 32 | {_, TrueRet} = Ret, 33 | {ok, {ProjectDir, ProjectName, TrueRet}}. 34 | 35 | then([build,the,app,normally], 36 | State = {_, _, BuildState}, _) -> 37 | ?assertMatch([], sin_state:get_run_errors(BuildState)), 38 | {ok, State}. 39 | 40 | transform_contents() -> 41 | "-module(sint_gh50_z_some_module). 42 | -export([parse_transform/2]). 43 | parse_transform(Forms, _Options) -> 44 | Forms. 45 | ". 46 | 47 | include_contents() -> 48 | "-compile({parse_transform, sint_gh50_z_some_module}).". 49 | 50 | dependent_contents() -> 51 | "-module(sint_gh50_a_some_module). 52 | -include_lib(\"super_foo/include/sint_gh50_some_include.hrl\"). 53 | -export([new/2]). 54 | new(_, _) -> ok. 55 | ". 56 | -------------------------------------------------------------------------------- /test/sint_multirun_support.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @copyright (C) 2011, Erlware, LLC. 3 | %%% @doc 4 | %%% Add the ability to run sinan multiple times concurrently 5 | %%% @end 6 | %%% Created : 5 Sep 2011 by Eric Merritt 7 | %%%------------------------------------------------------------------- 8 | -module(sint_multirun_support). 9 | 10 | -export([given/3, 'when'/3, then/3]). 11 | 12 | -include_lib("eunit/include/eunit.hrl"). 13 | 14 | %% Step definitions for the sample calculator Addition feature. 15 | 16 | given([two, generated, projects], _, _) -> 17 | BaseDir = ec_file:mkdtemp(), 18 | ProjectDescs = 19 | lists:map(fun(Number) -> 20 | ProjectName = "foobachoo" ++ 21 | erlang:integer_to_list(Number), 22 | {ProjectDir, _} = 23 | sint_test_project_gen:single_app_project(BaseDir, 24 | ProjectName), 25 | {ProjectName, ProjectDir} 26 | end, 27 | lists:seq(0, 2)), 28 | 29 | {ok, ProjectDescs}. 30 | 31 | 'when'([a, build, step, is, run, on, each, project, concurrently], 32 | ProjectDescs, _) -> 33 | Results = ec_plists:map(fun({_, ProjectDir}) -> 34 | sinan:run_sinan(["-s", ProjectDir, 35 | "build"]) 36 | end, ProjectDescs), 37 | {ok, lists:zip(ProjectDescs, Results)}. 38 | 39 | then([sinan, should, build, both, projects, without, a, problem], 40 | ProjectDescs, _) -> 41 | ?assertMatch(true, 42 | lists:all(fun({{ProjectName, ProjectDir}, {ok, _}}) -> 43 | ok == sint_test_project_gen:validate_single_app_project(ProjectDir, ProjectName); 44 | ({_, {error, _}}) -> 45 | false 46 | end, ProjectDescs)), 47 | {ok, ProjectDescs}. 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | 4 | 5 | What is Sinan 6 | ------------- 7 | 8 | Sinan is a build tool designed to build Erlang/OTP Projects, Releases 9 | and Applications. Sinan leverages the metadata artifacts provided by 10 | OTP to do a good job building, testing, releasing, etc with very 11 | little or no additional input from the developer. 12 | 13 | Getting Sinan 14 | ------------- 15 | 16 | By far the easiest way to get sinan is to download it from the 17 | [downloads site](https://github.com/erlware/sinan/downloads). 18 | 19 | More Information and FAQ 20 | ------------------------ 21 | 22 | Sinan has extensive further documentation in its 23 | [wiki on github](https://github.com/erlware/sinan/wiki). Check there 24 | for more information. 25 | 26 | The Sinan FAQ is available at 27 | [here](https://github.com/erlware/sinan/wiki/FAQ). 28 | 29 | ### The Community 30 | 31 | A community exists around Sinan and the other Erlware projects. You 32 | may participate in the community and ask questions by joining the 33 | [erlware-questions](http://groups.google.com/group/erlware-questions) 34 | mailing list. 35 | 36 | 37 | Quick Start 38 | ----------- 39 | 40 | To get started just cd into an OTP Application and type 41 | 42 | sinan build 43 | 44 | This will give you an fully built OTP application under the _build 45 | directory. You can then run the command 46 | 47 | sinan shell 48 | 49 | to get an erlang shell with all the paths pointing correctly to the 50 | various parts of your system. 51 | 52 | If you want to get adventurous you can run all the eunit tests in your 53 | app by running: 54 | 55 | sinan test 56 | 57 | and finially, if you want to package up a normal erlang release 58 | tarball you can run 59 | 60 | sinan dist 61 | 62 | The tarball will end up in 63 | 64 | /_build//tar/-.tar.gz 65 | 66 | To get a list of all tasks currently available run the command 67 | 68 | sinan help 69 | 70 | Hopefully thats enough to get you started, but sinan has many options 71 | to do various things with projects from small single app projects to 72 | very large multiple app projects. To get more information take a look 73 | at the sinan manual. 74 | 75 | -------------------------------------------------------------------------------- /test/sint_gh52.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @copyright (C) 2011, Erlware, LLC. 3 | %%% @doc 4 | %%% Test the ability to set a home directory and have a top level dir 5 | %%% @end 6 | %%%------------------------------------------------------------------- 7 | -module(sint_gh52). 8 | 9 | -include_lib("eunit/include/eunit.hrl"). 10 | 11 | -export([given/3, 'when'/3, then/3]). 12 | 13 | given([a,generated,project], _State, _) -> 14 | sint_test_project_gen:a_generated_project(erlang:atom_to_list(?MODULE)); 15 | given([a,'non-standard',home,directory], {PD, PN}, _) -> 16 | NewHomeDir = ec_file:mkdtemp(), 17 | {ok, {PD, PN, NewHomeDir}}; 18 | given([a,sinan,config,in,that,home,directory], State={_, _, NewHomeDir}, _) -> 19 | TopSinConfig = filename:join(NewHomeDir, ".sinan.config"), 20 | ok = file:write_file(TopSinConfig, config_contents()), 21 | {ok, State}. 22 | 23 | 'when'([a,home,directory,is,specified,on,the,command,line], State, _) -> 24 | %% This is a placeholder/reminder the actually command line is 25 | %% specified next 26 | {ok, State}; 27 | 'when'([a,build,step,is,run,on,this,project], 28 | {ProjectDir, PN, NewHomeDir}, _) -> 29 | Ret = sinan:run_sinan(["-u", NewHomeDir, "-s", ProjectDir, "build"]), 30 | ?assertMatch({_, _}, Ret), 31 | {_, TrueRet} = Ret, 32 | {ok, {ProjectDir, PN, TrueRet, NewHomeDir}}. 33 | 34 | then([sinan,uses,the,config,in,the,alternate,home,directory], 35 | State = {_ProjectDir, PN, _TrueRet, _HD}, _) -> 36 | ModuleName = erlang:list_to_atom(PN ++ "_app"), 37 | {compile, Options} = proplists:lookup(compile, ModuleName:module_info()), 38 | {options, Opts} = proplists:lookup(options, Options), 39 | ?assert(false == lists:member(debug_info, Opts)), 40 | {ok, State}; 41 | then([_,the,app,normally], State = {_, _, BuildState, _}, _) -> 42 | ?assertMatch([], sin_state:get_run_errors(BuildState)), 43 | {ok, State}; 44 | then([have,the,home,directory,correctly,specified,in,the,build,state], 45 | State = {_, _, BuildState, HomeDir}, _) -> 46 | ?assertMatch(HomeDir, sin_state:get_value(home_dir, BuildState)), 47 | {ok, State}. 48 | 49 | 50 | config_contents() -> 51 | "{compile_args, []}.". 52 | -------------------------------------------------------------------------------- /src/sin_task_version.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @author Eric Merritt 4 | %%% @doc 5 | %%% Return the sinan server version. 6 | %%% @end 7 | %%% @copyright (C) 2008-2011 Erlware 8 | %%%--------------------------------------------------------------------------- 9 | -module(sin_task_version). 10 | 11 | -behaviour(sin_task). 12 | 13 | -include_lib("sinan/include/sinan.hrl"). 14 | 15 | %% API 16 | -export([description/0, do_task/2]). 17 | 18 | -define(TASK, version). 19 | -define(DEPS, []). 20 | 21 | %%==================================================================== 22 | %% API 23 | %%==================================================================== 24 | %% @doc provides a description for this task 25 | -spec description() -> sin_task:task_description(). 26 | description() -> 27 | 28 | Desc = " 29 | version Task 30 | ============ 31 | 32 | This command simply prents out the current version of sinan", 33 | 34 | #task{name = ?TASK, 35 | task_impl = ?MODULE, 36 | bare = true, 37 | deps = ?DEPS, 38 | example = "version", 39 | short_desc = "Provides sinan version information", 40 | desc = Desc, 41 | opts = []}. 42 | 43 | %% @doc Get the version of sinan that is currently running 44 | -spec do_task(sin_config:config(), sin_state:state()) -> sin_state:state(). 45 | do_task(Config, State) -> 46 | Version = case get_version() of 47 | unknown_version -> 48 | "v4.1.1"; 49 | SinVersion -> 50 | SinVersion 51 | end, 52 | sin_log:normal(Config, "~s", [Version]), 53 | sin_state:store(sinan_vsn, Version, State). 54 | 55 | %%==================================================================== 56 | %%% Internal functions 57 | %%==================================================================== 58 | 59 | %% @doc Gets the current version of the sinan release. 60 | -spec get_version() -> Vsn::string() | unkown_version. 61 | get_version() -> 62 | SinDir = filename:join([filename:dirname(code:priv_dir(sinan)), "ebin", "sinan.app"]), 63 | get_version(file:consult(SinDir)). 64 | 65 | get_version({ok, [{application, sinan, Opts}]}) -> 66 | case lists:keysearch(vsn, 1, Opts) of 67 | {value, {vsn, Vsn}} -> 68 | Vsn; 69 | _ -> 70 | unknown_version 71 | end; 72 | get_version(_) -> 73 | unknown_version. 74 | 75 | -------------------------------------------------------------------------------- /test/sint_app_src_mod_list.erl: -------------------------------------------------------------------------------- 1 | -module(sint_app_src_mod_list). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | -export([given/3, 'when'/3, then/3]). 6 | 7 | given([a,generated,project,that,contains,an,'ebin/app'], _State, _) -> 8 | BaseDir = ec_file:mkdtemp(), 9 | ProjectName = "super_foo", 10 | {ProjectDir, _} = 11 | sint_test_project_gen:single_app_project(BaseDir, ProjectName), 12 | AppEbin = filename:join([ProjectDir, "ebin", ProjectName ++ ".app"]), 13 | AppSrc = filename:join([ProjectDir, "src", ProjectName ++ ".app.src"]), 14 | ?assertMatch(ok, file:write_file(AppEbin, 15 | sint_cuke_support_funs:app_src(ProjectName))), 16 | sint_cuke_support_funs:delete_if_exists(AppSrc), 17 | {ok, {ProjectDir, ProjectName}}; 18 | given([a,generated,project,that,contains,an,'app.src'], _State, _) -> 19 | BaseDir = ec_file:mkdtemp(), 20 | ProjectName = "super_foo", 21 | {ProjectDir, _} = 22 | sint_test_project_gen:single_app_project(BaseDir, ProjectName), 23 | {ok, {ProjectDir, ProjectName}}. 24 | 25 | 'when'([a,build,step,is,run,on,this,project], 26 | {ProjectDir, ProjectName}, _) -> 27 | Ret = sinan:run_sinan(["-s", ProjectDir, "build"]), 28 | ?assertMatch({_, _}, Ret), 29 | {ok, {ProjectDir, ProjectName, Ret}}. 30 | 31 | then([sinan, should, put, the, populate, the, module, list], 32 | {ProjectDir, ProjectName, Ret}, _) -> 33 | BuildEbin = filename:join([ProjectDir, 34 | "_build", ProjectName, 35 | "lib", ProjectName ++ "-0.1.0", 36 | "ebin", ProjectName ++ ".app"]), 37 | Result = file:consult(BuildEbin), 38 | ?assertMatch({ok, [{application, _, _}]}, Result), 39 | {ok, [{application, _, Details}]} = Result, 40 | ModuleEntry = lists:keyfind(modules, 1, Details), 41 | ?assertMatch({modules, _}, ModuleEntry), 42 | {modules, ModuleList} = ModuleEntry, 43 | ?assertMatch(2, erlang:length(ModuleList)), 44 | ?assertMatch(true, 45 | lists:member(erlang:list_to_atom(ProjectName ++ "_app"), 46 | ModuleList)), 47 | ?assertMatch(true, 48 | lists:member(erlang:list_to_atom(ProjectName ++ "_sup"), 49 | ModuleList)), 50 | 51 | {ok, {ProjectDir, ProjectName, Ret}}; 52 | 53 | then([build, the, app, normally], 54 | State = {_, _, Ret}, _) -> 55 | ?assertMatch({ok, _}, Ret), 56 | {ok, BuildState} = Ret, 57 | ?assertMatch([], sin_state:get_run_errors(BuildState)), 58 | {ok, State}. 59 | -------------------------------------------------------------------------------- /src/sin_dep_resolver.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Eric Merritt 3 | %%% @copyright (C) 2011, Erlware, LLC. 4 | %%% @doc 5 | %%% A signature definition for resolver providers 6 | %%% @end 7 | %%%------------------------------------------------------------------- 8 | -module(sin_dep_resolver). 9 | 10 | -export([new/3, 11 | app_dependencies/3, 12 | app_versions/2, 13 | resolve/3, 14 | behaviour_info/1]). 15 | 16 | -record(dep_resolver, {impl, state}). 17 | 18 | %%============================================================================ 19 | %% Types 20 | %%============================================================================ 21 | -opaque impl() :: record(dep_resolver). 22 | 23 | %%============================================================================ 24 | %% API 25 | %%============================================================================ 26 | 27 | %% @doc create a new dep resolver with the build config 28 | -spec new(module(), sin_config:matcher(), sin_state:state()) -> impl(). 29 | new(Impl, Config, State) -> 30 | #dep_resolver{impl=Impl, state=Impl:new(Config, State)}. 31 | 32 | %% @doc return the dependency specs for a particular version of 33 | %% an app 34 | -spec app_dependencies(impl(), sin_dep_solver:app(), 35 | sin_dep_solver:version()) -> 36 | {impl(), [sin_dep_solver:spec()]}. 37 | app_dependencies(Resolver = #dep_resolver{impl=Impl, state=State0}, App, Ver) -> 38 | {State1, Deps} = Impl:app_dependencies(State0, App, Ver), 39 | {Resolver#dep_resolver{state=State1}, Deps}. 40 | 41 | %% @doc get all available versions of an app 42 | -spec app_versions(impl(), sin_dep_solver:app()) -> 43 | {impl(), [sin_dep_solver:version()]}. 44 | app_versions(Resolver = #dep_resolver{impl=Impl, state=State0}, App) -> 45 | {State1, Versions} = Impl:app_versions(State0, App), 46 | {Resolver#dep_resolver{state=State1}, Versions}. 47 | 48 | %% @doc resolve the actual app and version and return a path and 49 | %% updated state to the resolved app. This will only be called once per dependency. 50 | -spec resolve(impl(), sin_dep_solver:app(), sin_dep_solver:version()) -> 51 | {impl(), string()}. 52 | resolve(Resolver = #dep_resolver{impl=Impl, state=State0}, App, Vsn) -> 53 | {State1, Path} = Impl:resolve(State0, App, Vsn), 54 | {Resolver#dep_resolver{state=State1}, Path}. 55 | 56 | %% @doc define the behaviour for tasks. 57 | behaviour_info(callbacks) -> 58 | [{new, 2}, 59 | {app_dependencies, 3}, 60 | {app_versions, 2}]; 61 | behaviour_info(_) -> 62 | undefined. 63 | -------------------------------------------------------------------------------- /overview.edoc: -------------------------------------------------------------------------------- 1 | @title Welcome to Sinan 2 | @doc 3 | 4 |

Overview

5 | 6 |

Sinan is our flagship build system. It is a build system designed expressly for 7 | Erlang OTP projects. It simply will not work on projects that are not based on 8 | the OTP principles. Fortunately, this shouldn't be too much of a problem because 9 | you shouldn't really be writing significant Erlang applications without using 10 | OTP.

11 | 12 |

By choosing to build only OTP projects we are able to make use of all that 13 | wonderful OTP metadata. This allows us, in the vast majority of cases, to simply 14 | 'Do the Right Thing' while building a project. This approach allows us to build 15 | a project with little or no input from the user. It also allows us to do a lot 16 | of the grunt work of OTP automatically.

17 | 18 |

Features

19 | 20 |

As of this writing Sinan will do the following things for you.

21 | 22 |
    23 | 24 |
  • Manage dependencies
  • 25 |
  • Retrieve packages to meet dependencies
  • 26 |
  • Run Unit Tests and provide code coverage metrics
  • 27 |
  • Warn about OTP layout and metadata issues
  • 28 |
  • Compile Erlang files
  • 29 |
  • Generate *.rel files
  • 30 |
  • Generate *.boot, *.script, and other files required by the otp release 31 | system
  • 32 | 33 |
  • Run Dialyzer across all of the code in the project
  • 34 |
  • Create a tar file that the OTP releases system understands and can use
  • 35 |
  • Clean up after itself
  • 36 |
37 | 38 |

Whats in a Name?

39 | 40 |

I usually try to pick good googleable project names. This usually means that I 41 | have to pick and arbitrary name and build meaning around it, since almost all of 42 | the proper nouns that would fit a project are taken or already well represented 43 | on the net. Since I had to pick a name for a build system I decided to use the 44 | name of a famous architect or builder from history. There is no small number of 45 | possible options. Unfortunately, many of them are already used for various open 46 | source projects out there. In my searches for possible names, I came across 47 | Ḳoca Mi‘mār Sinān Āġā a famous builder 48 | from the Ottoman Empire. He worked primarily in Istanbul. Well this caught my 49 | attention. At one time Istanbul was Constantinople and before that Byzantium and 50 | I have a certain fascination with Byzantine history. The fact that Sinan was a 51 | brilliant architect just kind of sealed the deal. So what I ended up with is a 52 | nice googleable name that is memorable and short enough to type on the command 53 | line, also having an interesting historical meaning.

54 | 55 | @author Eric Merritt 56 | @copyright 2007, 2008 Erlware 57 | -------------------------------------------------------------------------------- /src/sin_task_echo.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%------------------------------------------------------------------- 3 | %%%--------------------------------------------------------------------------- 4 | %%% @author Eric Merritt 5 | %%% @doc 6 | %%% This provides echo functionality to sinan. The ability to echo paths, 7 | %%% include directories, etc to The caller 8 | %%% @end 9 | %%% @copyright (C) 2012 Erlware 10 | %%%--------------------------------------------------------------------------- 11 | -module(sin_task_echo). 12 | 13 | -behaviour(sin_task). 14 | 15 | -include_lib("sinan/include/sinan.hrl"). 16 | 17 | %% API 18 | -export([description/0, do_task/2]). 19 | 20 | -define(TASK, echo). 21 | -define(DEPS, [depends]). 22 | 23 | %%==================================================================== 24 | %% API 25 | %%==================================================================== 26 | 27 | %% @doc provides a description of this task 28 | -spec description() -> sin_task:task_description(). 29 | description() -> 30 | 31 | Desc = " 32 | echo Task 33 | ========= 34 | 35 | This task echos the information that the user requests to back to the caller in 36 | a way that erlang can make use of. It mostly exists to make it easier to 37 | integrate Sinan with make. 38 | 39 | The two support arguments at the moment are `paths` and `includes` 40 | 41 | You can get other information out of the state but it will not be nicely 42 | formatted", 43 | 44 | #task{name = ?TASK, 45 | task_impl = ?MODULE, 46 | bare = false, 47 | deps = ?DEPS, 48 | example = "echo paths", 49 | desc = Desc, 50 | short_desc = "Displays the requested information on the command line", 51 | opts = []}. 52 | 53 | %% @doc run all tests for all modules in the system 54 | do_task(Config, State0) -> 55 | lists:foldl(fun print_correct/2, State0, Config:match(additional_args, [])). 56 | 57 | 58 | %%==================================================================== 59 | %%% Internal functions 60 | %%==================================================================== 61 | print_correct("paths", State) -> 62 | lists:foreach(fun(#app{path=Path}) -> 63 | io:format("-pa ~s ", [filename:join(Path, "ebin")]) 64 | end, sin_state:get_value(release_apps, State)), 65 | State; 66 | print_correct("includes", State) -> 67 | lists:foreach(fun(#app{path=Path}) -> 68 | io:format("-I ~s ", [filename:join(Path, "include")]) 69 | end, sin_state:get_value(release_apps, State)), 70 | State; 71 | print_correct(Else, State) -> 72 | AtomRep = erlang:list_to_atom(Else), 73 | io:format("~s ", [sin_state:get_value(AtomRep, State)]), 74 | State. 75 | -------------------------------------------------------------------------------- /src/sin_matcher.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @author Eric Merritt 4 | %%% @copyright 2011 Erlware, LLC. 5 | %%% @doc 6 | %%% @end 7 | %%%---------------------------------------------------------------------------- 8 | -module(sin_matcher, [Config, 9 | Opts, 10 | Task, 11 | Release, 12 | App, 13 | Dir, 14 | Module]). 15 | 16 | %% API 17 | -export([get/1, 18 | match/1, 19 | match/2, 20 | size/0, 21 | has_key/1, 22 | will_match/1, 23 | to_list/0, 24 | specialize/1]). 25 | 26 | -include_lib("sinan/include/sinan.hrl"). 27 | 28 | %%==================================================================== 29 | %% API 30 | %%==================================================================== 31 | 32 | %% @doc specialize an existing matcher on new opts 33 | -spec specialize(sin_config:spec_opts()) -> module(). 34 | specialize(NewOpts) -> 35 | sin_config:create_matcher(NewOpts ++ Opts, Config). 36 | 37 | %% @doc get a value from the key where. 38 | -spec get(sin_config:full_key()) -> sin_config:value(). 39 | get(Key) -> 40 | sin_config:get({Key, Task, Release, App, Dir, Module}, Config). 41 | 42 | %% @doc get a value from the key where. 43 | -spec match(sin_config:partial_key()) -> sin_config:value(). 44 | match(Key) -> 45 | sin_config:'__match__'({Key, Task, Release, App, Dir, Module}, Config). 46 | 47 | %% @doc get a value from the key where, if the value does not exist use the 48 | %% specified default 49 | -spec match(sin_config:partial_key(), sin_config:value()) -> sin_config:value(). 50 | match(Key, Default) -> 51 | sin_config:'__match__'({Key, Task, Release, App, Dir, Module}, 52 | Default, Config). 53 | 54 | %% @doc get the current number of entries in the config 55 | -spec size() -> non_neg_integer(). 56 | size() -> 57 | sin_config:size(Config). 58 | 59 | %% @doc test to see if the key exists in the data base via an exact match 60 | -spec has_key(sin_config:full_key()) -> boolean(). 61 | has_key(Key) -> 62 | sin_config:has_key({Key, Task, Release, App, Dir, Module}, Config). 63 | 64 | %% @doc test to see if the key exists in the data base via an exact match 65 | -spec will_match(sin_config:full_key()) -> boolean(). 66 | will_match(Key) -> 67 | sin_config:'__will_match__'({Key, Task, Release, App, Dir, Module}, 68 | Config). 69 | 70 | to_list() -> 71 | sin_config:to_list(Config). 72 | 73 | %%==================================================================== 74 | %% Internal Functions 75 | %%==================================================================== 76 | -------------------------------------------------------------------------------- /src/sin_release.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%------------------------------------------------------------------- 3 | %%% @author Eric Merritt 4 | %%% @copyright (C) 2009 - 2011, Erlware 5 | %%% @doc 6 | %%% correctly generate release information 7 | %%% @end 8 | %%%------------------------------------------------------------------- 9 | -module(sin_release). 10 | 11 | -export([get_release/4, 12 | get_deps/1, 13 | get_erts_vsn/1, 14 | get_rel_info/1]). 15 | 16 | %%==================================================================== 17 | %% API 18 | %%==================================================================== 19 | 20 | %% @doc get the rel file for the specified information 21 | -spec get_release(sin_state:state(), 22 | string(), string(), string()) -> 23 | RelFilePath::string(). 24 | get_release(State, RootDir, Name, Version) -> 25 | VName = [Name, "-", Version, ".rel"], 26 | NName = [Name, ".rel"], 27 | 28 | % Test /release/-.rel 29 | Paths = [filename:join([RootDir, "releases", VName]), 30 | 31 | % Test /release//.rel 32 | filename:join([RootDir, "releases", NName])], 33 | 34 | find_rel_file(State, Paths). 35 | 36 | %% @doc get dependenciens for the release 37 | -spec get_deps(ReleaseInfo::term()) -> 38 | [{Name::string(), Version::string()}]. 39 | get_deps({release, _RelInfo, _ErtsInfo, Deps}) -> 40 | lists:foldr(fun({Name, Vsn}, Acc) -> 41 | [{Name, Vsn} | Acc]; 42 | ({Name, Vsn, _}, Acc) -> 43 | [{Name, Vsn} | Acc]; 44 | ({Name, Vsn, _, _}, Acc) -> 45 | [{Name, Vsn} | Acc] 46 | end, 47 | [], 48 | Deps). 49 | 50 | %% @doc get the erts version from the release informatino 51 | -spec get_erts_vsn(ReleaseInfo::term()) -> Vsn::string(). 52 | get_erts_vsn({release, _RelInfo, {erts, Vsn}, _Deps}) -> 53 | Vsn. 54 | %% @doc get the release name from the system 55 | -spec get_rel_info(ReleaseInfo::term()) -> string(). 56 | get_rel_info({release, RelInfo, _ErtsInfo, _Deps}) -> 57 | RelInfo. 58 | 59 | %%==================================================================== 60 | %% Internal functions 61 | %%==================================================================== 62 | 63 | get_file(State, Path) -> 64 | case sin_utils:file_exists(State, Path) of 65 | true -> 66 | file:consult(Path); 67 | false -> 68 | no_file 69 | end. 70 | 71 | find_rel_file(State, [Path | Rest]) -> 72 | case get_file(State, Path) of 73 | {ok, [Data]} -> 74 | Data; 75 | no_file -> 76 | find_rel_file(State, Rest) 77 | end; 78 | find_rel_file(_State, []) -> 79 | no_file. 80 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | Building Sinan from Source 2 | ========================== 3 | 4 | The easiest way to get and use Sinan is to download the precompiled 5 | version from [the github sinan site] 6 | (https://github.com/downloads/erlware/sinan/sinan) 7 | 8 | To build Sinan, start with the Makefile in the root of the Sinan 9 | project. Unfortunately, Sinan can not actually build itself directly 10 | due to the lack of sandboxing support in the Erlang code loading 11 | subsystem. The purpose of the Makefile based bootstraping in the Sinan 12 | project is to get around that limitation. 13 | 14 | To get started building Sinan from source we need to have a few 15 | dependencies in place. 16 | 17 | Dependencies 18 | ------------ 19 | 20 | * [erlware_commons](https://github.com/erlware/erlware_commons) 21 | * [cucumberl](https://github.com/membase/cucumberl) 22 | * [getopt](https://github.com/jcomellas/getopt) 23 | * [proper](https://github.com/manopapad/proper) 24 | * [joxa](https://github.com/erlware/joxa) 25 | 26 | The versions of these OTP Applications that are required are detailed 27 | in the sinan.config located in the top level of the sinan project. If 28 | no version is specified there then any version will do quite well. 29 | 30 | Building the System 31 | ------------------- 32 | 33 | Make sure you are in the root of the system. Then run the following 34 | command: 35 | 36 | $> make build 37 | 38 | Assuming everything built correctly you are ready to move on to the 39 | next step. 40 | 41 | Testing The System 42 | ------------------ 43 | 44 | You may, at your discression, run sinan's test suite. There are four 45 | different kinds of tests in Sinan and all four sets of tests must pass 46 | for for the built version of Sinan to be considered stable. To run 47 | these tests enter the following commands on the command line. 48 | 49 | $> make eunit 50 | $> make cucumber 51 | $> make proper 52 | $> make smoketests 53 | 54 | The smoke tests have a lot of output. Its actually easier to see 55 | failures if you pipe standard out into /dev/null and then look for 56 | errors from standard error as follows: 57 | 58 | $> make smoketests > /dev/null 59 | 60 | With this command all errors in the smoketest should be immediately 61 | obvious. 62 | 63 | Assuming all of these tests pass you are ready to move on to 64 | packaging. 65 | 66 | Packaging Sinan as an Escript 67 | ----------------------------- 68 | 69 | As with everything else you can package sinan simply by running the 70 | following command: 71 | 72 | $> make escript 73 | 74 | This will run Sinan's escript task on Sinan itself producing a 'sinan' 75 | escript. The location of the escript will be printed as the last line 76 | of output after you run the command. 77 | 78 | Starting Development 79 | -------------------- 80 | 81 | Checkout the 82 | [sinan wiki](https://github.com/erlware/sinan/wiki/_pages) for more 83 | information about sinan and how to get started with it. Also checkout 84 | the 85 | [Building From Source](https://github.com/erlware/sinan/wiki/BuildingFromSource) 86 | document for expanded coverage of building sinan. 87 | -------------------------------------------------------------------------------- /src/sin_compile_erl.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Eric Merritt <> 3 | %%% @copyright (C) 2011, Eric Merritt 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 15 Apr 2011 by Eric Merritt <> 8 | %%%------------------------------------------------------------------- 9 | -module(sin_compile_erl). 10 | 11 | %% API 12 | -export([build_file/5, 13 | get_target/3, 14 | format_exception/1]). 15 | 16 | -include_lib("sinan/include/sinan.hrl"). 17 | 18 | %%%=================================================================== 19 | %%% API 20 | %%%=================================================================== 21 | get_target(BuildDir, File, ".erl") -> 22 | sin_task_build:get_target(File, ".erl", BuildDir, ".beam"). 23 | 24 | %% @doc Do the actual compilation on the file. 25 | -spec build_file(sin_config:matcher(), sin_state:state(), 26 | sin_file_info:mod(), [term()], string()) -> 27 | {module(), sin_config:config()}. 28 | build_file(Config, State, Module=#module{path=File}, Options, _Target) -> 29 | sin_log:verbose(Config, "Building ~s", [File]), 30 | case compile:file(File, Options) of 31 | {ok, ModuleName} -> 32 | reload_module(ModuleName), 33 | {State, [Module]}; 34 | {ok, ModuleName, []} -> 35 | reload_module(ModuleName), 36 | {State, [Module]}; 37 | {ok, ModuleName, Warnings0} -> 38 | reload_module(ModuleName), 39 | Warnings1 = sin_task_build:gather_fail_info(Warnings0, "warning"), 40 | {?WARN(State, 41 | {build_warnings, Warnings1}), 42 | [Module]}; 43 | {error, Errors, Warnings} -> 44 | ErrorStrings = 45 | lists:flatten([sin_task_build:gather_fail_info(Errors, 46 | "error"), 47 | sin_task_build:gather_fail_info(Warnings, 48 | "warning")]), 49 | sin_log:normal(Config, lists:flatten(ErrorStrings)), 50 | ?SIN_RAISE(State, {build_error, error_building_erl_file, File}); 51 | error -> 52 | sin_log:normal(Config, "Unknown error occured during build"), 53 | ?SIN_RAISE(State, {build_error, error_building_erl_file, File}) 54 | end. 55 | 56 | %% @doc Format an exception thrown by this module 57 | -spec format_exception(sin_exceptions:exception()) -> 58 | string(). 59 | format_exception(Exception) -> 60 | sin_exceptions:format_exception(Exception). 61 | %%%=================================================================== 62 | %%% Internal functions 63 | %%%=================================================================== 64 | %% We don't want to do this on ourselves 65 | -spec reload_module(atom()) -> {ok, atom()}. 66 | reload_module(Module) 67 | when not Module == ?MODULE -> 68 | code:purge(Module), 69 | code:load_file(Module), 70 | code:purge(Module), 71 | code:load_file(Module); 72 | reload_module(_) -> 73 | ok. 74 | 75 | -------------------------------------------------------------------------------- /test/sint_escript_exe.erl: -------------------------------------------------------------------------------- 1 | -module(sint_escript_exe). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | -export([given/3, 'when'/3, then/3]). 6 | 7 | given([a,generated,project], _State, _) -> 8 | sint_test_project_gen:a_generated_project("sin_escript_exe_proj"); 9 | given([a, escript, file, that, should, become, the, base], 10 | State = {ProjectDir, ProjectName}, _) -> 11 | AppBin =filename:join([ProjectDir, "bin"]), 12 | ok = ec_file:mkdir_path(AppBin), 13 | ?assertMatch(ok, 14 | file:write_file(filename:join([AppBin, "test_escript"]), 15 | escript_contents(ProjectName, false))), 16 | {ok, State}; 17 | given([a, project, name, named, module, with, a, main, function], 18 | State = {ProjectDir, ProjectName}, _) -> 19 | AppSrc = filename:join([ProjectDir, "src"]), 20 | ?assertMatch(ok, 21 | file:write_file(filename:join([AppSrc, ProjectName ++ ".erl"]), 22 | escript_contents(ProjectName, true))), 23 | {ok, State}; 24 | given([a, escript, directive, in, the, build, config], 25 | State = {ProjectDir, ProjectName}, _) -> 26 | ?assertMatch(ok, 27 | file:write_file(filename:join(ProjectDir, "sinan.config"), 28 | escript_directive(ProjectName))), 29 | {ok, State}. 30 | 31 | 32 | 33 | 'when'([an, escript, step, is, run, on, this, project], 34 | {ProjectDir, ProjectName}, _) -> 35 | Ret = sinan:run_sinan(["-s", ProjectDir, "escript"]), 36 | ?assertMatch({_, _}, Ret), 37 | {ok, {ProjectDir, ProjectName, Ret}}. 38 | 39 | then([produce, an, executable, escript], 40 | State = {_ProjectDir, ProjectName, Ret}, _) -> 41 | BuildDir = sin_state:get_value(build_dir, Ret), 42 | EscriptPath = filename:join([BuildDir, "escript", ProjectName]), 43 | ?assertMatch(true, sin_utils:file_exists(Ret, EscriptPath)), 44 | ?assertMatch("We are running!", os:cmd(EscriptPath)), 45 | {ok, State}; 46 | then([build, the, project, normally], 47 | {ProjectDir, ProjectName, Ret}, _) -> 48 | ?assertMatch({ok, _}, Ret), 49 | {ok, NewRet} = Ret, 50 | {ok, {ProjectDir, ProjectName, NewRet}}. 51 | 52 | escript_contents(ProjectName, Module) -> 53 | Header = case Module of 54 | true -> 55 | ["-module(", ProjectName, ").\n", 56 | "-export([main/1]).\n"]; 57 | false -> 58 | "" 59 | end, 60 | lists:flatten([Header, 61 | "main(_) -> \n" 62 | " io:format(\"We are running!\").\n"]). 63 | 64 | escript_directive(ProjectName) -> 65 | lists:flatten(["{project_name, ", ProjectName, "}.\n" 66 | "{project_vsn, \"0.1.0\"}.\n" 67 | "\n" 68 | "{build_dir, \"_build\"}.\n" 69 | "\n" 70 | "{ignore_dirs, [\"_\", \".\"]}.\n" 71 | "\n" 72 | "{ignore_apps, []}. \n" 73 | "\n", 74 | "{escript, [{source, \"bin/test_escript\"}]}.\n"]). 75 | -------------------------------------------------------------------------------- /smoketests/tests/test_dir_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sin_testing as st 3 | import pexpect 4 | import os 5 | 6 | class TestFail(st.SmokeTest): 7 | 8 | 9 | @st.sinan("build") 10 | def build(self, child, app_desc): 11 | if not os.path.isfile(os.path.join(os.getcwd(), 12 | "test", "test_module.erl")): 13 | raise "Nome module file" 14 | 15 | child.expect(pexpect.EOF) 16 | 17 | if not os.path.isfile(os.path.join(os.getcwd(), 18 | "_build", app_desc.project_name, 19 | "lib", app_desc.project_name + "-" + 20 | app_desc.project_version, "ebin", 21 | "test_module.beam")): 22 | raise "File Not Built" 23 | 24 | 25 | def output_testdir(self): 26 | path = os.path.join(os.getcwd(), "test") 27 | try: 28 | os.makedirs(path) 29 | except OSError as exc: 30 | if exc.errno == errno.EEXIST: 31 | pass 32 | else: raise 33 | 34 | Module = """ 35 | -module(test_module). 36 | 37 | -export([test/0]). 38 | 39 | test() -> 40 | ok.""" 41 | 42 | module_file = os.path.join(path, "test_module.erl") 43 | new_file = open(module_file, "w") 44 | new_file.write(Module) 45 | new_file.close() 46 | 47 | @st.sinan("gen foo") 48 | def run_custom_gen(self, child, appdesc): 49 | child.expect("your name> ") 50 | child.sendline(appdesc.user_name) 51 | child.expect("your email> ") 52 | child.sendline(appdesc.email) 53 | child.expect('copyright holder \("%s"\)> ' % appdesc.user_name) 54 | child.sendline() 55 | child.expect('project version> ') 56 | child.sendline(appdesc.project_version) 57 | child.expect('Please specify the ERTS version \(".*"\)> ') 58 | child.sendline() 59 | child.expect('Is this a single application project \("n"\)> ') 60 | child.sendline("y") 61 | child.expect('Would you like a build config\? \("y"\)> ') 62 | child.sendline() 63 | child.expect("Project was created, you should be good to go!") 64 | child.expect(pexpect.EOF) 65 | 66 | 67 | def test_gen_name(self): 68 | appdesc = st.AppDesc(user_name = "Smoke Test Gen", 69 | email = "noreply@erlware.org", 70 | copyright_holder = "Smoke Test Copy, LLC.", 71 | # This needs to match the gen name since 72 | # we are overriding it 73 | project_name = "foo", 74 | project_version = "0.134.0.0") 75 | 76 | self.run_custom_gen(appdesc) 77 | 78 | currentdir = os.getcwd() 79 | projdir = os.path.join(currentdir, appdesc.project_name) 80 | os.chdir(projdir) 81 | 82 | self.output_testdir() 83 | self.build(appdesc) 84 | 85 | os.chdir(currentdir) 86 | 87 | if __name__ == '__main__': 88 | unittest.main() 89 | -------------------------------------------------------------------------------- /src/sin_task_shell.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @author Eric Merritt 4 | %%% @doc 5 | %%% Starts a shell with the correct code paths. 6 | %%% @end 7 | %%% @copyright (C) 2006-2011 Erlware 8 | %%%--------------------------------------------------------------------------- 9 | -module(sin_task_shell). 10 | 11 | -behaviour(sin_task). 12 | 13 | -include_lib("eunit/include/eunit.hrl"). 14 | -include_lib("sinan/include/sinan.hrl"). 15 | 16 | %% API 17 | -export([description/0, do_task/2]). 18 | 19 | -define(TASK, shell). 20 | -define(DEPS, [build]). 21 | 22 | %%==================================================================== 23 | %% API 24 | %%==================================================================== 25 | %% @doc return a description of this task 26 | -spec description() -> sin_task:task_description(). 27 | description() -> 28 | 29 | Desc = " 30 | shell Task 31 | ========== 32 | 33 | The repl is an extraordinarily important part of any Erlang 34 | development project. This command helps the developer by starting up an 35 | erlang shell with all the paths to code utilized within the project already 36 | set. This does not, however, attempt to start any of that code. That is left 37 | up to the developer. 38 | 39 | Configuration Options 40 | --------------------- 41 | 42 | {kernel_start_args, [foo, shortnames]}. 43 | {cookie, my_secret}. 44 | 45 | kernel_start_args are the args that will be passed to net_kernel:start/1 46 | when the shell is started. cookie is the secret cookie to set. 47 | 48 | if you specify these values erlang will ensure epmd is started and start the 49 | networking kernel for erlang. Do not use these options if you do not want 50 | networking started.", 51 | 52 | #task{name = ?TASK, 53 | task_impl = ?MODULE, 54 | bare = false, 55 | deps = ?DEPS, 56 | example = "shell", 57 | desc = Desc, 58 | short_desc = "Provides an erlang repl on the project", 59 | opts = []}. 60 | 61 | %% @doc Run the shell command. 62 | -spec do_task(sin_config:config(), sin_state:state()) -> sin_state:state(). 63 | do_task(Config, State) -> 64 | case Config:match(kernel_start_args, undefined) of 65 | undefined -> 66 | ok; 67 | NodeName -> 68 | os:cmd("epmd -daemon"), 69 | net_kernel:start(NodeName), 70 | case Config:match(cookie, undefined) of 71 | undefined -> 72 | ok; 73 | Cookie -> 74 | erlang:set_cookie(node(), Cookie) 75 | end 76 | end, 77 | lists:foreach(fun(#app{path=Path}) -> 78 | Ebin = filename:join(Path, "ebin"), 79 | true = code:add_patha(Ebin) 80 | end, sin_state:get_value(release_deps, State)), 81 | 82 | shell:server(false, false), 83 | State. 84 | 85 | %%==================================================================== 86 | %%% Internal functions 87 | %%==================================================================== 88 | -------------------------------------------------------------------------------- /src/sin_compile_yrl.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Eric Merritt <> 3 | %%% @copyright (C) 2011, Eric Merritt 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 15 Apr 2011 by Eric Merritt <> 8 | %%%------------------------------------------------------------------- 9 | -module(sin_compile_yrl). 10 | 11 | %% API 12 | -export([build_file/5, 13 | get_target/3, 14 | format_exception/1]). 15 | 16 | -include_lib("sinan/include/sinan.hrl"). 17 | 18 | %%%=================================================================== 19 | %%% API 20 | %%%=================================================================== 21 | get_target(BuildDir, File, ".yrl") -> 22 | sin_task_build:get_target(File, ".yrl", BuildDir, ".beam"). 23 | 24 | build_file(Config, State0, Module=#module{path=File}, Options, Target) -> 25 | ErlFile = filename:basename(File, ".yrl"), 26 | AppDir = filename:dirname(Target), 27 | ErlTarget = filename:join([AppDir,"src"]), 28 | ErlName = filename:join([ErlTarget, 29 | lists:flatten([ErlFile, ".erl"])]), 30 | sin_log:verbose(Config, "Building ~s", [File]), 31 | {State2, ErlModule1} = 32 | case yecc:file(File, [{parserfile, ErlName} | 33 | sin_task_build:strip_options(Options)]) of 34 | {ok, _ModuleName} -> 35 | {State1, ErlModule0} = sin_file_info:process_file(State0, File, []), 36 | sin_compile_erl:build_file(Config, State1, 37 | rename_path(ErlModule0, ErlName), 38 | Options, Target); 39 | {ok, _ModuleName, []} -> 40 | {State1, ErlModule0} = sin_file_info:process_file(State0, File, []), 41 | sin_compile_erl:build_file(Config, State1, rename_path(ErlModule0, ErlName), 42 | Options, Target); 43 | {ok, _ModuleName, Warnings0} -> 44 | Warnings1 = sin_task_build:gather_fail_info(Warnings0, "warning"), 45 | ?SIN_RAISE(State0, {build_error, error_building_yecc, File, Warnings1}); 46 | {error, Errors, Warnings} -> 47 | Errors = lists:flatten([sin_task_build:gather_fail_info(Errors, 48 | "error"), 49 | sin_task_build:gather_fail_info(Warnings, 50 | "warning")]), 51 | ?SIN_RAISE(State0, {build_error, error_building_yecc, File}) 52 | end, 53 | {State2, [ErlModule1, Module]}. 54 | 55 | rename_path(Mod, NewPath) -> 56 | Mod#module{path=NewPath}. 57 | 58 | %% @doc Format an exception thrown by this module 59 | -spec format_exception(sin_exceptions:exception()) -> 60 | string(). 61 | format_exception(Exception) -> 62 | sin_exceptions:format_exception(Exception). 63 | %%%=================================================================== 64 | %%% Internal functions 65 | %%%=================================================================== 66 | -------------------------------------------------------------------------------- /smoketests/tests/configless_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sin_testing as st 3 | import os 4 | import shutil 5 | import pexpect 6 | 7 | class TestConfigless(st.SmokeTest): 8 | 9 | # clean the project 10 | @st.sinan("-p ctest_project -n 0.1.0 clean") 11 | def clean_configless(self, child, appdesc): 12 | child.expect(pexpect.EOF) 13 | self.assertTrue(not os.path.isdir(os.path.join(os.getcwd(), "_build"))) 14 | return appdesc 15 | 16 | @st.sinan("-p ctest_project -n 0.1.0 build") 17 | def build_configless(self, child, app_desc): 18 | child.expect(pexpect.EOF) 19 | 20 | build_dir = os.path.join(os.getcwd(), 21 | "_build", 22 | "ctest_project", 23 | "lib") 24 | 25 | self.assertTrue(os.path.isdir(build_dir)) 26 | 27 | for n in app_desc.app_names: 28 | app_dir = os.path.join(build_dir, "%s-0.1.0" % n) 29 | self.assert_dirs_exist(app_dir, 30 | "ebin", 31 | "src", 32 | "include", 33 | "doc") 34 | 35 | self.assert_files_exist(app_dir, 36 | [ "src", n + "_sup.erl"], 37 | ["src", n + "_app.erl"], 38 | ["ebin", n + "_sup.beam"], 39 | ["ebin", n + "_app.beam"]) 40 | 41 | 42 | def test_configless_project(self): 43 | app_desc = st.AppDesc(user_name = "Smoke Test User", 44 | email = "noreply@erlware.org", 45 | copyright_holder = "Smoke Test Copy, LLC.", 46 | project_name = "configless_project", 47 | project_version = "0.1.0.0", 48 | app_names = ["app1", "app2", "app3"]) 49 | 50 | self.do_run(app_desc) 51 | 52 | os.remove(os.path.join("sinan.config")) 53 | 54 | self.clean_configless(app_desc) 55 | self.build_configless(app_desc) 56 | 57 | 58 | def test_configless_single_app(self): 59 | app_desc = st.AppDesc(user_name = "Smoke Test User", 60 | email = "noreply@erlware.org", 61 | copyright_holder = "Smoke Test Copy, LLC.", 62 | project_name = "app1", 63 | project_version = "0.1.0.0", 64 | app_names = ["app1"]) 65 | 66 | self.do_run(app_desc) 67 | 68 | shutil.move(os.path.join("lib", "app1", "src"), 69 | ".") 70 | 71 | shutil.move(os.path.join("lib", "app1", "include"), 72 | ".") 73 | 74 | shutil.move(os.path.join("lib", "app1", "ebin"), 75 | ".") 76 | 77 | shutil.rmtree(os.path.join("lib")) 78 | os.remove(os.path.join("sinan.config")) 79 | 80 | self.do_clean(app_desc) 81 | 82 | self.do_build(app_desc) 83 | 84 | 85 | if __name__ == '__main__': 86 | unittest.main() 87 | -------------------------------------------------------------------------------- /smoketests/tests/release_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sin_testing as st 3 | import os 4 | import pexpect 5 | 6 | class TestFoo(st.SmokeTest): 7 | 8 | 9 | 10 | def gen_release_file(self, app_desc): 11 | contents = """ 12 | {project_name, %(project_name)s}. 13 | {project_vsn, "%(project_version)s"}. 14 | 15 | {{dep_constraints, [{release, r1}]}, 16 | [{exclude, app4}]}. 17 | {{dep_constraints, [{release, r2}]}, 18 | [{exclude, app4}, 19 | {exclude, app1}]}. 20 | {{dep_constraints, [{release, r3}]}, 21 | [{exclude, app4}, 22 | {exclude, app1}]}. 23 | {{dep_constraints, [{release, r4}]}, 24 | [{exclude, app4}, 25 | {exclude, app1}]}. 26 | 27 | {{include_erts, [{release, r1}]}, true}. 28 | """ % {"project_name" : app_desc.project_name, 29 | "project_version" : app_desc.project_version} 30 | 31 | with open("sinan.config", "w") as f: 32 | f.write(contents) 33 | 34 | 35 | 36 | def test_foo(self): 37 | app_desc = st.AppDesc(user_name = "Smoke Test User", 38 | email = "noreply@erlware.org", 39 | copyright_holder = "Smoke Test Copy, LLC.", 40 | project_name = "reltest", 41 | project_version = "0.134.0.0", 42 | app_names = ["app1", "app2", "app3", "app4"]) 43 | 44 | 45 | self.do_run(app_desc) 46 | 47 | self.gen_release_file(app_desc) 48 | for r in ["r1", "r2", "r3", "r4"]: 49 | def run_dist(self, child): 50 | child.expect(pexpect.EOF) 51 | 52 | self.release_name = r 53 | self.release_version = app_desc.project_version 54 | 55 | ((st.sinan("-r %s dist" % r)(run_dist))(self)) 56 | 57 | self.assert_dirs_exist("_build", 58 | [r, "releases", 59 | self.release_version]) 60 | 61 | self.assert_files_exist("_build", 62 | [r, "releases", 63 | self.release_version, 64 | self.release_name + ".rel"]) 65 | 66 | self.assert_files_exist("_build", 67 | [r, "releases", 68 | self.release_version, 69 | self.release_name + ".boot"]) 70 | 71 | self.assert_files_exist("_build", 72 | [r, "releases", 73 | self.release_version, 74 | self.release_name + ".script"]) 75 | 76 | 77 | self.assert_files_exist(["tar", 78 | "%s-%s.tar.gz" % (self.release_name, 79 | self.release_version)]) 80 | 81 | BuildDir = os.path.join("_build", "r1") 82 | BuildDirList = os.listdir(BuildDir) 83 | self.assertTrue(len([x for x in BuildDirList if x.startswith("erts-")]) == 1) 84 | 85 | if __name__ == '__main__': 86 | unittest.main() 87 | -------------------------------------------------------------------------------- /src/sin_sh.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @author Dave Smith 4 | %%% @author Eric Merritt 5 | %%% @doc 6 | %%% Run sh command with reasonable return values. This was originally taken 7 | %%% from rebar and but has been much changed since then. 8 | %%% @end 9 | %%%--------------------------------------------------------------------------- 10 | -module(sin_sh). 11 | 12 | %% API 13 | -export([sh/3]). 14 | 15 | %%==================================================================== 16 | %% API 17 | %%==================================================================== 18 | %% 19 | %% Env = [{string(), Val}] 20 | %% Val = string() | false 21 | %% 22 | sh(Config, Command0, Options0) -> 23 | Options = [expand_sh_flag(V) 24 | || V <- proplists:compact(Options0)], 25 | 26 | Command = patch_on_windows(Command0, proplists:get_value(env, Options, [])), 27 | PortSettings = proplists:get_all_values(port_settings, Options) ++ 28 | [exit_status, {line, 16384}, use_stdio, stderr_to_stdout, hide], 29 | Port = open_port({spawn, Command}, PortSettings), 30 | sh_loop(Port, 31 | fun(Output, Acc) -> 32 | sin_log:normal(Config, Output), 33 | [Output | Acc] 34 | end, []). 35 | 36 | %%==================================================================== 37 | %% Internal Functions 38 | %%==================================================================== 39 | sh_loop(Port, Fun, Acc) -> 40 | receive 41 | {Port, {data, {eol, Line}}} -> 42 | sh_loop(Port, Fun, Fun(Line ++ "\n", Acc)); 43 | {Port, {data, {noeol, Line}}} -> 44 | sh_loop(Port, Fun, Fun(Line, Acc)); 45 | {Port, {exit_status, 0}} -> 46 | {ok, lists:flatten(lists:reverse(Acc))}; 47 | {Port, {exit_status, Rc}} -> 48 | {error, {Rc, lists:flatten(lists:reverse(Acc))}} 49 | end. 50 | 51 | %% We do the shell variable substitution ourselves on Windows and hope that the 52 | %% command doesn't use any other shell magic. 53 | patch_on_windows(Cmd, Env) -> 54 | case os:type() of 55 | {win32,nt} -> 56 | "cmd /q /c " 57 | ++ lists:foldl(fun({Key, Value}, Acc) -> 58 | expand_env_variable(Acc, Key, Value) 59 | end, Cmd, Env); 60 | _ -> 61 | Cmd 62 | end. 63 | 64 | %% 65 | %% Given env. variable FOO we want to expand all references to 66 | %% it in InStr. References can have two forms: $FOO and ${FOO} 67 | %% The end of form $FOO is delimited with whitespace or eol 68 | %% 69 | expand_env_variable(InStr, VarName, RawVarValue) -> 70 | case string:chr(InStr, $$) of 71 | 0 -> 72 | %% No variables to expand 73 | InStr; 74 | _ -> 75 | VarValue = re:replace(RawVarValue, "\\\\", "\\\\\\\\", [global]), 76 | %% Use a regex to match/replace: 77 | %% Given variable "FOO": match $FOO\s | $FOOeol | ${FOO} 78 | RegEx = io_lib:format("\\\$(~s(\\s|$)|{~s})", [VarName, VarName]), 79 | ReOpts = [global, {return, list}], 80 | re:replace(InStr, RegEx, [VarValue, "\\2"], ReOpts) 81 | end. 82 | 83 | expand_sh_flag({cd, _CdArg} = Cd) -> 84 | {port_settings, Cd}; 85 | expand_sh_flag({env, _EnvArg} = Env) -> 86 | {port_settings, Env}. 87 | -------------------------------------------------------------------------------- /src/sin_task_doc.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @author Eric Merritt 4 | %%% @doc 5 | %%% Creates edoc format documentation for the project 6 | %%% @end 7 | %%% @copyright (C) 2006-2011 Erlware 8 | %%%--------------------------------------------------------------------------- 9 | -module(sin_task_doc). 10 | 11 | -behaviour(sin_task). 12 | 13 | -include_lib("sinan/include/sinan.hrl"). 14 | 15 | %% API 16 | -export([description/0, 17 | do_task/2, 18 | format_exception/1]). 19 | 20 | -define(TASK, doc). 21 | -define(DEPS, [build]). 22 | 23 | %%==================================================================== 24 | %% API 25 | %%==================================================================== 26 | 27 | %% @doc return a description of this task for callers 28 | -spec description() -> sin_task:task_description(). 29 | description() -> 30 | 31 | Desc = " 32 | doc Task 33 | ======== 34 | 35 | This command runs Erlang's 36 | [Edoc](http://www.erlang.org/doc/apps/edoc/index.html) across all sources in the 37 | project. 38 | 39 | Output of the doc task will be as you expect in the docs directory of the build 40 | area of the relevent OTP App. Remember that Sinan never outputs generated files 41 | into the project itself.", 42 | 43 | #task{name = ?TASK, 44 | task_impl = ?MODULE, 45 | bare = false, 46 | deps = ?DEPS, 47 | example = "doc", 48 | short_desc = "Genarates edoc documentation for the project", 49 | desc = Desc, 50 | opts = []}. 51 | 52 | %% @doc run edoc on all applications 53 | -spec do_task(sin_config:config(), sin_state:state()) -> sin_state:state(). 54 | do_task(Config, State) -> 55 | sin_task:ensure_started(edoc), 56 | Apps = sin_state:get_value(project_apps, State), 57 | run_docs(Config, State, Apps), 58 | State. 59 | 60 | %% @doc Format an exception thrown by this module 61 | -spec format_exception(sin_exceptions:exception()) -> 62 | string(). 63 | format_exception(Exception) -> 64 | sin_exceptions:format_exception(Exception). 65 | 66 | %%==================================================================== 67 | %%% Internal functions 68 | %%==================================================================== 69 | 70 | %% @doc 71 | %% Run edoc on all the modules in all of the applications. 72 | -spec run_docs(sin_config:config(), 73 | sin_state:state(), [AppInfo::tuple()]) -> ok. 74 | run_docs(Config, State, [#app{name=AppName, path=Path} | T]) -> 75 | DocDir = filename:join([Path, "docs"]), 76 | filelib:ensure_dir(filename:join([DocDir, "tmp"])), 77 | 78 | try 79 | edoc:application(AppName, 80 | Path, 81 | get_options(Config:specialize([{app, AppName}]), 82 | DocDir)) 83 | catch 84 | throw:Error -> 85 | ?SIN_RAISE(State, Error) 86 | end, 87 | run_docs(Config, State, T); 88 | run_docs(_Config, _State, []) -> 89 | ok. 90 | 91 | get_options(Config, DocDir) -> 92 | Options = Config:match(edoc_options, []), 93 | case proplists:get_value(dir, Options) of 94 | undefined -> 95 | [{dir, DocDir} | Options]; 96 | _ -> 97 | Options 98 | end. 99 | 100 | -------------------------------------------------------------------------------- /doc/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page.title }} 5 | 6 | 7 | 8 | x 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |

Sinan - {{ page.title }}

20 |
21 | 22 | 29 |
30 | 31 | 38 | 39 |
40 | 41 | {{content}} 42 | 43 |
44 | 45 |
46 | 47 | 48 |
49 |
50 | 51 |
52 |
53 |
54 | 56 | 57 | Follow ericbmerritt on Twitter 59 | 60 |

61 | Eric B Merritt - CTO of Afiniate, Inc - is a veteran 62 | entrepreneur, author and public speaker. Eric is expert in 63 | the architecture, development and deployment of 64 | large-scale distributed systems on heterogeneous hardware, 65 | and the languages and platforms required to support 66 | them. His experience spans from IBM mainframe and 67 | mid-range systems, to distributed build systems and 68 | massive fleet deployment tools for Amazon.com, and to 69 | high-frequency trading systems and financial exchange 70 | systems for leading private brokerages. Eric is a 71 | co-author of the popular book “Erlang and OTP in Action”. 72 |

73 |
74 | 75 |
76 |

GitHub release template built with HTML5, CSS3 and JS 77 | by Fublo

78 |
79 |
80 |
81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/sin_task_eqc.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%------------------------------------------------------------------- 3 | %%%--------------------------------------------------------------------------- 4 | %%% @author Eric Merritt 5 | %%% @doc 6 | %%% Runs the 'proper' function on all modules in an application 7 | %%% if that function exits. 8 | %%% @end 9 | %%% @copyright (C) 2007-2011 Erlware 10 | %%%--------------------------------------------------------------------------- 11 | -module(sin_task_eqc). 12 | 13 | -behaviour(sin_task). 14 | 15 | -include_lib("sinan/include/sinan.hrl"). 16 | 17 | %% API 18 | -export([description/0, do_task/2]). 19 | 20 | -define(TASK, eqc). 21 | -define(DEPS, [build]). 22 | 23 | %%==================================================================== 24 | %% API 25 | %%==================================================================== 26 | 27 | %% @doc provides a description of this task 28 | -spec description() -> sin_task:task_description(). 29 | description() -> 30 | 31 | Desc = " 32 | eqc Task 33 | ======== 34 | 35 | This task runs QuviQ Quick Check on the project, running any eqc tests that it 36 | finds. Note that you *must* have a licensed version of Quick Check installed 37 | on the box for this to work.", 38 | 39 | #task{name = ?TASK, 40 | task_impl = ?MODULE, 41 | bare = false, 42 | deps = ?DEPS, 43 | example = "eqc", 44 | desc = Desc, 45 | short_desc = "Runs all of the existing quick check tests in the project", 46 | opts = []}. 47 | 48 | %% @doc run all tests for all modules in the system 49 | do_task(Config, State0) -> 50 | lists:foldl( 51 | fun(#app{name=AppName, properties=Props}, State1) -> 52 | Modules = proplists:get_value(modules, Props), 53 | io:format("Quick Check testing app ~p~n", [AppName]), 54 | case Modules == undefined orelse length(Modules) =< 0 of 55 | true -> 56 | sin_log:verbose(Config, "No modules defined for ~p.", 57 | [AppName]), 58 | State1; 59 | false -> 60 | run_module_tests(State1, filter_modules(Config, Modules)) 61 | end 62 | end, State0, sin_state:get_value(project_apps, State0)). 63 | 64 | 65 | %%==================================================================== 66 | %%% Internal functions 67 | %%==================================================================== 68 | 69 | filter_modules(Config, Modules) -> 70 | AdditionalArgs = Config:match(additional_args), 71 | case AdditionalArgs of 72 | [] -> 73 | Modules; 74 | FilterModules -> 75 | [Mod || Mod <- Modules, 76 | lists:member(atom_to_list(Mod#module.name), 77 | FilterModules)] 78 | end. 79 | 80 | %% @doc Run tests for each module that has a test/0 function 81 | -spec run_module_tests(sin_state:state(), [sin_file_info:mod()]) -> ok. 82 | run_module_tests(State0, AllModules) -> 83 | lists:foldl( 84 | fun(#module{name=Name, tags=Tags}, State1) -> 85 | case sets:is_element(eqc, Tags) of 86 | true -> 87 | case eqc:module(Name) of 88 | [] -> 89 | State1; 90 | Errors when is_list(Errors) -> 91 | sin_state:add_run_error(Name, eqc_failure, State1) 92 | end; 93 | _ -> 94 | State1 95 | end 96 | end, State0, AllModules). 97 | -------------------------------------------------------------------------------- /src/sin_task_proper.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%------------------------------------------------------------------- 3 | %%%--------------------------------------------------------------------------- 4 | %%% @author Eric Merritt 5 | %%% @doc 6 | %%% Runs the 'proper' function on all modules in an application 7 | %%% if that function exits. 8 | %%% @end 9 | %%% @copyright (C) 2007-2011 Erlware 10 | %%%--------------------------------------------------------------------------- 11 | -module(sin_task_proper). 12 | 13 | -behaviour(sin_task). 14 | 15 | -include_lib("sinan/include/sinan.hrl"). 16 | 17 | %% API 18 | -export([description/0, do_task/2]). 19 | 20 | -define(TASK, proper). 21 | -define(DEPS, [build]). 22 | 23 | %%==================================================================== 24 | %% API 25 | %%==================================================================== 26 | 27 | %% @doc provides a description of this task 28 | -spec description() -> sin_task:task_description(). 29 | description() -> 30 | 31 | Desc = " 32 | proper Task 33 | =========== 34 | 35 | [PropEr](https://github.com/manopapad/proper) is a Quick Check like testing 36 | framework for Erlang. This command runs all proper tests available in 37 | the project. ", 38 | 39 | #task{name = ?TASK, 40 | task_impl = ?MODULE, 41 | bare = false, 42 | deps = ?DEPS, 43 | example = "proper", 44 | desc = Desc, 45 | short_desc = "Runs all of the existing proper tests in the project", 46 | opts = []}. 47 | 48 | %% @doc run all tests for all modules in the system 49 | do_task(Config, State0) -> 50 | sin_task:ensure_started(proper), 51 | lists:foldl( 52 | fun(#app{name=AppName, properties=Props}, State1) -> 53 | Modules = proplists:get_value(modules, Props), 54 | sin_log:verbose(Config, "PropEr testing app ~p~n", [AppName]), 55 | case Modules == undefined orelse length(Modules) =< 0 of 56 | true -> 57 | sin_log:verbose(Config, "No modules defined for ~p.", 58 | [AppName]), 59 | State1; 60 | false -> 61 | run_module_tests(State1, filter_modules(Config, Modules)) 62 | end 63 | end, State0, sin_state:get_value(project_apps, State0)). 64 | 65 | 66 | %%==================================================================== 67 | %%% Internal functions 68 | %%==================================================================== 69 | 70 | filter_modules(Config, Modules) -> 71 | AdditionalArgs = Config:match(additional_args), 72 | case AdditionalArgs of 73 | [] -> 74 | Modules; 75 | FilterModules -> 76 | [Mod || Mod <- Modules, 77 | lists:member(atom_to_list(Mod#module.name), 78 | FilterModules)] 79 | end. 80 | 81 | %% @doc Run tests for each module that has a test/0 function 82 | -spec run_module_tests(sin_state:state(), [sin_file_info:mod()]) -> ok. 83 | run_module_tests(State0, AllModules) -> 84 | lists:foldl( 85 | fun(#module{name=Name, tags=Tags}, State1) -> 86 | case sets:is_element(proper, Tags) of 87 | true -> 88 | case proper:module(Name) of 89 | [] -> 90 | State1; 91 | Errors when is_list(Errors) -> 92 | sin_state:add_run_error(Name, proper_failure, State1) 93 | end; 94 | _ -> 95 | State1 96 | end 97 | end, State0, AllModules). 98 | -------------------------------------------------------------------------------- /test/sint_app_src.erl: -------------------------------------------------------------------------------- 1 | -module(sint_app_src). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | -include_lib("sinan/include/sinan.hrl"). 5 | 6 | -export([given/3, 'when'/3, then/3]). 7 | 8 | given([a, generated, project, that, contains, an, 'ebin/app'], 9 | _State, _) -> 10 | Result = {ok, {ProjectDir, ProjectName}} = sint_test_project_gen:a_generated_project(), 11 | AppEbin = filename:join([ProjectDir, "ebin", ProjectName ++ ".app"]), 12 | ?assertMatch(ok, file:write_file(AppEbin, 13 | sint_cuke_support_funs:app_src(ProjectName))), 14 | Result; 15 | given([a,generated,project,that,contains,an,'app.src'], _State, _) -> 16 | BaseDir = ec_file:mkdtemp(), 17 | ProjectName = "super_foo", 18 | {ProjectDir, _} = 19 | sint_test_project_gen:single_app_project(BaseDir, ProjectName), 20 | AppSrcPath = filename:join([ProjectDir, "src", 21 | ProjectName ++ ".app.src"]), 22 | ?assertMatch(ok, file:write_file(AppSrcPath, 23 | sint_cuke_support_funs:app_src(ProjectName))), 24 | {ok, {ProjectDir, ProjectName}}; 25 | given([does,'not',contain,an,'ebin/app'], 26 | State = {ProjectDir, ProjectName}, _) -> 27 | AppSrc = filename:join([ProjectDir, "ebin", ProjectName ++ ".app"]), 28 | ?assertMatch(ok, sint_cuke_support_funs:delete_if_exists(AppSrc)), 29 | {ok, State}; 30 | given([does,'not',contain,an,'app.src'], 31 | State = {ProjectDir, ProjectName}, _) -> 32 | AppSrc = filename:join([ProjectDir, "src", ProjectName ++ ".app.src"]), 33 | ?assertMatch(ok, sint_cuke_support_funs:delete_if_exists(AppSrc)), 34 | {ok, State}. 35 | 36 | 'when'([a, build, step, is, run, on, this, project], 37 | {ProjectDir, ProjectName}, _) -> 38 | Ret = sinan:run_sinan(["-s", ProjectDir, "build"]), 39 | ?assertMatch({_, _}, Ret), 40 | {_, TrueRet} = Ret, 41 | {ok, {ProjectDir, ProjectName, TrueRet}}. 42 | 43 | then([build, the, app, normally], State = {_, _, BuildState}, _) -> 44 | ?assertMatch([], sin_state:get_run_errors(BuildState)), 45 | {ok, State}; 46 | then([sinan, should, put, the, app, file, in, 'ebin/.app'], 47 | State = {_, ProjectName, BuildState}, _) -> 48 | verify_ebin_app(ProjectName, BuildState), 49 | {ok, State}; 50 | then([warn, the, user, that, the, 51 | 'ebin/app', is, being, ignored], 52 | State = {_, _, BuildState}, _) -> 53 | Warnings = sin_state:get_run_warnings(BuildState), 54 | ?assertMatch(true, 55 | lists:any(fun({sin_discover, Warning}) -> 56 | case Warning of 57 | "Unexpected ebin/.app overriding with src/app.src" -> 58 | true; 59 | _ -> 60 | false 61 | end; 62 | (_) -> 63 | false 64 | end, Warnings)), 65 | {ok, State}. 66 | 67 | verify_ebin_app(ProjectName, BuildState) -> 68 | App = sin_state:project_app_by_name(erlang:list_to_atom(ProjectName), 69 | BuildState), 70 | 71 | BasePath = filename:join([App#app.path, "ebin", ProjectName ++ 72 | ".app"]), 73 | ?assertMatch(true, 74 | sin_utils:file_exists(sin_state:new(), BasePath)), 75 | AppContents = file:consult(BasePath), 76 | AtomName = erlang:list_to_atom(ProjectName), 77 | ?assertMatch({ok, [{application, AtomName, _}]}, AppContents), 78 | {ok, [{_, _, Details}]} = AppContents, 79 | ?assertMatch({vsn, "0.1.0"}, lists:keyfind(vsn, 1, Details)), 80 | Details. 81 | -------------------------------------------------------------------------------- /doc/assets/css/skeleton/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | Your Page Title Here :) 12 | 13 | 14 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 |
46 |
47 |

Skeleton

48 |
Version 1.1
49 |
50 |
51 | 55 | 64 | 68 | 69 |
70 | 71 | 72 | 73 | 75 | 76 | 77 | 78 | 80 | 81 | -------------------------------------------------------------------------------- /doc/assets/css/code.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #ffffff; } 3 | .highlight .c { color: #999988; font-style: italic } /* Comment */ 4 | .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 5 | .highlight .k { font-weight: bold } /* Keyword */ 6 | .highlight .o { font-weight: bold } /* Operator */ 7 | .highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ 8 | .highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ 9 | .highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ 10 | .highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ 11 | .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #aa0000 } /* Generic.Error */ 14 | .highlight .gh { color: #999999 } /* Generic.Heading */ 15 | .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ 16 | .highlight .go { color: #888888 } /* Generic.Output */ 17 | .highlight .gp { color: #555555 } /* Generic.Prompt */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gu { color: #aaaaaa } /* Generic.Subheading */ 20 | .highlight .gt { color: #aa0000 } /* Generic.Traceback */ 21 | .highlight .kc { font-weight: bold } /* Keyword.Constant */ 22 | .highlight .kd { font-weight: bold } /* Keyword.Declaration */ 23 | .highlight .kn { font-weight: bold } /* Keyword.Namespace */ 24 | .highlight .kp { font-weight: bold } /* Keyword.Pseudo */ 25 | .highlight .kr { font-weight: bold } /* Keyword.Reserved */ 26 | .highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ 27 | .highlight .m { color: #009999 } /* Literal.Number */ 28 | .highlight .s { color: #bb8844 } /* Literal.String */ 29 | .highlight .na { color: #008080 } /* Name.Attribute */ 30 | .highlight .nb { color: #999999 } /* Name.Builtin */ 31 | .highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ 32 | .highlight .no { color: #008080 } /* Name.Constant */ 33 | .highlight .ni { color: #800080 } /* Name.Entity */ 34 | .highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ 35 | .highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ 36 | .highlight .nn { color: #555555 } /* Name.Namespace */ 37 | .highlight .nt { color: #000080 } /* Name.Tag */ 38 | .highlight .nv { color: #008080 } /* Name.Variable */ 39 | .highlight .ow { font-weight: bold } /* Operator.Word */ 40 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 41 | .highlight .mf { color: #009999 } /* Literal.Number.Float */ 42 | .highlight .mh { color: #009999 } /* Literal.Number.Hex */ 43 | .highlight .mi { color: #009999 } /* Literal.Number.Integer */ 44 | .highlight .mo { color: #009999 } /* Literal.Number.Oct */ 45 | .highlight .sb { color: #bb8844 } /* Literal.String.Backtick */ 46 | .highlight .sc { color: #bb8844 } /* Literal.String.Char */ 47 | .highlight .sd { color: #bb8844 } /* Literal.String.Doc */ 48 | .highlight .s2 { color: #bb8844 } /* Literal.String.Double */ 49 | .highlight .se { color: #bb8844 } /* Literal.String.Escape */ 50 | .highlight .sh { color: #bb8844 } /* Literal.String.Heredoc */ 51 | .highlight .si { color: #bb8844 } /* Literal.String.Interpol */ 52 | .highlight .sx { color: #bb8844 } /* Literal.String.Other */ 53 | .highlight .sr { color: #808000 } /* Literal.String.Regex */ 54 | .highlight .s1 { color: #bb8844 } /* Literal.String.Single */ 55 | .highlight .ss { color: #bb8844 } /* Literal.String.Symbol */ 56 | .highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ 57 | .highlight .vc { color: #008080 } /* Name.Variable.Class */ 58 | .highlight .vg { color: #008080 } /* Name.Variable.Global */ 59 | .highlight .vi { color: #008080 } /* Name.Variable.Instance */ 60 | .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ 61 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Getting Started 4 | --- 5 | 6 | 7 | What is Sinan 8 | ------------- 9 | 10 | Sinan is a build tool designed to build Erlang/OTP Projects. The 11 | output of the build system is OTP compliant Releases and 12 | Applications. Sinan leverages the metadata artifacts provided by OTP 13 | to do a good job building, testing, releasing, etc with very little or 14 | no additional input from the developer. 15 | 16 | More Information and FAQ 17 | ------------------------ 18 | 19 | Sinan has extensive further documentation in its 20 | [wiki on github](https://github.com/erlware/sinan/wiki). Check there 21 | for more information. 22 | 23 | The Sinan FAQ is available at 24 | [here](https://github.com/erlware/sinan/wiki/FAQ). 25 | 26 | ### The Community 27 | 28 | A community exists around Sinan and the other Erlware projects. You 29 | may participate in the community and ask questions by joining the 30 | [erlware-questions](http://groups.google.com/group/erlware-questions) 31 | mailing list. 32 | 33 | 34 | 35 | Generate A Project 36 | ------------------ 37 | 38 | You can skip this step if you already have project. If not continue. 39 | 40 | $> sinan gen foo 41 | 42 | This will take you through a series of quetions about yourself and the 43 | project. I have provided a series of answers here, that illustrate how 44 | to answer these questions. 45 | 46 | Building 47 | -------- 48 | 49 | Now just cd into your project top level directory and type 50 | 51 | sinan build 52 | 53 | This will give you an fully built project under the _build 54 | directory. You can then run the command 55 | 56 | sinan shell 57 | 58 | to get an erlang shell with all the paths pointing correctly to the 59 | various parts of your system. 60 | 61 | Other Interesting Things 62 | ------------------------ 63 | 64 | If you want to get adventurous you can run all the eunit tests in your 65 | app by running: 66 | 67 | sinan test 68 | 69 | and finially, if you want to package up a normal erlang release 70 | tarball you can run 71 | 72 | sinan dist 73 | 74 | The tarball will end up in 75 | 76 | /_build//tar/-.tar.gz 77 | 78 | To get a list of all tasks currently available run the command 79 | 80 | sinan help 81 | 82 | Hopefully thats enough to get you started, but sinan has many options 83 | to do various things with projects from small single app projects to 84 | very large multiple app projects. To get more information take a look 85 | at the sinan manual. 86 | 87 | Trouble Shooting 88 | ---------------- 89 | 90 | If you end up seeing an error that looks like the following: 91 | 92 | It looks like we couldn't satisfy all the dependency constraints We are 93 | going to search the space to see what the problem is but this could take a while 94 | Getting the powerset of all constraints 95 | Power set contains 4 elements 96 | Doing optimal sort of the power set 97 | Looking for the first passing constraint set 98 | Unable to resolve compile time dependencies, probably do to the 99 | following constraints: 100 | constraint on proper with constraints [proper] originating from these 101 | application(s) ['__top_level__'] constraint on eunit with constraints 102 | [eunit] originating from these application(s) ['__top_level__'] 103 | 104 | Its probably because you are missing some of the compile time 105 | dependencies that all sinan projects need. Unfortunately, we can't 106 | distribute these dependencies with sinan due to some of the 107 | limitations with escript. However, they have been packaged up for 108 | download [here](https://github.com/downloads/erlware/sinan). Unzip 109 | these applications into your erlang lib or into a location pointed to 110 | by ERL_LIB and it should solve this problem. 111 | -------------------------------------------------------------------------------- /src/sin_task_help.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @author Eric Merritt 4 | %%% @doc 5 | %%% Describes the extant tasks. 6 | %%% @end 7 | %%% @copyright (C) 2007-2011 Erlware 8 | %%%--------------------------------------------------------------------------- 9 | -module(sin_task_help). 10 | 11 | -behaviour(sin_task). 12 | 13 | -include_lib("sinan/include/sinan.hrl"). 14 | 15 | %% API 16 | -export([description/0, do_task/2]). 17 | 18 | -define(TASK, help). 19 | -define(DEPS, []). 20 | 21 | %%==================================================================== 22 | %% API 23 | %%==================================================================== 24 | %% @doc return a description of the task for callers 25 | -spec description() -> sin_task:task_description(). 26 | description() -> 27 | 28 | Desc = " 29 | help Task 30 | ========= 31 | 32 | Provides helpful information about the tasks available in Sinan and how to 33 | invoke them. ", 34 | 35 | #task{name = ?TASK, 36 | task_impl = ?MODULE, 37 | bare = true, 38 | deps = ?DEPS, 39 | desc = Desc, 40 | example = "help [command] | [ len ", 41 | short_desc = "Provides help information for the available tasks", 42 | opts = []}. 43 | 44 | %% @doc print out help text for everything in the system 45 | -spec do_task(sin_config:config(), sin_state:state()) -> sin_state:state(). 46 | do_task(Config, State) -> 47 | Tasks = sin_task:get_tasks() , 48 | Result = 49 | case Config:match(additional_args) of 50 | [] -> 51 | sinan:usage(), 52 | sin_log:normal(Config, " available commands are as follows~n"), 53 | sin_log:normal(Config, "~nfor more information run 'sinan help '~n~n"), 54 | 55 | TaskNames = 56 | lists:map(fun(Task) -> 57 | sin_log:normal(Config, " ~-20s: ~s", [Task#task.name, 58 | Task#task.short_desc]), 59 | Task#task.name 60 | end, 61 | Tasks), 62 | 63 | {command_list, TaskNames}; 64 | [Task] -> 65 | process_task_entry(Config, Task, Tasks), 66 | {help_detail, Task} 67 | end, 68 | sin_state:store(help_displayed, Result, State). 69 | 70 | %%==================================================================== 71 | %%% Internal functions 72 | %%==================================================================== 73 | 74 | %% @doc Prints out the task description. 75 | -spec process_task_entry(sin_config:config(), string(), 76 | sin_task:task_description()) -> ok. 77 | process_task_entry(Config, TaskName, Tasks) -> 78 | AtomName = list_to_atom(TaskName), 79 | ActualTask = lists:foldl(fun(Task, Acc) -> 80 | case Task#task.name == AtomName of 81 | true -> 82 | Task; 83 | false -> 84 | Acc 85 | end 86 | end, 87 | undefined, 88 | Tasks), 89 | case ActualTask of 90 | undefined -> 91 | sin_log:normal(Config, "~p: not found", [AtomName]); 92 | _ -> 93 | sin_log:normal(Config, "~nexample: sinan ~s", [ActualTask#task.example]), 94 | sin_log:normal(Config, ""), 95 | sin_log:normal(Config, ActualTask#task.desc) 96 | end. 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/sin_task_eunit.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%------------------------------------------------------------------- 3 | %%%--------------------------------------------------------------------------- 4 | %%% @author Eric Merritt 5 | %%% @doc 6 | %%% Runs the 'test' function on all modules in an application 7 | %%% if that function exits. 8 | %%% @end 9 | %%% @copyright (C) 2007-2011 Erlware 10 | %%%--------------------------------------------------------------------------- 11 | -module(sin_task_eunit). 12 | 13 | -behaviour(sin_task). 14 | 15 | -include_lib("sinan/include/sinan.hrl"). 16 | 17 | %% API 18 | -export([description/0, do_task/2]). 19 | 20 | -define(TASK, eunit). 21 | -define(DEPS, [build]). 22 | 23 | %%==================================================================== 24 | %% API 25 | %%==================================================================== 26 | 27 | %% @doc provides a description of this task 28 | -spec description() -> sin_task:task_description(). 29 | description() -> 30 | 31 | Desc = " 32 | eunit Task 33 | ========== 34 | 35 | This command searches the project for 36 | [Eunit](http://www.erlang.org/doc/apps/eunit/chapter.html) tests and runs any 37 | tests that it finds.", 38 | 39 | #task{name = ?TASK, 40 | task_impl = ?MODULE, 41 | bare = false, 42 | deps = ?DEPS, 43 | example = "eunit", 44 | desc = Desc, 45 | short_desc = "Runs all of the existing eunit unit tests in the project", 46 | opts = []}. 47 | 48 | %% @doc run all tests for all modules in the system 49 | do_task(Config, State0) -> 50 | sin_task:ensure_started(eunit), 51 | lists:foldl(fun(#app{name=AppName, modules=Modules}, State1) -> 52 | sin_log:verbose(Config, "Eunit testing app ~p~n", [AppName]), 53 | case Modules == undefined orelse length(Modules) =< 0 of 54 | true -> 55 | sin_log:verbose(Config, "No modules defined for ~p.", 56 | [AppName]), 57 | State1; 58 | false -> 59 | run_module_tests(Config, State1, filter_modules(Config, Modules)) 60 | end 61 | end, State0, sin_state:get_value(project_apps, State0)). 62 | 63 | %%==================================================================== 64 | %%% Internal functions 65 | %%==================================================================== 66 | filter_modules(Config, Modules) -> 67 | AdditionalArgs = Config:match(additional_args), 68 | case AdditionalArgs of 69 | [] -> 70 | Modules; 71 | FilterModules -> 72 | [Mod || Mod <- Modules, 73 | lists:member(atom_to_list(Mod), 74 | FilterModules)] 75 | end. 76 | 77 | %% @doc Run tests for each module that has a test/0 function 78 | -spec run_module_tests(sin_config:config(), 79 | sin_state:state(), [sin_file_info:mod()]) -> ok. 80 | run_module_tests(Config, State0, AllModules) -> 81 | lists:foldl(fun(Name, State1) -> 82 | case lists:member({test, 0}, Name:module_info(exports)) of 83 | true -> 84 | sin_log:normal(Config, "testing ~p", [Name]), 85 | case eunit:test(Name, 86 | [{verbose, Config:match(verbose, false)}]) of 87 | error -> 88 | sin_state:add_run_error(Name, eunit_failure, State1); 89 | _ -> 90 | State1 91 | end; 92 | _ -> 93 | State1 94 | end 95 | end, State0, AllModules). 96 | -------------------------------------------------------------------------------- /src/sin_task_dist.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @author Eric Merritt 4 | %%% @doc 5 | %%% Builds up a distributable application (in the sense of a unix application, 6 | %%% not an otp application). It looks for a top level bin and adds 7 | %%% all of the apps to the system. Its a little stupid right now, but as 8 | %%% I get a better understanding of my needs its usefulness will grow. 9 | %%% @end 10 | %%% @copyright (C) 2007-2011 Erlware 11 | %%%--------------------------------------------------------------------------- 12 | -module(sin_task_dist). 13 | 14 | -behaviour(sin_task). 15 | 16 | -include_lib("sinan/include/sinan.hrl"). 17 | 18 | %% API 19 | -export([description/0, do_task/2, format_exception/1]). 20 | 21 | -define(TASK, dist). 22 | -define(DEPS, [release]). 23 | 24 | %%==================================================================== 25 | %% API 26 | %%==================================================================== 27 | 28 | %% @doc provides a description of the sytem, for help and other reasons 29 | -spec description() -> sin_task:task_description(). 30 | description() -> 31 | 32 | Desc = " 33 | dist Task 34 | ========= 35 | 36 | This command packages up the release directory into a tarball that can be 37 | deployed in the standard erlang manner. Everything in the release dir 38 | ends up in the tarball.", 39 | 40 | #task{name = ?TASK, 41 | task_impl = ?MODULE, 42 | bare = false, 43 | deps = ?DEPS, 44 | example = "dist", 45 | short_desc = "Provides a standard erlang distribution tarball", 46 | desc = Desc, 47 | opts = []}. 48 | 49 | %% @doc Build a dist tarball for this project 50 | -spec do_task(sin_config:matcher(), sin_state:state()) -> sin_state:state(). 51 | do_task(Config, State) -> 52 | BuildRoot = sin_state:get_value(build_root, State), 53 | TarDir = filename:join([BuildRoot, "tar"]), 54 | filelib:ensure_dir(filename:join([TarDir, "tmp"])), 55 | ReleaseName = erlang:atom_to_list(sin_state:get_value(release, State)) ++ "-" 56 | ++ sin_state:get_value(release_vsn, State), 57 | BuildDir = sin_state:get_value(build_dir, State), 58 | List1 = gather_dirs(BuildDir, ReleaseName), 59 | create_tar_file(Config, State, filename:join([TarDir, 60 | lists:flatten([ReleaseName, ".tar.gz"])]), 61 | List1), 62 | State. 63 | 64 | 65 | %% @doc Format an exception thrown by this module 66 | -spec format_exception(sin_exceptions:exception()) -> 67 | string(). 68 | format_exception(Exception) -> 69 | sin_exceptions:format_exception(Exception). 70 | 71 | %%==================================================================== 72 | %%% Internal functions 73 | %%==================================================================== 74 | 75 | %% @doc Actually create the tar file and write in all of the contents. 76 | -spec create_tar_file(sin_config:config(), sin_state:state(), string(), [string()]) -> 77 | ok. 78 | create_tar_file(Config, State, FileName, TarContents) -> 79 | case erl_tar:open(FileName, [compressed, write]) of 80 | Error = {error, _} -> 81 | sin_log:log(Config, "Unable to open tar file ~s, unable to build " 82 | "distribution.", [FileName]), 83 | ?SIN_RAISE(State, {unable_to_build_dist, Error}); 84 | {ok, Tar} -> 85 | lists:foreach(fun({Name, NewName}) -> 86 | erl_tar:add(Tar, Name, NewName, 87 | [dereference]) 88 | end, TarContents), 89 | 90 | erl_tar:close(Tar) 91 | end. 92 | 93 | %% @doc Gather up the applications and return a list of {DirName, InTarName} 94 | %% pairs. 95 | -spec gather_dirs(string(), string()) -> 96 | [{string(), string()}]. 97 | gather_dirs(ActualDir, TarDir) -> 98 | {ok, Files} = file:list_dir(ActualDir), 99 | [{filename:join(ActualDir, File), filename:join(TarDir, filename:basename(File))} || 100 | File <- Files, 101 | File =/= ".sig"]. 102 | 103 | -------------------------------------------------------------------------------- /src/sin_topo.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%------------------------------------------------------------------- 3 | %%% @author Joe Armstrong 4 | %%% @author Eric Merritt 5 | %%% @doc 6 | %%% This is a pretty simple topological sort for erlang. It 7 | %%% was originally written for ermake by Joe Armstrong back in '98. 8 | %%% -- 9 | %%% -type([{X, X}]) -> {ok, [{X,Y}]} | {cycle, [{X,Y}]} 10 | %%% topological_sort:pairs(L) 11 | %%% 12 | %%% A partial order on the set S is a set of pairs {Xi,Xj} such that 13 | %%% some relation between Xi and Xj is obeyed. 14 | %%% 15 | %%% A topological sort of a partial order is a sequence of elements 16 | %%% [X1, X2, X3 ...] such that if whenever {Xi, Xj} is in the partial 17 | %%% order i < j 18 | %%% @end 19 | %%%------------------------------------------------------------------- 20 | -module(sin_topo). 21 | 22 | -include_lib("eunit/include/eunit.hrl"). 23 | 24 | -export([sort/1]). 25 | -export_type([pair/2]). 26 | 27 | %%==================================================================== 28 | %% Exported Types 29 | %%==================================================================== 30 | 31 | -type pair(T1, T2) :: {T1, T2}. 32 | 33 | %%==================================================================== 34 | %% API 35 | %%==================================================================== 36 | 37 | %% @doc Do a topological sort on the list of pairs. 38 | -spec sort([pair(T1, T2)]) -> {ok, [pair(T1, T2)]} | {cycle, [pair(T1, T2)]}. 39 | sort(Pairs) -> 40 | iterate(Pairs, [], all(Pairs)). 41 | 42 | %%==================================================================== 43 | %% Internal Functions 44 | %%==================================================================== 45 | 46 | %% @doc Iterate over the system. @private 47 | -spec iterate([pair(T1, T2)], [pair(T1, T2)], [pair(T1, T2)]) -> 48 | {ok, [pair(T1, T2)]}. 49 | iterate([], L, All) -> 50 | {ok, remove_duplicates(L ++ subtract(All, L))}; 51 | iterate(Pairs, L, All) -> 52 | case subtract(lhs(Pairs), rhs(Pairs)) of 53 | [] -> 54 | {cycle, Pairs}; 55 | Lhs -> 56 | iterate(remove_pairs(Lhs, Pairs), L ++ Lhs, All) 57 | end. 58 | 59 | all(L) -> 60 | lhs(L) ++ rhs(L). 61 | lhs(L) -> 62 | lists:map(fun({X,_}) -> X end, L). 63 | rhs(L) -> 64 | lists:map(fun({_,Y}) -> Y end, L). 65 | 66 | %% @doc all the elements in L1 which are not in L2 67 | %% @private 68 | -spec subtract([pair(T1, T2)], [pair(T1, T2)]) -> [pair(T1, T2)]. 69 | subtract(L1, L2) -> 70 | lists:filter(fun(X) -> 71 | not lists:member(X, L2) 72 | end, L1). 73 | 74 | %% @doc remove dups from the list. @private 75 | -spec remove_duplicates([pair(T1, T2)]) -> [pair(T1, T2)]. 76 | remove_duplicates([H|T]) -> 77 | case lists:member(H, T) of 78 | true -> 79 | remove_duplicates(T); 80 | false -> 81 | [H|remove_duplicates(T)] 82 | end; 83 | remove_duplicates([]) -> 84 | []. 85 | 86 | %% @doc 87 | %% removes all pairs from L2 where the first element 88 | %% of each pair is a member of L1 89 | %% 90 | %% L2' L1 = [X] L2 = [{X,Y}]. 91 | %% @private 92 | -spec remove_pairs([pair(T1, T2)], [pair(T1, T2)]) -> [pair(T1, T2)]. 93 | remove_pairs(L1, L2) -> 94 | lists:filter(fun({X,_Y}) -> 95 | not lists:member(X, L1) 96 | end, L2). 97 | 98 | %%==================================================================== 99 | %% Tests 100 | %%==================================================================== 101 | topo_1_test() -> 102 | Pairs = [{1,2},{2,4},{4,6},{2,10},{4,8},{6,3},{1,3}, 103 | {3,5},{5,8},{7,5},{7,9},{9,4},{9,10}], 104 | ?assertMatch({ok, [1,7,2,9,4,6,3,5,8,10]}, 105 | sort(Pairs)). 106 | topo_2_test() -> 107 | Pairs = [{app2, app1}, {zapp1, app1}, {stdlib, app1}, 108 | {app3, app2}, {kernel, app1}, {kernel, app3}, 109 | {app2, zapp1}, {app3, zapp1}, {zapp2, zapp1}], 110 | ?assertMatch({ok, [stdlib, kernel, zapp2, 111 | app3, app2, zapp1, app1]}, 112 | sort(Pairs)). 113 | 114 | topo_3_test() -> 115 | Pairs = [{app2, app1}, {app1, app2}, {stdlib, app1}], 116 | ?assertMatch({cycle, [{app2, app1}, {app1, app2}]}, 117 | sort(Pairs)). 118 | 119 | -------------------------------------------------------------------------------- /src/sin_deps.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @copyright 2008 Anders Nygren 4 | %%% @author Anders Nygren 5 | %%% @doc Find the applications that an application or realease 6 | %%% is depending on. 7 | %%% @end 8 | %%%------------------------------------------------------------------- 9 | -module(sin_deps). 10 | 11 | %% API 12 | -export([app/1, 13 | rel/3]). 14 | 15 | -export([find/2]). 16 | 17 | -define(PRELOADED, [erlang, init, prim_file, erl_prim_loader]). 18 | 19 | %%==================================================================== 20 | %% API 21 | %%==================================================================== 22 | 23 | %% @doc Find all applications and their versions that have to be included in a 24 | %% release. 25 | -spec rel(LibDir::string(), ExtraApps::[atom()], 26 | ExcludeApps::[atom()]) -> list(). 27 | rel(LibDir, ExtraApps, Exclude) -> 28 | MyApps = find_apps(LibDir), 29 | AppsInPath = lists:foldl(fun (D, Acc) -> 30 | case find_app(D) of 31 | {'',"."} -> Acc; 32 | App -> [App|Acc] 33 | end 34 | end, 35 | [],code:get_path()), 36 | Extras = lists:map(fun(App) -> 37 | {value,Val}= lists:keysearch(App, 1, 38 | AppsInPath), 39 | Val 40 | end, ExtraApps), 41 | InitApps = MyApps++Extras, 42 | lists:sort(app_deps(InitApps, Exclude)). 43 | 44 | app_deps(InitApps, Exclude) -> 45 | As = [read_app_file(App, EbinDir) || {App, EbinDir} <- InitApps], 46 | Acc= [{App, Vsn} || {app, App, Vsn, _Modules} <-As], 47 | app_deps(InitApps, Exclude, Acc). 48 | 49 | app_deps(Apps, Exclude, Done) -> 50 | lists:foldl( 51 | fun(App, Acc) -> 52 | Deps = app(App), 53 | Deps1 = [{A,Vsn}||{A,Vsn}<-Deps, 54 | not lists:keymember(A, 1, Acc), 55 | not lists:member(A, Exclude)], 56 | Deps1 ++ Acc 57 | end, Done, Apps). 58 | 59 | %% @doc Find all applications and their versions that an application depends on. 60 | -spec app(App::atom()) -> Result::[{App::atom(), Vsn::string()}]. 61 | app(App) when is_atom(App) -> 62 | [D] = find(".", "^"++atom_to_list(App)++".app$"), 63 | Dir = filename:dirname(D), 64 | app({App, Dir}); 65 | 66 | app({App, Dir}) when is_list(Dir) -> 67 | {app, App, _Vsn, Modules} = read_app_file(App, Dir), 68 | Ds = [lists:usort(get_deps(Module)) || Module <- Modules], 69 | R = lists:umerge(Ds), 70 | Exts = lists:subtract(R, Modules++?PRELOADED), 71 | AppFiles = lists:usort([mod_to_appfile(M) || M<-Exts]), 72 | [appfile_to_app(AppFile) || AppFile <- AppFiles]. 73 | 74 | %%==================================================================== 75 | %% Internal functions 76 | %%==================================================================== 77 | find_apps(LibDir) -> 78 | AppFiles = find(LibDir, "^.*.app$"), 79 | [split_app_path(AppFile) || AppFile <- AppFiles]. 80 | 81 | find_app(Dir) -> 82 | AppFile = find(Dir, "^.*.app$"), 83 | split_app_path(AppFile). 84 | 85 | split_app_path(Path) -> 86 | AppName = list_to_atom(filename:rootname(filename:basename(Path))), 87 | Ebin = filename:dirname(Path), 88 | {AppName, Ebin}. 89 | 90 | read_app_file(App, Dir) -> 91 | AppFile = filename:join(Dir, atom_to_list(App)++".app"), 92 | {ok, [{application, App, Attrs}]} = file:consult(AppFile), 93 | {value, {vsn, Vsn}} = lists:keysearch(vsn, 1, Attrs), 94 | {value, {modules, Modules}} = lists:keysearch(modules, 1, Attrs), 95 | {app, App, Vsn, Modules}. 96 | 97 | get_deps(Module) -> 98 | {ok,{_,[{imports,Is}]}} = beam_lib:chunks(code:which(Module),[imports]), 99 | lists:usort([Mod || {Mod, _Fun, _Ar} <- Is]). 100 | 101 | mod_to_appfile(Mod) -> 102 | case code:which(Mod) of 103 | non_existing -> 104 | {app_not_found, Mod}; 105 | preloaded -> 106 | {preloaded, Mod}; 107 | Path -> 108 | [AppFile]=find(filename:dirname(Path), "^.*.app$"), 109 | AppFile 110 | end. 111 | 112 | appfile_to_app({app_not_found,_App}=A) -> 113 | A; 114 | appfile_to_app(AppFile) -> 115 | {ok,[{application,App,Attrs}]} = file:consult(AppFile), 116 | {value,{vsn,Vsn}} = lists:keysearch(vsn,1,Attrs), 117 | {App, Vsn}. 118 | 119 | find(Root, RegExp) -> 120 | filelib:fold_files(Root, RegExp, true, fun (F,Acc) -> [F|Acc] end,[]). 121 | 122 | 123 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VSN=4.1.1 2 | ERLC=/usr/bin/env erlc 3 | ERL=/usr/bin/env erl 4 | 5 | RELDIR=$(CURDIR)/_build/sinan 6 | APPDIR=$(RELDIR)/lib/sinan-$(VSN) 7 | LOGDIR=$(RELDIR)/logs 8 | BINDIR=$(DESTDIR)/usr/bin 9 | INSTALL_TARGET=$(DESTDIR)/usr/lib/erlang/lib/sinan-$(VSN) 10 | 11 | TARBALL=../erlang-sinan_$(VSN).orig.tar.gz 12 | SRCDIR=src 13 | TESTDIR=test 14 | COPYDIRS= include src test 15 | BEAMDIR=$(APPDIR)/ebin 16 | SMOKETEST_DIR=$(CURDIR)/smoketests 17 | PYPATH=$(PYTHONPATH):$(SMOKETEST_DIR) 18 | BEHAVIOURS= src/sin_task.erl src/sin_dep_resolver.erl 19 | 20 | .PHONY=all setup build escript cucumber proper eunit dialyzer \ 21 | run debug smoketests testall gh-pages clean install-deb \ 22 | build-deb publish-ppa update-version 23 | 24 | SINFLAGS=-s $(CURDIR) -p sinan -n $(VSN) 25 | ERLFLAGS= -noinput -pa $(BEAMDIR) 26 | 27 | RSYNC_OPTIONS=-vaz --delete 28 | .SUFFIXES: .erl .beam .yrl 29 | 30 | vpath %.erl src test 31 | 32 | ERL_OBJ = $(patsubst src/%.erl,$(BEAMDIR)/%.beam, $(wildcard $(SRCDIR)/*erl)) 33 | ERL_TEST_OBJ = $(patsubst test/%.erl,$(BEAMDIR)/%.beam, $(wildcard $(TESTDIR)/*erl)) 34 | 35 | all: main 36 | 37 | setup: 38 | for f in $(COPYDIRS) ; do \ 39 | mkdir -p $(APPDIR)/$$f ; \ 40 | rsync $(RSYNC_OPTIONS) $$f $(APPDIR); \ 41 | done 42 | mkdir -p $(APPDIR)/ebin; 43 | rsync $(RSYNC_OPTIONS) ebin/sinan.app $(APPDIR)/ebin/sinan.app 44 | 45 | build_behaviours: $(BEHAVIOURS) 46 | # make sure sin_task gets built first so its always available 47 | $(ERLC) -pa $(BEAMDIR) +warn_export_vars +warn_export_all \ 48 | +warn_obsolete_guard \ 49 | +warnings_as_errors +bin_opt_info +debug_info -W -o $(BEAMDIR) $(BEHAVIOURS) 50 | 51 | main: setup build_behaviours ${ERL_OBJ} ${ERL_TEST_OBJ} 52 | 53 | $(BEAMDIR)/%.beam: %.erl 54 | $(ERLC) -pa $(BEAMDIR) +warn_export_vars +warn_export_all \ 55 | +warn_obsolete_guard \ 56 | +warnings_as_errors +bin_opt_info +debug_info -W -o $(BEAMDIR) $< 57 | 58 | build: main 59 | $(ERL) $(ERLFLAGS) -s sinan main -extra $(SINFLAGS) build 60 | 61 | escript: main 62 | $(ERL) $(ERLFLAGS) -s sinan main -extra $(SINFLAGS) escript 63 | 64 | cucumber: main 65 | $(ERL) $(ERLFLAGS) -s sinan main -extra $(SINFLAGS) cucumber 66 | 67 | proper: main 68 | $(ERL) $(ERLFLAGS) -s sinan main -extra $(SINFLAGS) proper 69 | 70 | eunit: main 71 | $(ERL) $(ERLFLAGS) -s sinan main -extra $(SINFLAGS) eunit 72 | 73 | dialyzer: main 74 | $(ERL) $(ERLFLAGS) -s sinan main -extra $(SINFLAGS) dialyzer 75 | 76 | run: main 77 | $(ERL) -pa $(BEAMDIR) 78 | 79 | debug: main 80 | $(ERL) $(ERLFLAGS) -s debugger start 81 | 82 | smoketests: main 83 | for f in $(wildcard $(SMOKETEST_DIR)/tests/*.py) ; do \ 84 | PYTHONPATH=$(PYPATH) python $$f ; \ 85 | done 86 | 87 | ct: main $(LOGDIR) 88 | ct_run -dir $(CURDIR)/test -logdir $(LOGDIR) -erl_args -pa $(BEAMDIR) 89 | 90 | testall : cucumber proper eunit smoketests 91 | 92 | gh-pages: 93 | ./do-gh-pages 94 | 95 | clean: 96 | rm -f ../erlang-sinan_*.debian.tar.gz 97 | rm -f ../erlang-sinan_*.dsc 98 | rm -f ../erlang-sinan_*.build 99 | rm -f ../erlang-sinan_*.changes 100 | rm -rf debian/patches 101 | rm -rf debian/erlang-sinan 102 | rm -rf $(CURDIR)/usr 103 | rm -rf _build 104 | rm -rf erl_crash.dump 105 | find smoketests -name \*.pyc -exec rm -f {} \; 106 | 107 | update-version: 108 | awk '{sub(/project_vsn, \"[0-9]+\.[0-9]+\.[0-9]+[a-z]?\"/,"project_vsn, \"$(VSN)\"");print}' sinan.config > tmp.txt 109 | @mv tmp.txt sinan.config 110 | awk '{sub(/vsn, \"[0-9]+\.[0-9]+\.[0-9]+[a-z]?\"/,"vsn, \"$(VSN)\"");print}' ebin/sinan.app > tmp.txt 111 | @mv tmp.txt ebin/sinan.app 112 | awk '{sub(/\"v[0-9]+\.[0-9]+\.[0-9]+[a-z]?\"/,"\"v$(VSN)\"");print}' src/sin_task_version.erl > tmp.txt 113 | @mv tmp.txt src/sin_task_version.erl 114 | git add sinan.config 115 | git add Makefile 116 | git add ebin/sinan.app 117 | git add src/sin_task_version.erl 118 | git commit -m "Version bump $(VSN)" 119 | git tag v$(VSN) 120 | 121 | $(LOGDIR): 122 | mkdir -p $(LOGDIR) 123 | 124 | 125 | ## 126 | ## Debian packaging support for sinan 127 | ## 128 | 129 | $(TARBALL): 130 | git archive --format=tar --prefix=sinan/ HEAD | gzip > $(TARBALL) 131 | 132 | install-deb: 133 | mkdir -p $(INSTALL_TARGET) 134 | mkdir -p $(BINDIR) 135 | cp -r $(APPDIR)/* $(INSTALL_TARGET) 136 | cp -r _build/sinan/escript/sinan $(BINDIR) 137 | 138 | build-deb: $(TARBALL) 139 | pdebuild 140 | debuild -S 141 | 142 | 143 | publish-ppa: build-deb 144 | dput -f ppa:afiniate/ppa ../erlang-sinan_$(VSN)_source.changes 145 | -------------------------------------------------------------------------------- /src/sin_hooks.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%------------------------------------------------------------------- 3 | %%% @author Eric Merritt 4 | %%% @copyright (C) 2009-2011 Eric Merritt 5 | %%% @doc 6 | %%% Provides a means of correctly creating eta pre/post task hooks 7 | %%% and executing those hooks that exist. 8 | %%% @end 9 | %%%------------------------------------------------------------------- 10 | -module(sin_hooks). 11 | 12 | -include_lib("sinan/include/sinan.hrl"). 13 | 14 | -define(NEWLINE, 10). 15 | -define(CARRIAGE_RETURN, 13). 16 | 17 | %% API 18 | -export([get_hooks_function/2, 19 | format_exception/1]). 20 | 21 | %%%=================================================================== 22 | %%% API 23 | %%%=================================================================== 24 | 25 | %% @doc Creats a function that can be used to run build hooks in the system. 26 | -spec get_hooks_function(sin_state:state(), 27 | ProjectRoot::string()) -> function(). 28 | get_hooks_function(State, ProjectRoot) -> 29 | HooksDir = filename:join([ProjectRoot, "_hooks"]), 30 | case sin_utils:file_exists(State, HooksDir) of 31 | false -> 32 | no_hooks; 33 | true -> 34 | gen_build_hooks_function(HooksDir) 35 | end. 36 | 37 | %% @doc Format an exception thrown by this module 38 | -spec format_exception(sin_exceptions:exception()) -> 39 | string(). 40 | format_exception(Exception) -> 41 | sin_exceptions:format_exception(Exception). 42 | 43 | %%%=================================================================== 44 | %%% Internal functions 45 | %%%=================================================================== 46 | 47 | %% @doc Generate a function that can be run pre and post task 48 | -spec gen_build_hooks_function(HooksDir::string()) -> function(). 49 | gen_build_hooks_function(HooksDir) -> 50 | fun(Type, Task, Config, State) -> 51 | do_hook(Config, State, Type, Task, HooksDir) 52 | end. 53 | 54 | %% @doc Setup to run the hook and run it if it exists. 55 | -spec do_hook(sin_config:config(), sin_state:state(), 56 | Type::atom(), Task::atom(), 57 | HooksDir::string()) -> ok. 58 | do_hook(Config, State, Type, Task, HooksDir) when is_atom(Task) -> 59 | HookName = atom_to_list(Type) ++ "_" ++ atom_to_list(Task), 60 | HookPath = filename:join(HooksDir, HookName), 61 | case sin_utils:file_exists(State, HookPath) of 62 | true -> 63 | sin_log:verbose(Config, "hook: ~s", [HookName]), 64 | run_hook(Config, State, HookPath, list_to_atom(HookName)); 65 | _ -> 66 | State 67 | end. 68 | 69 | %% @doc Setup the execution environment and run the hook. 70 | -spec run_hook(sin_config:config(), sin_state:state(), 71 | HookPath::list(), HookName::atom()) -> ok. 72 | run_hook(Config, State, HookPath, HookName) -> 73 | command(Config, State, HookPath, create_env(State), HookName). 74 | 75 | %% @doc create a minimal env for the hook from the state. 76 | -spec create_env(sin_state:state()) -> Env::[{string(), string()}]. 77 | create_env(State) -> 78 | Env = 79 | [{"SIN_RELEASE", 80 | erlang:atom_to_list(sin_state:get_value(release, State))}, 81 | {"SIN_RELEASE_VSN", sin_state:get_value(release_vsn, State)}, 82 | {"SIN_BUILD_ROOT", sin_state:get_value(build_root, State)}, 83 | {"SIN_BUILD_DIR", sin_state:get_value(build_dir, State)}, 84 | {"SIN_APPS_DIR", sin_state:get_value(apps_dir, State)}, 85 | {"SIN_RELEASE_DIR", sin_state:get_value(release_dir, State)}, 86 | {"SIN_HOME_DIR", sin_state:get_value(home_dir, State)}, 87 | {"SIN_PROJECT_DIR", sin_state:get_value(project_dir, State)}] ++ 88 | [[{"SIN_" ++ erlang:atom_to_list(Name) ++ 89 | "_VSN", Vsn}, 90 | {"SIN_" ++ erlang:atom_to_list(Name) ++ 91 | "_DIR", AppDir}] || 92 | #app{name=Name, vsn=Vsn, path=AppDir} 93 | <- sin_state:get_value(project_apps, [], State)], 94 | lists:flatten(Env). 95 | 96 | %% @doc Given a command an an environment run that command with the environment 97 | -spec command(sin_config:config(), sin_state:state(), Command::list(), Env::list(), 98 | HookName::atom()) -> list(). 99 | command(Config, State, Cmd, Env, HookName) -> 100 | Opt = [{env, Env}], 101 | case sin_sh:sh(Config, Cmd, Opt) of 102 | {ok, _} -> 103 | State; 104 | {error, Reason} -> 105 | ?SIN_RAISE(State, {error_running_hook, HookName, Reason}) 106 | end. 107 | -------------------------------------------------------------------------------- /src/sin_file_info.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @copyright Erlware, LLC 4 | %%% @author Eric Merritt 5 | %%% @author Anders Nygren 6 | %%% @doc Parse an erlang file (yrl, hrl, erl) and information about the file 7 | %%% that is relevant to the build system 8 | %%% @end 9 | %%%------------------------------------------------------------------- 10 | -module(sin_file_info). 11 | 12 | %% API 13 | -export([file_regex/0, process_file/3, initialize/2]). 14 | -export_type([date/0, time/0, date_time/0, mod/0]). 15 | 16 | -include_lib("sinan/include/sinan.hrl"). 17 | -include_lib("kernel/include/file.hrl"). 18 | 19 | 20 | %%==================================================================== 21 | %% Types 22 | %%==================================================================== 23 | -type date() :: {Year :: non_neg_integer(), 24 | Month :: non_neg_integer(), 25 | Day :: non_neg_integer()}. 26 | 27 | -type time() :: {Hour :: non_neg_integer(), 28 | Minute :: non_neg_integer(), 29 | Second :: non_neg_integer()}. 30 | 31 | -type date_time() :: {date() , time()}. 32 | -type mod() :: record(module). 33 | 34 | -type type() :: hrl | erl | yrl | jxa | {other, string()}. 35 | 36 | %%==================================================================== 37 | %% API 38 | %%==================================================================== 39 | file_regex() -> 40 | "^((.+\.erl)|(.+\.hrl)|(.+\.yrl)|(.+\.jxa))$". 41 | 42 | -spec process_file(sin_state:state(), string(), [string()]) -> 43 | sinan:mod(). 44 | process_file(State0, Path0, Includes) -> 45 | sin_sig:do_if_changed(?MODULE, 46 | Path0, 47 | fun deps_changed/2, 48 | fun(Path1, State1) -> 49 | do_extract(State1, Path1, Includes) 50 | end, State0). 51 | 52 | -spec initialize(string(), type()) -> 53 | sin_file_info:mod(). 54 | initialize(Path, Type) -> 55 | #module{type=Type, 56 | path=Path, 57 | module_deps=sets:new(), 58 | includes=sets:new(), 59 | tags=sets:new(), 60 | include_timestamps=[], 61 | called_modules=sets:new()}. 62 | 63 | 64 | %%==================================================================== 65 | %% Internal Functions 66 | %%==================================================================== 67 | -spec deps_changed(mod(), sin_state:state()) -> boolean(). 68 | deps_changed(#module{include_timestamps=Includes}, State) -> 69 | lists:any(fun({Path, Stamp}) -> 70 | not Stamp == get_timestamp_info(State, Path) 71 | end, Includes). 72 | 73 | -spec do_extract(sin_state:state(), string(), [string()]) -> 74 | {sin_state:state(), mod()}. 75 | do_extract(State0, Path, Includes) -> 76 | case filename:extension(Path) of 77 | Ext when Ext == ".erl"; 78 | Ext == ".hrl"; 79 | Ext == ".yrl" -> 80 | {State1, Mod0, ChangeSig} = sin_erl_info:process_file(State0, 81 | Path, 82 | Includes), 83 | Mod1 = add_stamp_info(State1, Path, Mod0, ChangeSig), 84 | {State1, Mod1}; 85 | Ext when Ext == ".jxa" -> 86 | {State1, Mod0, ChangeSig} = sin_jxa_info:process_file(State0, 87 | Path, 88 | Includes), 89 | Mod1 = add_stamp_info(State1, Path, Mod0, ChangeSig), 90 | {State1, Mod1}; 91 | Error -> 92 | ?SIN_RAISE(State0, {unable_to_parse_file, Path, Error}) 93 | end. 94 | 95 | -spec add_stamp_info(sin_state:state(), string(), mod(), term()) -> mod(). 96 | add_stamp_info(State, Path, Mod, ChangeSig) -> 97 | Mod#module{changed=get_timestamp_info(State, Path), 98 | change_sig=ChangeSig}. 99 | 100 | -spec get_timestamp_info(sin_state:state(), string()) -> non_neg_integer(). 101 | get_timestamp_info(State, Path) -> 102 | case file:read_file_info(Path) of 103 | {ok, FileInfo} -> 104 | FileInfo#file_info.mtime; 105 | {error, enoent} -> 106 | 0; 107 | {error, Reason} -> 108 | ?SIN_RAISE(State, {unable_to_get_file_info, Path, Reason}) 109 | end. 110 | 111 | -------------------------------------------------------------------------------- /test/sint_test_project_gen.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @copyright (C) 2011, Erlware, LLC. 3 | %%% @doc 4 | %%% Test the ability to generate a project programatically 5 | %%% @end 6 | %%% Created : 5 Sep 2011 by Eric Merritt 7 | %%%------------------------------------------------------------------- 8 | -module(sint_test_project_gen). 9 | 10 | -export([a_generated_project/0, 11 | a_generated_project/1, 12 | single_app_project/2, 13 | single_app_project/3, 14 | validate_single_app_project/2, 15 | multi_app_project/3]). 16 | 17 | -include_lib("eunit/include/eunit.hrl"). 18 | 19 | a_generated_project() -> 20 | a_generated_project("super_foo"). 21 | 22 | a_generated_project(ProjectName) -> 23 | BaseDir = ec_file:mkdtemp(), 24 | {ProjectDir, _} = 25 | single_app_project(BaseDir, ProjectName), 26 | {ok, {ProjectDir, ProjectName}}. 27 | 28 | 29 | single_app_project(BaseDir, ProjectName) -> 30 | single_app_project(BaseDir, ProjectName, "0.1.0"). 31 | 32 | single_app_project(BaseDir, ProjectName, Vsn) -> 33 | ProjectDir = filename:join([BaseDir, ProjectName]), 34 | Env = [{year, "2010"}, 35 | {username, "ATestUser"}, 36 | {email_address, "ATestUser@noreply.com"}, 37 | {copyright_holder, "ATestUser"}, 38 | {project_version, Vsn}, 39 | {project_name, ProjectName}, 40 | {project_dir, ProjectDir}, 41 | {erts_version, erlang:system_info(version)}, 42 | {single_app_project, true}, 43 | {wants_build_config, true}], 44 | sin_gen:gen(sin_config:new(), Env), 45 | {ProjectDir, Env}. 46 | 47 | multi_app_project(BaseDir, ProjectName, Apps) -> 48 | ProjectDir = filename:join([BaseDir, ProjectName]), 49 | Env = [{year, "2010"}, 50 | {username, "ATestUser"}, 51 | {email_address, "ATestUser@noreply.com"}, 52 | {copyright_holder, "ATestUser"}, 53 | {project_version, "0.1.0"}, 54 | {project_name, ProjectName}, 55 | {project_dir, ProjectDir}, 56 | {erts_version, erlang:system_info(version)}, 57 | {single_app_project, false}, 58 | {apps, Apps}, 59 | {wants_build_config, true}], 60 | sin_gen:gen(sin_config:new(), Env), 61 | {ProjectDir, Env}. 62 | 63 | validate_single_app_project(ProjectDir, ProjectName) -> 64 | ?assertMatch(true, filelib:is_file(ProjectDir)), 65 | Config = filename:join([ProjectDir, "config"]), 66 | ?assertMatch(true, filelib:is_file(Config)), 67 | ?assertMatch(true, filelib:is_regular(filename:join([Config, "sys.config"]))), 68 | Src = filename:join([ProjectDir, "src"]), 69 | ?assertMatch(true, filelib:is_file(Src)), 70 | ?assertMatch(true, 71 | filelib:is_regular(filename:join([Src, 72 | ProjectName ++ ".app.src"]))), 73 | 74 | ?assertMatch(true, filelib:is_regular(filename:join([Src, ProjectName ++ "_app.erl"]))), 75 | ?assertMatch(true, filelib:is_regular(filename:join([Src, ProjectName ++ "_sup.erl"]))), 76 | ?assertMatch(true, filelib:is_file(filename:join([ProjectDir, "include"]))), 77 | ?assertMatch(true, filelib:is_file(filename:join([ProjectDir, "doc"]))), 78 | ?assertMatch(true, filelib:is_regular(filename:join([ProjectDir, "sinan.config"]))), 79 | EBin = filename:join([ProjectDir, "ebin"]), 80 | ?assertMatch(true, filelib:is_file(EBin)), 81 | ?assertMatch(true, filelib:is_regular(filename:join([EBin, "overview.edoc"]))), 82 | AppDir = filename:join([ProjectDir, "_build", ProjectName, "lib", 83 | ProjectName ++ "-0.1.0"]), 84 | ?assertMatch(true, filelib:is_file(AppDir)), 85 | BConfig = filename:join([AppDir, "config"]), 86 | ?assertMatch(true, filelib:is_file(BConfig)), 87 | ?assertMatch(true, filelib:is_regular(filename:join([BConfig, "sys.config"]))), 88 | BSrc = filename:join([AppDir, "src"]), 89 | ?assertMatch(true, filelib:is_file(BSrc)), 90 | ?assertMatch(true, filelib:is_regular(filename:join([BSrc, ProjectName ++ "_app.erl"]))), 91 | ?assertMatch(true, filelib:is_regular(filename:join([BSrc, ProjectName ++ "_sup.erl"]))), 92 | ?assertMatch(true, filelib:is_file(filename:join([AppDir, "include"]))), 93 | ?assertMatch(true, filelib:is_file(filename:join([AppDir, "doc"]))), 94 | BEBin = filename:join([AppDir, "ebin"]), 95 | ?assertMatch(true, filelib:is_file(BEBin)), 96 | ?assertMatch(true, filelib:is_regular(filename:join([BEBin, ProjectName ++ "_app.beam"]))), 97 | ?assertMatch(true, filelib:is_regular(filename:join([BEBin, ProjectName ++ "_sup.beam"]))), 98 | ?assertMatch(true, filelib:is_regular(filename:join([BEBin, ProjectName ++ ".app"]))), 99 | ok. 100 | -------------------------------------------------------------------------------- /include/sinan.hrl: -------------------------------------------------------------------------------- 1 | %%%--------------------------------------------------------------------------- 2 | %%% @author Eric Merritt 3 | %%% @doc 4 | %%% Provides the task and resource definitions for the system. 5 | %%% @end 6 | %%% @copyright 2007 Erlware 7 | %%%--------------------------------------------------------------------------- 8 | 9 | %% @doc Describes n application, and whether or not the application is a 10 | %% runtime dep or compile time dep, also wether it is a project 11 | %% application or a dependency 12 | -record(app, {name :: atom(), 13 | vsn :: string(), 14 | path :: string(), 15 | type :: runtime | compiletime, 16 | project :: boolean(), 17 | %% Everything below will only be populated if the 18 | %% project field above is true otherwise they will be 19 | %% undefined. 20 | id="" :: string() | list(), 21 | maxP=infinity :: integer() | infinity, 22 | maxT=infinity :: integer() | infinity, 23 | env=[] :: list(), 24 | start_phases = undefined :: term() | undefined, 25 | basedir :: string() | undefined, 26 | description="" :: string() | list(), 27 | registered :: [atom()] | undefined, 28 | applications :: [atom()] | undefined, 29 | included_applications :: [atom()] | undefined, 30 | dep_constraints :: term() | undefined, 31 | mod :: term() | undefined, 32 | dotapp :: string() | undefined, 33 | deps :: [atom()] | undefined, 34 | properties=[] :: proplists:proplist(), 35 | modules :: [atom()] | undefined}). 36 | 37 | %% @doc describes a module in the system 38 | %% - name: is the name of the module 39 | -record(module, {name :: module(), 40 | type :: hrl | yrl | erl | {other, string()}, 41 | path :: string(), 42 | module_deps :: [module()], 43 | includes :: [atom()], 44 | include_timestamps :: [{string(), 45 | sin_file_info:date_time()}], 46 | tags :: [atom()], 47 | called_modules :: [atom()], 48 | changed :: sin_file_info:date_time(), 49 | change_sig :: number()}). 50 | 51 | 52 | -record(task, {name :: atom(), % The 'user friendly' name of the task 53 | task_impl :: atom(), % The implementation of the task, maybe fun or 54 | bare :: boolean(), % Indicates whether a build config is needed 55 | deps :: [atom()], % The list of dependencies 56 | desc :: string(), % The description for the task 57 | short_desc :: string(), % A one line short description of the task 58 | example :: string(), % An example of the task usage 59 | opts :: list()}). % The list of options that the task requires/understands 60 | 61 | 62 | -define(SIN_EXEP_UNPARSE(S, Problem, Description), 63 | {pe, S, {_, _, {Problem, Description}}}). 64 | -define(SIN_EXEP_UNPARSE(S, Problem), 65 | {pe, S, {_, _, Problem}}). 66 | 67 | -define(SIN_RAISE(State, Problem), 68 | throw({pe, 69 | sin_state:add_run_error(?MODULE, 70 | {?MODULE, ?LINE, Problem}, State), 71 | {?MODULE, ?LINE, Problem}})). 72 | 73 | -define(SIN_RAISE(State, Problem, Description), 74 | throw({pe, 75 | sin_state:add_run_error(?MODULE, 76 | {?MODULE, ?LINE, 77 | {Problem, Description}}, State), 78 | {?MODULE, ?LINE, 79 | {Problem, Description}}})). 80 | 81 | -define(SIN_RAISE(State, Problem, Description, DescArgs), 82 | throw({pe, 83 | sin_state:add_run_error(?MODULE, 84 | {?MODULE, ?LINE, 85 | {Problem, 86 | lists:flatten( 87 | io_lib:format(Description, 88 | DescArgs))}}, State), 89 | {?MODULE, ?LINE, 90 | {Problem, 91 | lists:flatten(io_lib:format(Description, DescArgs))}}})). 92 | 93 | -define(WARN(State, Warnings), 94 | ((fun() -> 95 | WarnRef = 96 | sin_state:add_run_warning(?MODULE, Warnings, State), 97 | WarnRef 98 | end)())). 99 | 100 | 101 | -define(WARN(State, Warnings, Detail), 102 | ((fun() -> 103 | WarnRef = 104 | sin_state:add_run_warning(?MODULE, 105 | lists:flatten(io_lib:format(Warnings, Detail)), State), 106 | WarnRef 107 | end)())). 108 | -------------------------------------------------------------------------------- /doc/assets/css/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * A clean concise theme for your GitHub projects 3 | * 4 | * Licenced under GPL v3 5 | * http://www.gnu.org/licenses/gpl.html 6 | **/ 7 | 8 | /* Page */ 9 | body { 10 | background: #333333 url('../images/page-background.png') repeat top left; 11 | font-family: arial,sans-serif; 12 | } 13 | 14 | /* Header */ 15 | header { 16 | padding-top: 50px; 17 | padding-bottom: 28px; 18 | } 19 | 20 | /* Footer */ 21 | footer { 22 | background-color: #FFFFFF; 23 | margin-top: 50px; 24 | -webkit-box-shadow: -10px 0 15px #000000; 25 | -moz-box-shadow: -10px 0 15px #000000; 26 | box-shadow: -10px 0 15px #000000; 27 | } 28 | 29 | div.credits { 30 | padding-top: 9px; 31 | margin-top: 30px; 32 | margin-bottom: 25px; 33 | border-top: 1px solid #DDD2B2; 34 | text-align: center; 35 | } 36 | 37 | footer p { 38 | color: #333333; 39 | } 40 | 41 | div.repo-author { 42 | padding-top: 30px; 43 | } 44 | 45 | /* Text */ 46 | p, strong, li { 47 | color: #CCCCCC; 48 | font-size: 14px; 49 | } 50 | 51 | strong { 52 | font-weight: bold; 53 | color: #EEEEEE; 54 | } 55 | 56 | a { 57 | color: #0075B6; 58 | text-decoration: none; 59 | } 60 | 61 | a:visited { 62 | color: #0075B6; 63 | } 64 | 65 | a:hover { 66 | text-decoration: underline; 67 | } 68 | 69 | h1, h2 { 70 | font-family: georgia,serif; 71 | } 72 | 73 | h1 { 74 | font-style: italic; 75 | color: #FFFFFF; 76 | font-size: 50px; 77 | margin: 0; 78 | } 79 | 80 | h2 { 81 | color: #CCCCCC; 82 | font-size: 25px; 83 | line-height: 23px; 84 | padding-top: 15px; 85 | } 86 | 87 | h3, h4, h5 { 88 | color: #FFFFFF; 89 | font-weight: bold; 90 | font-family: inherit; 91 | } 92 | 93 | 94 | /* Useful classes and styles */ 95 | a.github-ribbon { 96 | position: absolute; 97 | top: 0; 98 | left: 0; 99 | border: 0; 100 | } 101 | 102 | a.download-button { 103 | display: block; 104 | padding: 15px 20px 10px 20px; 105 | color: #FFFFFF; 106 | text-decoration: none; 107 | font-size: 28px; 108 | font-weight: bold; 109 | background: #33A700 url('../images/github-logo.png') no-repeat 92% 50%; 110 | border: 2px solid #339410; 111 | -webkit-box-shadow: 3px 3px 5px #000000; 112 | -moz-box-shadow: 3px 3px 5px #000000; 113 | box-shadow: 3px 3px 5px #000000; 114 | -webkit-border-radius: 5px; 115 | -moz-border-radius: 5px; 116 | border-radius: 5px; 117 | -webkit-transition: 350ms; 118 | -moz-transition: 350ms; 119 | -o-transition: 350ms; 120 | transition: 350ms; 121 | } 122 | 123 | a.download-button:hover { 124 | background-color: #267C00; 125 | background-position: 90% 50%; 126 | } 127 | 128 | a.download-button span { 129 | font-size: 14px; 130 | display: block; 131 | margin-top: 2px; 132 | } 133 | 134 | div.highlight { 135 | margin-top: 15px; 136 | min-height: 220px; 137 | border: 3px solid #FFFFFF; 138 | background-color: #CCCC99; 139 | display: block; 140 | padding: 20px; 141 | font-family: monospace; 142 | -webkit-box-shadow: 3px 3px 5px #000000; 143 | -moz-box-shadow: 3px 3px 5px #000000; 144 | box-shadow: 3px 3px 5px #000000; 145 | overflow-x: auto; 146 | } 147 | 148 | dl { 149 | margin-top: 15px; 150 | min-height: 220px; 151 | border: 3px solid #FFFFFF; 152 | background-color: #CCCC99; 153 | display: block; 154 | padding: 20px; 155 | font-family: monospace; 156 | -webkit-box-shadow: 3px 3px 5px #000000; 157 | -moz-box-shadow: 3px 3px 5px #000000; 158 | box-shadow: 3px 3px 5px #000000; 159 | overflow-x: auto; 160 | } 161 | dt { 162 | font-weight: bold 163 | } 164 | 165 | 166 | code { 167 | margin-top: 15px; 168 | min-height: 50px; 169 | border: 3px solid #FFFFFF; 170 | background-color: #CCCC99; 171 | display: block; 172 | padding: 20px; 173 | font-family: monospace; 174 | -webkit-box-shadow: 3px 3px 5px #000000; 175 | -moz-box-shadow: 3px 3px 5px #000000; 176 | box-shadow: 3px 3px 5px #000000; 177 | overflow-x: auto; 178 | } 179 | 180 | pre.prettyprint { 181 | border: 0; 182 | padding: 0; 183 | margin: 0; 184 | } 185 | 186 | img.repo-author-logo { 187 | float: left; 188 | margin-right: 15px; 189 | } 190 | 191 | .menu { 192 | width: 100%; 193 | border-bottom: 2px solid #77746C; 194 | border-top: 2px solid #77746C; 195 | margin-bottom: 28px; 196 | } 197 | 198 | .menu ul { 199 | margin: 0; 200 | padding: 0; 201 | float: left; 202 | } 203 | 204 | .menu ul li { 205 | display: inline; 206 | } 207 | 208 | .menu ul li a { 209 | float: left; 210 | text-decoration: none; 211 | color: white; 212 | padding: 10.5px 11px; 213 | } 214 | 215 | .menu ul li a:visited { 216 | color: white; 217 | } 218 | 219 | .menu ul li a:hover, .menu ul li .current { 220 | color: #fff; 221 | background-color:#0b75b2; 222 | } 223 | 224 | /* Media queries */ 225 | /* Hide the ribbon when we are on a phone, screen is too small */ 226 | @media only screen and (max-width: 479px) { 227 | a.github-ribbon { 228 | display: none; 229 | } 230 | } -------------------------------------------------------------------------------- /src/sin_state.erl: -------------------------------------------------------------------------------- 1 | %% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- 2 | %%%--------------------------------------------------------------------------- 3 | %%% @author Eric Merritt 4 | %%% @copyright 2006, 2011 Erlware 5 | %%% @doc 6 | %%% An abstract storage and management piece for build related state 7 | %%% pairs. 8 | %%% @end 9 | %%%---------------------------------------------------------------------------- 10 | -module(sin_state). 11 | 12 | %% API 13 | -export([new/0, 14 | store/2, 15 | store/3, 16 | get_value/2, 17 | get_value/3, 18 | get_pairs/1, 19 | project_app_by_name/2, 20 | delete/2, 21 | add_run_error/3, 22 | get_run_errors/1, 23 | add_run_warning/3, 24 | get_run_warnings/1, 25 | format_exception/1]). 26 | 27 | -export_type([key/0, 28 | value/0, 29 | state/0]). 30 | 31 | -include_lib("eunit/include/eunit.hrl"). 32 | -include_lib("sinan/include/sinan.hrl"). 33 | 34 | %%==================================================================== 35 | %% Types 36 | %%==================================================================== 37 | 38 | -type key() :: term(). 39 | -type value() :: term(). 40 | -opaque state() :: any(). 41 | 42 | %%==================================================================== 43 | %% API 44 | %%==================================================================== 45 | 46 | %% @doc Create a new empty config 47 | -spec new() -> state(). 48 | new() -> 49 | dict:new(). 50 | 51 | %% @doc Add a key to the state. 52 | -spec store(key(), value(), state()) -> state(). 53 | store(Key, Value, State) -> 54 | dict:store(Key, Value, State). 55 | 56 | %% @doc Store a list of key value pairs into the state 57 | -spec store(KeyValuePairs::[{string(), term()}], state()) -> 58 | state(). 59 | store(KeyValuePairs, State) when is_list(KeyValuePairs) -> 60 | lists:foldl(fun ({Key, Value}, Dict) -> 61 | dict:store(Key, Value, Dict) 62 | end, State, KeyValuePairs). 63 | 64 | %% @doc Get a value from the state. 65 | -spec get_value(key(), state()) -> value() | undefined. 66 | get_value(Key, State) -> 67 | case dict:find(Key, State) of 68 | error -> 69 | undefined; 70 | {ok, Value} when is_binary(Value) -> 71 | binary_to_list(Value); 72 | {ok, Value} -> 73 | Value 74 | end. 75 | 76 | %% @doc Attempts to get the specified key. If the key doesn't exist it 77 | %% returns the requested default instead of just undefined. 78 | -spec get_value(key(), value(), state()) -> value(). 79 | get_value(Key, DefaultValue, State) -> 80 | case get_value(Key, State) of 81 | undefined -> 82 | DefaultValue; 83 | Value -> 84 | Value 85 | end. 86 | 87 | %% @doc Delete a value from the state. 88 | -spec delete(key(), state()) -> state(). 89 | delete(Key, State) -> 90 | dict:erase(Key, State). 91 | 92 | project_app_by_name(AppName, State) -> 93 | ProjectApps = sin_state:get_value(project_apps, State), 94 | case ec_lists:search(fun(App=#app{name=Name}) -> 95 | case Name == AppName of 96 | true -> 97 | {ok, App}; 98 | _ -> 99 | not_found 100 | end 101 | end, ProjectApps) of 102 | {ok, ProjApp, _} -> 103 | ProjApp; 104 | _ -> 105 | not_found 106 | end. 107 | 108 | %% @doc Get the complete state as key,value pairs 109 | -spec get_pairs(state()) -> [{key(), value()}]. 110 | get_pairs(State) -> 111 | dict:to_list(State). 112 | 113 | %% @doc Add a run time error occurance to the state 114 | -spec add_run_error(atom(), term(), state()) -> state(). 115 | add_run_error(Task, Error, State) -> 116 | CurrentErrors = get_value(run_errors, [], State), 117 | store(run_errors, [{Task, Error} | CurrentErrors], State). 118 | 119 | %% @doc return the list of run errors in the state 120 | -spec get_run_errors(state()) -> [term()]. 121 | get_run_errors(State) -> 122 | get_value(run_errors, [], State). 123 | 124 | %% @doc Add a run time warning occurance to the state 125 | -spec add_run_warning(atom(), term(), state()) -> state(). 126 | add_run_warning(Task, Warning, State) -> 127 | CurrentWarnings = get_value(run_warnings, [], State), 128 | store(run_warnings, [{Task, Warning} | CurrentWarnings], State). 129 | 130 | %% @doc return the list of run warnings in the state 131 | -spec get_run_warnings(state()) -> [term()]. 132 | get_run_warnings(State) -> 133 | get_value(run_warnings, [], State). 134 | 135 | %% @doc Format an exception thrown by this module 136 | -spec format_exception(sin_exceptions:exception()) -> 137 | string(). 138 | format_exception(Exception) -> 139 | sin_exceptions:format_exception(Exception). 140 | 141 | %%==================================================================== 142 | %% Internal Functions 143 | %%==================================================================== 144 | --------------------------------------------------------------------------------