bake-bake-bake / bakeware

Compile Elixir applications into single, easily distributed executable binaries
Other
1.43k stars 45 forks source link

Conflict with escript #113

Open sheepduke opened 3 years ago

sheepduke commented 3 years ago

When bakeware and escript are used at the same time, the generated application will invoke main function twice.

How to reproduce

Taking the simple_script as an example.

Modify mix.exs by adding escript settings, as follows:

defmodule SimpleScript.MixProject do
  use Mix.Project

  @app :simple_script

  def project do
    [
      app: @app,
      version: "0.1.0",
      elixir: "~> 1.10",
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      releases: [{@app, release()}],
      escript: escript(), # <-- !! newly added
      preferred_cli_env: [release: :prod]
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger],
      mod: {SimpleScript, []}
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      {:bakeware, path: "../..", runtime: false}
    ]
  end

  defp release do
    [
      overwrite: true,
      cookie: "#{@app}_cookie",
      quiet: true,
      steps: [:assemble, &Bakeware.assemble/1],
      strip_beams: Mix.env() == :prod
    ]
  end

  # !! newly added
  defp escript do
    [main_module: SimpleScript]
  end
end

After running mix escript.build, you can get the following result:

~/workspace/bakeware/examples/simple_script $ ./simple_script
Pass me some arguments and I will upcase them if you specify --upcase
Pass me some arguments and I will upcase them if you specify --upcase

Questions

My understanding is that when the program starts, module specified by mod is executed, then escript invokes the main function second time.

Now my questions are:

  1. Is it unexpected behavior?
  2. Is there any workaround to bypass it? (Maybe using another option instead of mod?)
ConnorRigby commented 3 years ago

Is it unexpected behavior?

I'm not sure that it isn't unexpected behavior as we didn't add any checks. I think we could work around it in the assembler.

Is there any workaround to bypass it?

the only thing i could think of is having two different modules, one for escript, and one for bakeware. Maybe something like:

defmodule SimpleScript.MixProject do
  use Mix.Project

  @app :simple_script

  def project do
    [
      app: @app,
      version: "0.1.0",
      elixir: "~> 1.10",
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      releases: [{@app, release()}],
      escript: escript(), # <-- !! newly added
      preferred_cli_env: [release: :prod]
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger],
      mod: {SimpleScript.BakewareMain, []} # <- bakeware entry point
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      {:bakeware, path: "../..", runtime: false}
    ]
  end

  defp release do
    [
      overwrite: true,
      cookie: "#{@app}_cookie",
      quiet: true,
      steps: [:assemble, &Bakeware.assemble/1],
      strip_beams: Mix.env() == :prod
    ]
  end

  # !! newly added
  defp escript do
    [main_module: SimpleScript.EscriptMain] # <- escript entrypoint
  end
end

And then have both of those modules (SimpleScript.BakewareMain, SimpleScript.EscriptMain) use some common shared code between them.

All that being said, i'm not sure i personally understand the usecase of having escript and bakeware in the same project. Could you explain your usecase a little bit so that maybe we can make accommodations in the library for it.

CC: @fhunleth

jjcarstens commented 3 years ago

To me, this is an anti-pattern. The use-case for bakeware is in place of other scripting options, like escript. Though I would still be interested to here in the expectations here as I could just clearly be missing something.

Maybe the desire is for interaction during development?

sheepduke commented 3 years ago

And then have both of those modules (SimpleScript.BakewareMain, SimpleScript.EscriptMain) use some common shared code between them.

In fact, even if you do it like this, it still does not work. The mod section under application will still be read and take effect, which leads to exactly the same behavior.

sheepduke commented 3 years ago

All that being said, i'm not sure i personally understand the usecase of having escript and bakeware in the same project. Could you explain your usecase a little bit so that maybe we can make accommodations in the library for it.

Please consider the following scenario:

Alice wrote many Elixir scripts for personal use. She compiled the scripts using escript (instead of Bakeware) to save some disk space, since she always has Erlang/Elixir installed on machine.

One day, Bob was impressed by one of Alice's script and wants to try it, but he does not have Erlang/Elixir installed. In order to make Bob's life easier, Alice decided to build an all-in-one binary using Bakeware since they are using the same OS.

Another day, Alice wanted to add some new function and build a V2 script for herself. Then boom! The escript did not work anymore, unless she commented out the mod line in mix.exs.

To me, this is an anti-pattern. The use-case for bakeware is in place of other scripting options, like escript.

IMHO, it is a valid scenario (I got the same issue as Alice).

I understand that the goal of Bakeware is to replace escript etc, but the ability to distribute software in multiple ways should also be taken into consideration, since Bakeware cannot generate a binary without the runtime.

(Of course my statement will no longer hold if Bakeware can do that, but from the docs I tend to believe it cannot.)

So my ideas are:

  1. Support function to produce a binary without the runtime, i.e. include the function of escript. Then a build warning might be added when escript usage is detected.
  2. Make it work with escript.

The current error is rather confusing (and hard to find) when Bakeware and escript are used together.

What do you think?