Current section

Files

Jump to
exrm lib mix tasks release.ex
Raw

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