Packages
exrm
0.5.0
1.0.8
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-rc8
1.0.0-rc7
1.0.0-rc6
1.0.0-rc5
1.0.0-rc4
1.0.0-rc3
0.19.9
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.8
0.18.7
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.0
0.15.3
0.15.2
0.15.1
0.15.0
0.14.17
0.14.16
0.14.15
0.14.14
0.14.13
0.14.12
0.14.11
0.14.10
0.14.9
0.14.8
0.14.7
0.14.6
0.14.5
0.14.3
0.14.2
0.14.1
0.14.0
0.12.12
0.12.11
0.12.10
0.12.9
0.12.8
0.12.7
0.12.6
0.12.5
0.12.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.1
0.10.0
0.9.2
0.9.1
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.7
Exrm, or Elixir Release Manager, provides mix tasks for building, upgrading, and controlling release packages for your application.
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/release.ex
defmodule Mix.Tasks.Release do
@moduledoc """
Build a release for the current mix application.
## Examples
# Build a release using defaults
mix release
# Pass args to erlexec when running the release
mix release --erl="-env TZ UTC"
# Enable dev mode. Make changes, compile using MIX_ENV=prod
# and execute your release again to pick up the changes
mix release --dev
# Set the verbosity level
mix release --verbosity=[silent|quiet|normal|verbose]
You may pass any number of arguments as needed. Make sure you pass arguments
using `--key=value`, not `--key value`, as the args may be interpreted incorrectly
otherwise.
"""
@shortdoc "Build a release for the current mix application."
use Mix.Task
import ReleaseManager.Utils
@_RELXCONF "relx.config"
@_RUNNER "runner"
@_SYSCONFIG "sys.config"
@_RELEASE_DEF "release_definition.txt"
@_RELEASES "{{{RELEASES}}}"
@_NAME "{{{PROJECT_NAME}}}"
@_VERSION "{{{PROJECT_VERSION}}}"
@_ERTS_VSN "{{{ERTS_VERSION}}}"
@_ERL_OPTS "{{{ERL_OPTS}}}"
@_ELIXIR_PATH "{{{ELIXIR_PATH}}}"
def run(args) do
# Ensure this isn't an umbrella project
if Mix.Project.umbrella? do
raise Mix.Error, message: "Umbrella projects are not currently supported!"
end
# Start with a clean slate
Mix.Tasks.Release.Clean.do_cleanup(:build)
# Collect release configuration
config = [ priv_path: Path.join([__DIR__, "..", "..", "..", "priv"]) |> Path.expand,
name: Mix.project |> Keyword.get(:app) |> atom_to_binary,
version: Mix.project |> Keyword.get(:version),
dev: false,
erl: "",
upgrade?: false,
verbosity: :quiet]
config
|> Keyword.merge(args |> parse_args)
|> prepare_relx
|> build_project
|> generate_relx_config
|> generate_runner
|> do_release
info "Your release is ready!"
end
defp prepare_relx(config) do
# Ensure relx has been downloaded
verbosity = config |> Keyword.get(:verbosity)
priv = config |> Keyword.get(:priv_path)
relx = Path.join([priv, "bin", "relx"])
dest = Path.join(File.cwd!, "relx")
case File.copy(relx, dest) do
{:ok, _} ->
dest |> chmod("+x")
# Continue...
config
{:error, reason} ->
if verbosity == :verbose do
error reason
end
error "Unable to copy relx to your project's directory!"
exit(:normal)
end
end
defp build_project(config) do
# Fetch deps, and compile, using the prepared Elixir binaries
verbosity = config |> Keyword.get(:verbosity)
cond do
verbosity == :verbose ->
mix "deps.get", :prod, :verbose
mix "deps.compile", :prod, :verbose
mix "compile", :prod, :verbose
true ->
mix "deps.get", :prod
mix "deps.compile", :prod
mix "compile", :prod
end
# Continue...
config
end
defp generate_relx_config(config) do
# Get configuration
priv = config |> Keyword.get(:priv_path)
name = config |> Keyword.get(:name)
version = config |> Keyword.get(:version)
# Get paths
deffile = Path.join([priv, "rel", "files", @_RELEASE_DEF])
source = Path.join([priv, "rel", @_RELXCONF])
base = Path.join(File.cwd!, "rel")
dest = Path.join(base, @_RELXCONF)
# Get relx.config template contents
relx_config = source |> File.read!
# Get release definition template contents
tmpl = deffile |> File.read!
# Generate release configuration for historical releases
releases = get_releases(name)
|> Enum.map(fn {rname, rver} -> tmpl |> replace_release_info(rname, rver) end)
|> Enum.join
# Set upgrade flag if this is an upgrade release
config = case releases do
"" -> config
_ -> config |> Keyword.merge [upgrade?: true]
end
# Write release configuration
relx_config = relx_config
|> String.replace(@_RELEASES, releases)
|> String.replace(@_ELIXIR_PATH, get_elixir_path() |> Path.join("lib"))
# Replace placeholders for current release
relx_config = relx_config |> replace_release_info(name, version)
# Ensure destination base path exists
File.mkdir_p!(base)
# Write relx.config
File.write!(dest, relx_config)
# Return the project config after we're done
config
end
defp generate_runner(config) do
priv = config |> Keyword.get(:priv_path)
name = config |> Keyword.get(:name)
version = config |> Keyword.get(:version)
erts = :erlang.system_info(:version) |> iolist_to_binary
erl_opts = config |> Keyword.get(:erl)
runner = Path.join([priv, "rel", "files", @_RUNNER])
sysconfig = Path.join([priv, "rel", "files", @_SYSCONFIG])
base = Path.join([File.cwd!, "rel", "files"])
dest = Path.join(base, @_RUNNER)
# Ensure destination base path exists
File.mkdir_p!(base)
debug "Generating boot script..."
contents = File.read!(runner)
|> String.replace(@_NAME, name)
|> String.replace(@_VERSION, version)
|> String.replace(@_ERTS_VSN, erts)
|> String.replace(@_ERL_OPTS, erl_opts)
File.write!(dest, contents)
# Copy default sys.config only if user hasn't provided their own
case Path.join(base, @_SYSCONFIG) |> File.exists? do
true -> :ok
_ -> File.cp!(sysconfig, Path.join(base, @_SYSCONFIG))
end
# Make executable
dest |> chmod("+x")
# Return the project config after we're done
config
end
defp do_release(config) do
debug "Generating release..."
name = config |> Keyword.get(:name)
version = config |> Keyword.get(:version)
verbosity = config |> Keyword.get(:verbosity)
upgrade? = config |> Keyword.get(:upgrade?)
dev_mode? = config |> Keyword.get(:dev)
# If this is an upgrade release, generate an appup
if upgrade? do
# Change mix env for appup generation
with_env :prod do
# Generate appup
app = name |> binary_to_atom
v1 = get_last_release(name)
v1_path = Path.join([File.cwd!, "rel", name, "lib", "#{name}-#{v1}"])
v2_path = Mix.Project.config |> Mix.Project.compile_path |> String.replace("/ebin", "")
own_path = Path.join([File.cwd!, "rel", "#{name}.appup"])
# Look for user's own .appup file before generating one
case own_path |> File.exists? do
true ->
# Copy it to ebin
case File.cp(own_path, Path.join([v2_path, "/ebin", "#{name}.appup"])) do
:ok ->
info "Using custom .appup located in rel/#{name}.appup"
{:error, reason} ->
error "Unable to copy custom .appup file: #{reason}"
exit(:normal)
end
_ ->
# No custom .appup found, proceed with autogeneration
case ReleaseManager.Appups.make(app, v1, version, v1_path, v2_path) do
{:ok, _} ->
info "Generated .appup for #{name} #{v1} -> #{version}"
{:error, reason} ->
error "Appup generation failed with #{reason}"
exit(:normal)
end
end
end
end
# Do release
case relx name, version, verbosity, upgrade?, dev_mode? do
:ok ->
# Clean up template files
Mix.Tasks.Release.Clean.do_cleanup(:relfiles)
# Continue..
config
{:error, message} ->
error message
exit(:normal)
end
end
defp parse_args(argv) do
{args, _, _} = OptionParser.parse(argv)
args |> Enum.map(&parse_arg/1)
end
defp parse_arg({:verbosity, verbosity}), do: {:verbosity, binary_to_atom(verbosity)}
defp parse_arg({_key, _value} = arg), do: arg
defp replace_release_info(template, name, version) do
template
|> String.replace(@_NAME, name)
|> String.replace(@_VERSION, version)
end
end