aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml13
-rw-r--r--Makefile10
-rw-r--r--docs/conf.py285
-rw-r--r--docs/configuration.rst89
-rw-r--r--docs/docs.mkf154
-rw-r--r--docs/index.rst22
-rw-r--r--docs/overview.rst58
-rw-r--r--examples/relcool.config74
-rw-r--r--examples/relcool_simple.config19
-rw-r--r--rebar.config19
-rw-r--r--src/rcl_app_info.erl26
-rw-r--r--src/rcl_cmd_args.erl2
-rw-r--r--src/rcl_provider.erl7
-rw-r--r--src/rcl_prv_assembler.erl37
-rw-r--r--src/rcl_prv_config.erl3
-rw-r--r--src/rcl_prv_discover.erl53
-rw-r--r--src/rcl_state.erl14
-rw-r--r--src/relcool.erl34
-rw-r--r--test/rclt_release_SUITE.erl58
20 files changed, 311 insertions, 668 deletions
diff --git a/.gitignore b/.gitignore
index 4e58778..bf72009 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,5 +8,5 @@ relcool
src/rcl_goal.erl
logs
test/*_data
-relcool_output/*
+_rel/*
.* \ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..3832235
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,13 @@
+language: erlang
+otp_release:
+ - R15B02
+ - R15B01
+ - R15B
+ - R14B04
+ - R14B03
+ - R14B02
+script: "make rebuild"
+branches:
+ only:
+ - master
+ - next \ No newline at end of file
diff --git a/Makefile b/Makefile
index 599bcb3..2e43f9d 100644
--- a/Makefile
+++ b/Makefile
@@ -41,15 +41,6 @@ endif
all: compile escript dialyzer test
# =============================================================================
-# Include relevant sub-makefiles.
-# =============================================================================
-# These are not subdirs, they are just additional makefile information
-# that happens to live subdirectories
-
-include $(CURDIR)/docs/docs.mkf
-
-
-# =============================================================================
# Rules to build the system
# =============================================================================
@@ -107,6 +98,7 @@ clean-common-test-data:
clean: clean-common-test-data
- rm -rf $(CURDIR)/test/*.beam
- rm -rf $(CURDIR)/logs
+ - rm -rf $(CURDIR)/ebin
$(REBAR) skip_deps=true clean
distclean: clean
diff --git a/docs/conf.py b/docs/conf.py
deleted file mode 100644
index f29af4c..0000000
--- a/docs/conf.py
+++ /dev/null
@@ -1,285 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Relcool documentation build configuration file, created by
-# sphinx-quickstart on Mon Sep 10 10:04:06 2012.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys, os
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-# -- General configuration -----------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = []
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'Relcool'
-copyright = u'2012, Eric B MErritt'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = '0.0.1'
-# The full version, including alpha/beta/rc tags.
-release = '0.0.1'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = ['_build']
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-
-# -- Options for HTML output ---------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'default'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-#html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'Relcooldoc'
-
-
-# -- Options for LaTeX output --------------------------------------------------
-
-latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
-latex_documents = [
- ('index', 'Relcool.tex', u'Relcool Documentation',
- u'Eric B MErritt', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output --------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
- ('index', 'relcool', u'Relcool Documentation',
- [u'Eric B MErritt'], 1)
-]
-
-# If true, show URL addresses after external links.
-#man_show_urls = False
-
-
-# -- Options for Texinfo output ------------------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-# dir menu entry, description, category)
-texinfo_documents = [
- ('index', 'Relcool', u'Relcool Documentation',
- u'Eric B MErritt', 'Relcool', 'One line description of project.',
- 'Miscellaneous'),
-]
-
-# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
-
-# If false, no module index is generated.
-#texinfo_domain_indices = True
-
-# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
-
-
-# -- Options for Epub output ---------------------------------------------------
-
-# Bibliographic Dublin Core info.
-epub_title = u'Relcool'
-epub_author = u'Eric B MErritt'
-epub_publisher = u'Eric B MErritt'
-epub_copyright = u'2012, Eric B MErritt'
-
-# The language of the text. It defaults to the language option
-# or en if the language is not set.
-#epub_language = ''
-
-# The scheme of the identifier. Typical schemes are ISBN or URL.
-#epub_scheme = ''
-
-# The unique identifier of the text. This can be a ISBN number
-# or the project homepage.
-#epub_identifier = ''
-
-# A unique identification for the text.
-#epub_uid = ''
-
-# A tuple containing the cover image and cover page html template filenames.
-#epub_cover = ()
-
-# HTML files that should be inserted before the pages created by sphinx.
-# The format is a list of tuples containing the path and title.
-#epub_pre_files = []
-
-# HTML files shat should be inserted after the pages created by sphinx.
-# The format is a list of tuples containing the path and title.
-#epub_post_files = []
-
-# A list of files that should not be packed into the epub file.
-#epub_exclude_files = []
-
-# The depth of the table of contents in toc.ncx.
-#epub_tocdepth = 3
-
-# Allow duplicate toc entries.
-#epub_tocdup = True
diff --git a/docs/configuration.rst b/docs/configuration.rst
deleted file mode 100644
index 4e84deb..0000000
--- a/docs/configuration.rst
+++ /dev/null
@@ -1,89 +0,0 @@
-Configuration
-=============
-
-Configuration of reltool is done via Erlang key, value elements in the
-`relcool.config` file. There are various configuration elements that
-can appear, each affects the running of the system in specific
-ways. Note that these must be complete Erlang terms terminated by a
-`.`.
-
-Paths
------
-
-Paths is a way to add additional code paths (that point to Erlang beam
-files) to the system. These are not used for dependency resolution,
-they are only used as code paths. The main use for this is simply to
-provide a non-resolvable path for additional providers.
-
-the path confuration element looks as follows::
-
- {paths, [<list of directory paths>]}.
-
-So if we wanted to add `/usr/local/lib` and `/opt/lib` to the code
-paths for the system we could add the following::
-
- {paths, ["/usr/local/lib", "/opt/lib"]}.
-
-to our configuration.
-
-Providers
----------
-
-Providers are how the Relcool system can be extended by a user. They
-are basically Erlang modules that implement the `rcl_provider`
-behaviour and are available via a code path. See the section on
-providers for more information.
-
-The `providers` element provides a completely new list of providers,
-replacing any providers that may already exist. The provider element
-is as follows::
-
- {providers, <list of module names>}.
-
-Lets say I have three providers; `my_custom_assembler`,
-`my_rpm_assembler` and `my_deb_assembler`. I could make these the
-complete list of providers by doing the following in my config::
-
- {providers, [my_custom_assembler,
- my_rpm_assembler,
- my_deb_assembler]}.
-
-Order is important in the providers as they will be executed in the
-order specified.
-
-Add Providers
--------------
-
-`add_providers` is very similar to `providers` with the exception that
-the listed providers are added to the end of the current list of
-providers rather then replacing the list all together. Add providres
-looks as follows::
-
-
- {add_providers, <list of module names>}.
-
-Lets take our previous example but only add `my_rpm_assembler` and
-`my_deb_assembler` to the existing list of providers. We do this by
-adding the following::
-
- {add_providers, [my_rpm_assembler,
- my_deb_assembler]}.
-
-Releases
---------
-
-Release configuration is the bread and butter, the blood and bones of
-the Relcool system. It provides the framework for realizing
-dependencies in the system. The release element basically minics the
-standard _Erlang/OTP Release Metadata file format:
-http://www.erlang.org/doc/man/rel.html. There are two main
-differences. The first is that you don't have to specify a complete
-list of applications and you may specify version constraints instead
-of hard versions.
-
-
-{release, {relname, vsn},
- app goals}
-{release, {relname, vsn},
-{erts, vsn},
-app goals}
diff --git a/docs/docs.mkf b/docs/docs.mkf
deleted file mode 100644
index 6987063..0000000
--- a/docs/docs.mkf
+++ /dev/null
@@ -1,154 +0,0 @@
-# -*- mode: Makefile; fill-column: 80; comment-column: 75; -*-
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-PAPER =
-BUILDDIR = _build
-
-# Internal variables.
-PAPEROPT_a4 = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-# the i18n builder cannot share the environment and doctrees with the others
-I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
-
-doc-help:
- @echo "Please use \`make <target>' where <target> is one of"
- @echo " html to make standalone HTML files"
- @echo " dirhtml to make HTML files named index.html in directories"
- @echo " singlehtml to make a single large HTML file"
- @echo " pickle to make pickle files"
- @echo " json to make JSON files"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " qthelp to make HTML files and a qthelp project"
- @echo " devhelp to make HTML files and a Devhelp project"
- @echo " epub to make an epub"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " latexpdf to make LaTeX files and run them through pdflatex"
- @echo " text to make text files"
- @echo " man to make manual pages"
- @echo " texinfo to make Texinfo files"
- @echo " info to make Texinfo files and run them through makeinfo"
- @echo " gettext to make PO message catalogs"
- @echo " changes to make an overview of all changed/added/deprecated items"
- @echo " linkcheck to check all external links for integrity"
- @echo " doctest to run all doctests embedded in the documentation (if enabled)"
-
-doc-clean:
- -rm -rf $(BUILDDIR)/*
-
-html:
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
- $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
- $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
- @echo
- @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-pickle:
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
- @echo
- @echo "Build finished; now you can process the pickle files."
-
-json:
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
- @echo
- @echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
- @echo
- @echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
- $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
- @echo
- @echo "Build finished; now you can run "qcollectiongenerator" with the" \
- ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Relcool.qhcp"
- @echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Relcool.qhc"
-
-devhelp:
- $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
- @echo
- @echo "Build finished."
- @echo "To view the help file:"
- @echo "# mkdir -p $$HOME/.local/share/devhelp/Relcool"
- @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Relcool"
- @echo "# devhelp"
-
-epub:
- $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
- @echo
- @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
- @echo "Run \`make' in that directory to run these through (pdf)latex" \
- "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo "Running LaTeX files through pdflatex..."
- $(MAKE) -C $(BUILDDIR)/latex all-pdf
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
- $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
- @echo
- @echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
- $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
- @echo
- @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-texinfo:
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
- @echo
- @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
- @echo "Run \`make' in that directory to run these through makeinfo" \
- "(use \`make info' here to do that automatically)."
-
-info:
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
- @echo "Running Texinfo files through makeinfo..."
- make -C $(BUILDDIR)/texinfo info
- @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
-
-gettext:
- $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
- @echo
- @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
-
-changes:
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
- @echo
- @echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
- @echo
- @echo "Link check complete; look for any errors in the above output " \
- "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
- @echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/index.rst b/docs/index.rst
deleted file mode 100644
index f367ef8..0000000
--- a/docs/index.rst
+++ /dev/null
@@ -1,22 +0,0 @@
-.. Relcool documentation master file, created by
- sphinx-quickstart on Mon Sep 10 10:04:06 2012.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
-
-Welcome to Relcool's documentation!
-===================================
-
-Contents:
-
-.. toctree::
- :maxdepth: 2
-
-
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
diff --git a/docs/overview.rst b/docs/overview.rst
deleted file mode 100644
index 77453a3..0000000
--- a/docs/overview.rst
+++ /dev/null
@@ -1,58 +0,0 @@
-Overview
-========
-
-Relcool is a tool that, given a specification, assembles
-releases. That is it makes an assessment of all of the Erlang/OTP Apps
-available to it and the constraints that you supply as part of your
-configuration and resolves a complete set of OTP Applications and
-thier versions for use in the release. This may be a bit hard to
-understand if you are not familiar with the way version resolutions
-work in package management systems. So lets look at an example.
-
-Lets say that you have the following OTP Applications
-
-* app1-1.2
- with dependencies::
- app2
- app5
- app6
-* app1-1.3
- with dependencies::
- app2
- app6
-* app2-2.0
- with dependencies::
- app6
-* app2-2.1
- with dependencies::
- app6
- app7
-* app3-2.0
-* app4-1.0.0
-* app5-3.0
-* app6-1.0
- with dependencies::
- app3
-* app7-2.0
-
-This is the world of OTP Apps your Relcool knows about (basically OTP
-Apps in the Library Directories you have specified). You have set a
-config that looks like the following::
-
- {release, {awesome_supercool, "1.0"},
- [{app1, "1.3", '>='},
- {app2, "2.0", '>'},
- app3]}
-
-When the Relcool process has run you will end up with a complete release as follows::
-
- {release, {awesome_supercool, "1.0"},
- [{app1, "1.3"},
- {app2, "2.1"},
- {app3, "2.0"},
- {app6, "1.0"},
- {app7, "2.0"}]}
-
-As you can see that is a fully realied view of your direct and
-transative dependencies based on the world that Relcool knows about
-and the constraints that you specified in your configuration.
diff --git a/examples/relcool.config b/examples/relcool.config
new file mode 100644
index 0000000..fe6182b
--- /dev/null
+++ b/examples/relcool.config
@@ -0,0 +1,74 @@
+%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
+%% Example Relcool Config
+%% ======================
+%%
+%% This is an example relcool config whose purpose is to demonstrate all of the
+%% options available in relcool. Its not expected that you will use all of the
+%% things here. In fact, there is a high likely hood that *your* relcool.config
+%% will be extremely minimal, as relcool does a very good job of figuring out
+%% things on its own.
+%%
+%% The Release We Are Building
+%% ---------------------------
+%%
+%% Lets say we have a release called sexpr. The sexpr release supports versions
+%% 0.0.1 and 0.0.2 with different dependencies. 0.0.1 requires erlware commons
+%% 0.8.0 or lesser. 0.0.2 requires erlware_commons 0.8.1 or greater along with
+%% neotoma (any version). We also do not want neotoma to be loaded. We also want
+%% our default release. the one we build in the common case to be sexper 0.0.2.
+
+%% You can tell relcool about additional directories that you want searched for
+%% otp apps during the discovery process. You do that in the 'paths' config. You
+%% can also specify these paths on the command line with `-p`. Be aware that
+%% relcool plays well with rebar so if you have a deps directory in the current
+%% directory it will be automatically added.
+{paths, ["/opt/erlang_apps"]}.
+
+
+%% If you have a sys.config file you need to tell relcool where it is. If you do
+%% that relcool will include the sys.config in the appropriate place
+%% automatically.
+{sys_config, "./config/sys.config"}.
+
+%% relcool will include erts by default. However, if you don't want to include
+%% erts you can add the `include_erts` tuple to the config and tell relcool not
+%% to include it.
+{include_erts, false}.
+
+%% When we have multiple releases relcool needs to know which one to build. You
+%% can specify that on the command line with the `-n` and `-v` arguments to
+%% relcool. However, it is often more convenient to do it in the config.
+{default_release, sexpr, "0.0.2"}.
+
+{release, {sexpr, "0.0.1"},
+ [sexpr,
+ %% There are two syntaxes for constraints.
+ %% The first is the tuple syntax shown here.
+ {erlware_commons, "0.8.0", '<='}]}.
+
+{release, {sexpr, "0.0.2"},
+ [sexpr,
+
+ %% This is the second constraint syntax, it is interchangeable with the tuple
+ %% syntax and its up to you which you find more readable/usable.
+ "erlware_commons>=0.8.1",
+
+ %% You can put the release load types in the release spec here in exactly the
+ %% same way that you can do it for a normal relfile. The syntax is
+ %% {<constraint>, <type>}.
+ {neotoma, load}]}.
+
+%% During development its often the case that you want to substitute the app
+%% that you are working on for a 'production' version of an app. You can
+%% explicitly tell relcool to override all versions of an app that you specify
+%% with an app in an arbitrary directory. Relcool will then symlink that app
+%% into the release in place of the specified app. be aware though that relcool
+%% will check your app for consistancy so it should be a normal OTP app and
+%% already be built.
+{overrides, [{sexpr, "../sexpr"}]}.
+
+
+%% In some cases you might want to add additional functionality to relcool. You
+%% can do this via a 'provider'. A provider is an implementation of the relcool
+%% provider behaviour. This probably shouldn't be needed very often.
+{add_providers, [my_custom_functionality]}.
diff --git a/examples/relcool_simple.config b/examples/relcool_simple.config
new file mode 100644
index 0000000..88e9e8d
--- /dev/null
+++ b/examples/relcool_simple.config
@@ -0,0 +1,19 @@
+%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
+%% Example Relcool Simple Config
+%% =============================
+%%
+%% This is an example relcool config whose purpose demonstrate the minimal
+%% config needed for relcool. For a more complete example see the relcool.config
+%% in the examples directory.
+%%
+%% The Release We Are Building
+%% ---------------------------
+%%
+%% Lets say we have a release called sexpr. The sexpr release has version 0.0.2
+%% 0.0.2 requires erlware_commons 0.8.1 or greater along with neotoma
+%% (any version).
+
+{release, {sexpr, "0.0.2"},
+ [sexpr,
+ {erlware_commons, 0.8.1, '>='},
+ neotoma]}.
diff --git a/rebar.config b/rebar.config
index 2211100..2b2f0b3 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,5 +1,5 @@
%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
-
+%% Dependencies ================================================================
{deps, [{rebar_vsn_plugin, ".*",
{git, "https://github.com/erlware/rebar_vsn_plugin.git",
{tag, "master"}}},
@@ -12,14 +12,21 @@
{git, "https://github.com/jcomellas/getopt.git",
{branch, "master"}}}]}.
+%% Rebar Plugins ==============================================================
{plugins, [rebar_vsn_plugin]}.
-{escript_incl_apps,
- [getopt, erlware_commons]}.
-
-{first_files, [rcl_provider]}.
+%% Compiler Options ============================================================
+{erl_opts,
+ [debug_info, warnings_as_errors, inline]}.
+%% EUnit =======================================================================
+{eunit_opts,
+ [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}.
{cover_enabled, true}.
{cover_print_enabled, true}.
-{erl_opts, [debug_info, warnings_as_errors, inline]}.
+%% Misc =======================================================================
+{escript_incl_apps,
+ [getopt, erlware_commons]}.
+
+{first_files, [rcl_provider]}.
diff --git a/src/rcl_app_info.erl b/src/rcl_app_info.erl
index d0192e6..bc64e30 100644
--- a/src/rcl_app_info.erl
+++ b/src/rcl_app_info.erl
@@ -49,6 +49,8 @@
active_deps/2,
library_deps/1,
library_deps/2,
+ link/1,
+ link/2,
format_error/1,
format/2,
format/1]).
@@ -60,6 +62,7 @@
-record(app_info_t, {name :: atom(),
vsn :: ec_semver:semver(),
dir :: file:name(),
+ link=false :: boolean(),
active_deps :: [atom()],
library_deps :: [atom()]}).
@@ -80,7 +83,13 @@ new() ->
%% @doc build a complete version of the app info with all fields set.
-spec new(atom(), string(), file:name(), [atom()], [atom()]) ->
{ok, t()} | relcool:error().
-new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps)
+new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps) ->
+ new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps, false).
+
+%% @doc build a complete version of the app info with all fields set.
+-spec new(atom(), string(), file:name(), [atom()], [atom()], boolean()) ->
+ {ok, t()} | relcool:error().
+new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps, Link)
when erlang:is_atom(AppName),
erlang:is_list(Dir),
erlang:is_list(ActiveDeps),
@@ -91,7 +100,8 @@ new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps)
ParsedVsn ->
{ok, #app_info_t{name=AppName, vsn=ParsedVsn, dir=Dir,
active_deps=ActiveDeps,
- library_deps=LibraryDeps}}
+ library_deps=LibraryDeps,
+ link=Link}}
end.
-spec name(t()) -> atom().
@@ -145,6 +155,14 @@ library_deps(AppInfo=#app_info_t{}, LibraryDeps)
when erlang:is_list(LibraryDeps) ->
AppInfo#app_info_t{library_deps=LibraryDeps}.
+-spec link(t()) -> boolean().
+link(#app_info_t{link=Link}) ->
+ Link.
+
+-spec link(t(), boolean()) -> t().
+link(AppInfo, NewLink) ->
+ AppInfo#app_info_t{link=NewLink}.
+
-spec format_error(Reason::term()) -> iolist().
format_error({vsn_parse, AppName}) ->
io_lib:format("Error parsing version for ~p",
@@ -156,9 +174,11 @@ format(AppInfo) ->
-spec format(non_neg_integer(), t()) -> iolist().
format(Indent, #app_info_t{name=Name, vsn=Vsn, dir=Dir,
- active_deps=Deps, library_deps=LibDeps}) ->
+ active_deps=Deps, library_deps=LibDeps,
+ link=Link}) ->
[rcl_util:indent(Indent), erlang:atom_to_list(Name), "-", ec_semver:format(Vsn),
": ", Dir, "\n",
+ rcl_util:indent(Indent + 1), "Symlink: ", erlang:atom_to_list(Link), "\n",
rcl_util:indent(Indent + 1), "Active Dependencies:\n",
[[rcl_util:indent(Indent + 2), erlang:atom_to_list(Dep), ",\n"] || Dep <- Deps],
rcl_util:indent(Indent + 1), "Library Dependencies:\n",
diff --git a/src/rcl_cmd_args.erl b/src/rcl_cmd_args.erl
index ab073a2..68bd9ce 100644
--- a/src/rcl_cmd_args.erl
+++ b/src/rcl_cmd_args.erl
@@ -147,7 +147,7 @@ convert_goals([RawSpec | Rest], Acc) ->
-spec create_output_dir([getopt:option()], rcl_state:cmd_args()) ->
{ok, rcl_state:cmd_args()} | relcool:error().
create_output_dir(Opts, Acc) ->
- OutputDir = proplists:get_value(output_dir, Opts, "./relcool_output"),
+ OutputDir = proplists:get_value(output_dir, Opts, "./_rel"),
case filelib:is_dir(OutputDir) of
false ->
case rcl_util:mkdir_p(OutputDir) of
diff --git a/src/rcl_provider.erl b/src/rcl_provider.erl
index 9d1dd88..c3ef434 100644
--- a/src/rcl_provider.erl
+++ b/src/rcl_provider.erl
@@ -10,6 +10,7 @@
%% API
-export([new/2,
do/2,
+ impl/1,
format_error/1,
format_error/2,
format/1]).
@@ -57,6 +58,12 @@ new(ModuleName, State0) when is_atom(ModuleName) ->
do({?MODULE, Mod}, State) ->
Mod:do(State).
+%%% @doc get the name of the module that implements the provider
+%%% @param Provider the provider object
+-spec impl(Provider::t()) -> module().
+impl({?MODULE, Mod}) ->
+ Mod.
+
%% @doc format an error produced from a provider.
-spec format_error(Reason::term()) -> iolist().
format_error({non_existing, ModuleName}) ->
diff --git a/src/rcl_prv_assembler.erl b/src/rcl_prv_assembler.erl
index a118b12..c0f65c9 100644
--- a/src/rcl_prv_assembler.erl
+++ b/src/rcl_prv_assembler.erl
@@ -73,8 +73,6 @@ format_error({release_script_generation_error, Module, Errors}) ->
["Errors generating release \n",
rcl_util:indent(1), Module:format_error(Errors)].
-
-
%%%===================================================================
%%% Internal Functions
%%%===================================================================
@@ -102,15 +100,20 @@ copy_app(LibDir, App) ->
AppVsn = rcl_app_info:vsn_as_string(App),
AppDir = rcl_app_info:dir(App),
TargetDir = filename:join([LibDir, AppName ++ "-" ++ AppVsn]),
- ec_plists:map(fun(SubDir) ->
- copy_dir(AppDir, TargetDir, SubDir)
- end, ["ebin",
- "include",
- "priv",
- "src",
- "c_src",
- "README",
- "LICENSE"]).
+ case rcl_app_info:link(App) of
+ true ->
+ file:make_symlink(AppDir, TargetDir);
+ false ->
+ ec_plists:map(fun(SubDir) ->
+ copy_dir(AppDir, TargetDir, SubDir)
+ end, ["ebin",
+ "include",
+ "priv",
+ "src",
+ "c_src",
+ "README",
+ "LICENSE"])
+ end.
copy_dir(AppDir, TargetDir, SubDir) ->
SubSource = filename:join(AppDir, SubDir),
@@ -157,7 +160,10 @@ write_bin_file(State, Release, OutputDir, RelDir) ->
ok = ec_file:mkdir_p(BinDir),
VsnRel = filename:join(BinDir, RelName ++ "-" ++ RelVsn),
BareRel = filename:join(BinDir, RelName),
- StartFile = bin_file_contents(RelName, RelVsn, rcl_release:erts(Release)),
+ ErlOpts = rcl_state:get(State, erl_opts, ""),
+ StartFile = bin_file_contents(RelName, RelVsn,
+ rcl_release:erts(Release),
+ ErlOpts),
ok = file:write_file(VsnRel, StartFile),
ok = file:change_mode(VsnRel, 8#777),
ok = file:write_file(BareRel, StartFile),
@@ -275,7 +281,7 @@ get_code_paths(Release, OutDir) ->
rcl_app_info:vsn_as_string(App), "ebin"]) ||
App <- rcl_release:application_details(Release)].
-bin_file_contents(RelName, RelVsn, ErtsVsn) ->
+bin_file_contents(RelName, RelVsn, ErtsVsn, ErlOpts) ->
[<<"#!/bin/sh
set -e
@@ -286,6 +292,7 @@ REL_NAME=">>, RelName, <<"
REL_VSN=">>, RelVsn, <<"
ERTS_VSN=">>, ErtsVsn, <<"
REL_DIR=$RELEASE_ROOT_DIR/releases/$REL_NAME-$REL_VSN
+ERL_OPTS=">>, ErlOpts, <<"
ERTS_DIR=
SYS_CONFIG=
@@ -324,6 +331,4 @@ export EMU=beam
export PROGNAME=erl
export LD_LIBRARY_PATH=$ERTS_DIR/lib
-
-
-$BINDIR/erlexec $SYS_CONFIG -boot $REL_DIR/$REL_NAME $@">>].
+$BINDIR/erlexec $ERL_OPTS $SYS_CONFIG -boot $REL_DIR/$REL_NAME $@">>].
diff --git a/src/rcl_prv_config.erl b/src/rcl_prv_config.erl
index 7a1cd19..ee1c770 100644
--- a/src/rcl_prv_config.erl
+++ b/src/rcl_prv_config.erl
@@ -82,7 +82,8 @@ load_terms({add_providers, Providers0}, {ok, State0}) ->
ExistingProviders = rcl_state:providers(State1),
{ok, rcl_state:providers(State1, ExistingProviders ++ Providers3)}
end;
-
+load_terms({overrides, Overrides0}, {ok, State0}) ->
+ {ok, rcl_state:overrides(State0, Overrides0)};
load_terms({release, {RelName, Vsn}, Applications}, {ok, State0}) ->
Release0 = rcl_release:new(RelName, Vsn),
case rcl_release:goals(Release0, Applications) of
diff --git a/src/rcl_prv_discover.erl b/src/rcl_prv_discover.erl
index 23a3937..0a12d24 100644
--- a/src/rcl_prv_discover.erl
+++ b/src/rcl_prv_discover.erl
@@ -49,16 +49,28 @@ do(State) ->
["Resolving OTP Applications from directories:\n",
[[rcl_util:indent(1), LibDir, "\n"] || LibDir <- LibDirs]]
end),
+ resolve_app_metadata(State, LibDirs, OutputDir).
+-spec format_error([ErrorDetail::term()]) -> iolist().
+format_error(ErrorDetails)
+ when erlang:is_list(ErrorDetails) ->
+ [[format_detail(ErrorDetail), "\n"] || ErrorDetail <- ErrorDetails].
+
+%%%===================================================================
+%%% Internal Functions
+%%%===================================================================
+resolve_app_metadata(State, LibDirs, OutputDir) ->
AppMeta0 = lists:flatten(ec_plists:map(fun(LibDir) ->
discover_dir([OutputDir],
LibDir)
end, LibDirs)),
+ AppMeta1 = setup_overrides(State, AppMeta0),
+
Errors = [case El of
{error, Ret} -> Ret;
_ -> El
end
- || El <- AppMeta0,
+ || El <- AppMeta1,
case El of
{error, _} ->
true;
@@ -66,30 +78,38 @@ do(State) ->
false
end],
- lists:filter(fun({error, _}) -> true;
- (_) -> false
- end, AppMeta0),
case Errors of
[] ->
- AppMeta1 = lists:flatten(AppMeta0),
+ AppMeta2 = lists:flatten(AppMeta1),
rcl_log:debug(rcl_state:log(State),
fun() ->
["Resolved the following OTP Applications from the system: \n",
- [[rcl_app_info:format(1, App), "\n"] || App <- AppMeta1]]
+ [[rcl_app_info:format(1, App), "\n"] || App <- AppMeta2]]
end),
- {ok, rcl_state:available_apps(State, AppMeta1)};
+ {ok, rcl_state:available_apps(State, AppMeta2)};
_ ->
?RCL_ERROR(Errors)
end.
--spec format_error([ErrorDetail::term()]) -> iolist().
-format_error(ErrorDetails)
- when erlang:is_list(ErrorDetails) ->
- [[format_detail(ErrorDetail), "\n"] || ErrorDetail <- ErrorDetails].
+app_name({error, _}) ->
+ undefined;
+app_name(AppMeta) ->
+ rcl_app_info:name(AppMeta).
+
+setup_overrides(State, AppMetas0) ->
+ Overrides = rcl_state:overrides(State),
+ AppMetas1 = [AppMeta || AppMeta <- AppMetas0,
+ not lists:keymember(app_name(AppMeta), 1, Overrides)],
+ [case is_valid_otp_app(filename:join([FileName, "ebin",
+ erlang:atom_to_list(AppName) ++ ".app"])) of
+ [] ->
+ {error, {invalid_override, AppName, FileName}};
+ Error = {error, _} ->
+ Error;
+ App ->
+ rcl_app_info:link(App, true)
+ end || {AppName, FileName} <- Overrides] ++ AppMetas1.
-%%%===================================================================
-%%% Internal Functions
-%%%===================================================================
get_lib_dirs(State) ->
LibDirs0 = rcl_state:lib_dirs(State),
add_rebar_deps_dir(State, LibDirs0).
@@ -133,6 +153,9 @@ add_system_lib_dir(State, LibDirs) ->
end.
-spec format_detail(ErrorDetail::term()) -> iolist().
+format_detail({error, {invalid_override, AppName, FileName}}) ->
+ io_lib:format("Override {~p, ~p} is not a valid OTP App. Perhaps you forgot to build it?",
+ [AppName, FileName]);
format_detail({accessing, File, eaccess}) ->
io_lib:format("permission denied accessing file ~s", [File]);
format_detail({accessing, File, Type}) ->
@@ -172,7 +195,7 @@ discover_dir(IgnoreDirs, File) ->
is_valid_otp_app(File)
end.
--spec is_valid_otp_app(file:name()) -> [rcl_app_info:t() | {error, Reason::term()} | []].
+-spec is_valid_otp_app(file:name()) -> rcl_app_info:t() | {error, Reason::term()} | [].
is_valid_otp_app(File) ->
%% Is this an ebin dir?
EbinDir = filename:dirname(File),
diff --git a/src/rcl_state.erl b/src/rcl_state.erl
index 46c91c7..7397e6f 100644
--- a/src/rcl_state.erl
+++ b/src/rcl_state.erl
@@ -27,6 +27,8 @@
log/1,
output_dir/1,
lib_dirs/1,
+ overrides/1,
+ overrides/2,
goals/1,
config_files/1,
providers/1,
@@ -64,6 +66,7 @@
available_apps = [] :: [rcl_app_info:t()],
default_release :: {rcl_release:name(), rcl_release:vsn()},
sys_config :: file:filename() | undefined,
+ overrides :: [{AppName::atom(), Directory::file:filename()}],
releases :: ec_dictionary:dictionary({ReleaseName::atom(),
ReleaseVsn::string()},
rcl_release:t()),
@@ -96,10 +99,21 @@ new(PropList, Targets) when erlang:is_list(PropList) ->
providers = [],
releases=ec_dictionary:new(ec_dict),
config_values=ec_dictionary:new(ec_dict),
+ overrides = proplists:get_value(overrides, PropList, []),
default_release={proplists:get_value(relname, PropList, undefined),
proplists:get_value(relvsn, PropList, undefined)}},
create_logic_providers(State0).
+%% @doc the application overrides for the system
+-spec overrides(t()) -> [{AppName::atom(), Directory::file:filename()}].
+overrides(#state_t{overrides=Overrides}) ->
+ Overrides.
+
+%% @doc the application overrides for the system
+-spec overrides(t(), [{AppName::atom(), Directory::file:filename()}]) -> t().
+overrides(State, Overrides) ->
+ State#state_t{overrides=Overrides}.
+
%% @doc get the current log state for the system
-spec log(t()) -> rcl_log:t().
log(#state_t{log=LogState}) ->
diff --git a/src/relcool.erl b/src/relcool.erl
index 44debc9..0d75c62 100644
--- a/src/relcool.erl
+++ b/src/relcool.erl
@@ -22,6 +22,7 @@
-export([main/1,
do/7,
+ do/8,
format_error/1,
opt_spec_list/0]).
@@ -59,15 +60,30 @@ main(Args) ->
%% @param OutputDir - The directory where the release should be built to
%% @param Configs - The list of config files for the system
do(RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Configs) ->
+ do(RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, [], Configs).
+
+%% @doc provides an API to run the Relcool process from erlang applications
+%%
+%% @param RelName - The release name to build (maybe `undefined`)
+%% @param RelVsn - The release version to build (maybe `undefined`)
+%% @param Goals - The release goals for the system in depsolver or Relcool goal
+%% format
+%% @param LibDirs - The library dirs that should be used for the system
+%% @param OutputDir - The directory where the release should be built to
+%% @param Overrides - A list of overrides for the system
+%% @param Configs - The list of config files for the system
+do(RelName, RelVsn, Goals, LibDirs, LogLevel, OutputDir, Overrides, Configs) ->
State = rcl_state:new([{relname, RelName},
{relvsn, RelVsn},
{goals, Goals},
+ {overrides, Overrides},
{output_dir, OutputDir},
{lib_dirs, LibDirs},
{log, rcl_log:new(LogLevel)}],
Configs),
run_relcool_process(rcl_state:caller(State, api)).
+
-spec opt_spec_list() -> [getopt:option_spec()].
opt_spec_list() ->
[
@@ -112,14 +128,17 @@ run_providers(State0) ->
{ok, State1} ->
Providers = rcl_state:providers(State1),
Result = run_providers(ConfigProvider, Providers, State1),
- case rcl_state:caller(State1) of
- command_line ->
- init:stop(0);
- api ->
- Result
- end
+ handle_output(State1, rcl_state:caller(State1), Result)
end.
+handle_output(State, command_line, E={error, _}) ->
+ report_error(State, E),
+ init:stop(127);
+handle_output(_State, command_line, _) ->
+ init:stop(0);
+handle_output(_State, api, Result) ->
+ Result.
+
run_providers(ConfigProvider, Providers, State0) ->
case Providers of
[ConfigProvider | Rest] ->
@@ -135,10 +154,13 @@ run_providers(ConfigProvider, Providers, State0) ->
run_provider(_Provider, Error = {error, _}) ->
Error;
run_provider(Provider, {ok, State0}) ->
+ rcl_log:debug(rcl_state:log(State0), "Running provider ~p~n",
+ [rcl_provider:impl(Provider)]),
case rcl_provider:do(Provider, State0) of
{ok, State1} ->
{ok, State1};
E={error, _} ->
+
E
end.
diff --git a/test/rclt_release_SUITE.erl b/test/rclt_release_SUITE.erl
index 7cdf362..c13672a 100644
--- a/test/rclt_release_SUITE.erl
+++ b/test/rclt_release_SUITE.erl
@@ -24,10 +24,12 @@
end_per_suite/1,
init_per_testcase/2,
all/0,
- make_release/1]).
+ make_release/1,
+ make_overridden_release/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
+-include_lib("kernel/include/file.hrl").
suite() ->
[{timetrap,{seconds,30}}].
@@ -47,7 +49,7 @@ init_per_testcase(_, Config) ->
{state, State} | Config].
all() ->
- [make_release].
+ [make_release, make_overridden_release].
make_release(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
@@ -84,6 +86,58 @@ make_release(Config) ->
?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)),
?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)).
+
+make_overridden_release(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ OverrideDir1 = filename:join([DataDir, create_random_name("override_dir_")]),
+ LibDir1 = proplists:get_value(lib1, Config),
+ [(fun({Name, Vsn}) ->
+ create_app(LibDir1, Name, Vsn, [kernel, stdlib], [])
+ end)(App)
+ ||
+ App <-
+ [{create_random_name("lib_app1_"), create_random_vsn()}
+ || _ <- lists:seq(1, 100)]],
+ OverrideApp = create_random_name("override_app"),
+ OverrideVsn = create_random_vsn(),
+ OverrideAppDir = filename:join(OverrideDir1, OverrideApp),
+ OverrideAppName = erlang:list_to_atom(OverrideApp),
+
+ create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []),
+ create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []),
+ create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []),
+ create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]),
+ create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []),
+
+ create_app(OverrideDir1, OverrideApp, OverrideVsn, [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relcool.config"]),
+ write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app_1,
+ erlang:list_to_atom(OverrideApp),
+ goal_app_2]}]),
+ OutputDir = filename:join([proplists:get_value(data_dir, Config),
+ create_random_name("relcool-output")]),
+ {ok, State} = relcool:do(undefined, undefined, [], [LibDir1], 2,
+ OutputDir, [{OverrideAppName, OverrideAppDir}],
+ [ConfigFile]),
+ [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rcl_state:releases(State)),
+ AppSpecs = rcl_release:applications(Release),
+ ?assert(lists:keymember(stdlib, 1, AppSpecs)),
+ ?assert(lists:keymember(kernel, 1, AppSpecs)),
+ ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)),
+ ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)),
+ ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)),
+ ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)),
+ ?assert(lists:member({OverrideAppName, OverrideVsn}, AppSpecs)),
+ ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)),
+ {ok, Real} = file:read_link(filename:join([OutputDir, "lib",
+ OverrideApp ++ "-" ++ OverrideVsn])),
+ ?assertMatch(OverrideAppDir, Real).
+
+
+
%%%===================================================================
%%% Helper Functions
%%%===================================================================