Learn With Me: Elixir - Application Packaging (#63)
An Elixir application can be packaged into a single, independent, standalone file that you can run. This file is actually a zip archive that contains the precompiled code that was built by mix. Since it contains compiled binaries, it can only run on machines with the same architecture.
This is a great way to encapsulate the entire project into one easy package for copying and deployment, but it not really used for that. The documentation actually advises against using this as a deployment mechanism. They recommend using this only for packaging up an application to send to another developer. For deployment to live systems, they recommend using "mix run" or building a release, neither of which I've learned about yet. Elixir sure has a lot of ways to run an application, and here's another one of them.
When you create an application package, you will be able to use Erlang's escript utility to run the package. When escript runs your project, it will look at the mix.exs file to see which module is the main module. It will then call the main/1
function in the main module, so you'll need to be sure that you define a "main" function that accepts command line arguments.
The Steps to Create an Application Package
I'm going to walk through the steps to create an application package. I created a new project called "package_app" by running mix new package_app
.
You can either follow along or go look at the final result in the the "lwm 63 - Application Packaging" folder in the code examples in the Learn With Me: Elixir repository on Github.
Now that I've generated the application project, let's make it into an application package.
- Modify mix.exs
I'm going to modify the project/0
function in mix.exs by adding an "escript" key with the value being defined by a call to an escript_config/0
function, which is another function in mix.exs that defines the escript configuration. I created the escript_config/0
function, since it did not already exist.
Here's the modified project/0
function.
def project do
[
app: :package_app,
version: "0.1.0",
elixir: "~> 1.8",
start_permanent: Mix.env() == :prod,
escript: escript_config(),
deps: deps()
]
end
Here's the escript_config/0
that I added.
defp escript_config do
[
main_module: PackageApp.CLI
]
end
This indicates that the application's main entry function is in the PackageApp.CLI
module. The escript utility will always call the main/1
function in the main module.
Note that it's convention for an Elixir command line application to have a module called [AppName].CLI that provides the main entry function that will parse and handle any arguments from the command line and get things running.
- Create the Main Module
I renamed "lib/package_app.ex" to "lib/package_app_cli.ex" and put the following code into it.
defmodule PackageApp.CLI do
def main(argv) do
#Process the command line arguments and then call another method
#that will actually run something
IO.puts "Running PackageApp"
IO.puts "Command line arguments: #{inspect(argv)}"
end
end
The escript utility will automatically call the main/1
function I've just created.
- Build the Package
Now I'm going to build the package by running mix escript.build
.
> mix escript.build
Compiling 1 file (.ex)
Generated package_app app
Generated escript package_app with MIX_ENV=dev
That will create an application package with the same name as the application.
> ls -l
total 1042
-rw-r--r-- 1 kpeter 1049089 516 Feb 12 09:59 README.md
drwxr-xr-x 1 kpeter 1049089 0 Feb 12 10:40 _build/
drwxr-xr-x 1 kpeter 1049089 0 Feb 12 09:59 config/
drwxr-xr-x 1 kpeter 1049089 0 Feb 12 10:36 lib/
-rw-r--r-- 1 kpeter 1049089 689 Feb 12 10:19 mix.exs
-rwxr-xr-x 1 kpeter 1049089 1064453 Feb 12 10:40 package_app*
drwxr-xr-x 1 kpeter 1049089 0 Feb 12 09:59 test/
In this case, the package is about 1MB. It contains everything needed to run on the Erlang VM.
If you open that package file as a zip archive, you'll see all the files that are packaged inside it. They're mostly compiled .beam and .app files. Along with the compiled application code, there are .beam files in there for all the modules in Elixir standard library as well as for all the dependencies of the application, so everything the application needs is in that package.
You can look at the escript build options to see further options for building an application package.
I'm not sure how data files are handled: I think the application package only includes code files. I suspect that any data files would have to be shipped along with the code package.
Running the Application Package
On a Unix system, you should be able to just run the application by typing in "./my_application [args]". On a Windows system, you need to pass the package file to escript: "escript my_application [args]".
Let's give it a try.
> ./package_app
Running PackageApp
Command line arguments: []
It printed out an empty list since I didn't supply any command line arguments. Let's try running that again with some command line arguments.
> ./package_app --name bob --copy 23
Running PackageApp
Command line arguments: ["--name", "bob", "--copy", "23"]
Very nice.
You can take that package file and run it on any computer (with the same architecture) where Erlang is installed. Note that Elixir does not need to be installed, since the escript package contains all the Elixir library code compiled to Erlang bytecode. It truly does have everything needed to run in the Erlang VM.
The Erlang VM isn't aware of Elixir; it runs whatever bytecode is sent to it. This is the same concept as any .NET language compiled into .NET bytecode or languages like Clojure or Scala being compiled to JVM bytecode. The virtual machines that run the bytecode are unaware of what produced the bytecode.
I'm not sure why the same architecture is required. I think the beam bytecode itself isn't architecture specific, so there must be something that is. Perhaps there are endian issues or there's some component that's non-portable. I hear that some packages may contain C code that gets compiled for a particular machine, but I don't know of any examples. I'm just speculating at this point.