How do you include but not start an application in multiple applications

Hi everyone, I’ve been trying to figure out how to do this cleanly; I hope maybe one of you can help.

Suppose I have an umbrella project where I have a common application app_common that has modules I want to use like a library in two other applications, app_a and app_b. To be clear, I want app_common to not be started; I only want its modules to be available in a release.

I can add app_common to the applications lists in app_a.app.src and app_b.app.src, and when I build the release it will work, but app_common will be started. If on the other hand I put it in their included_applications lists instead, I get the error:

===> Error generating release:
Duplicated application included:
	app_common included in app_a and app_b

What is the idiomatic/clean way to achieve this goal?

Thanks for all you do!!!

If you are using rebar3, you could set the start type of the application to none:

e.g. for crypto:

{relx, [{release, {johnny, "0.1.0"},
         [johnny,
          sasl,
          {crypto, "5.1.4.1", none}]},
          ...

crypto is not started:

dev@f14:~/playground/erlang/johnny $ _build/default/rel/johnny/bin/johnny console
[..]
Erlang/OTP 25 [erts-13.2.2.4] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:30] [jit:ns] [dtrace]

Eshell V13.2.2.4  (abort with ^G)
(johnny@f14)1> application:loaded_applications().
[{stdlib,"ERTS  CXC 138 10","4.3.1.3"},
 {kernel,"ERTS  CXC 138 10","8.5.4.2"},
 {johnny,"An OTP application","0.1.0"},
 {sasl,"SASL  CXC 138 11","4.2"}]
(johnny@f14)2>

I did not know about this feature, thank you for this. Unfortunately though, I don’t think it resolves my problem. Your example only works if I list app_common in the applications list of app_a.app.src and app_b.app.src, and it only works for that one release configuration. Anyone else who wishes to use app_a and app_b would need to know to add this to their release configuration; I think most developers would expect this kind of nuance to be hidden from them so they can just add the apps to their projects without having to do extra steps.

The only solution I can think of is to move the application module from app_common into a dedicated application and make app_common a pure library application. Is that the “clean” way? Seems wasteful.

I think the cleanest way to implement this is to write a library application. rebar3 new lib <name> does the scaffolding.
A library application does not contain an application controller and therefore also no supervision tree.
I use such library applications for stateless functions.
Would your use case fit that description?

You can try with optional_application. Semantically sounds wrong, but if application is listed only in optional applications and not in applications it shouldn’t be started. Then, in you top level app, you can check if it is loaded and exit if it’s not.

May I ask why you don’t want the application to be started?

1 Like

@dischoen yes, I think you’re ultimately right… refactor the code into libraries and applications. it’s a last resort though because in my actual situation, this would impact a lot of code. fwiw i did try adding {app_common, "0.1.0", none} to the release and it not only prevented app_common from starting but also app_a and app_b. :confused:

@mmin this is very promising! i just ran some local tests… if i start app_a and app_b this way using rebar3 shell --apps app_a,app_b it works perfectly; the apps are started without app_common. however, when i build the release and run _build/prod/rel/test/bin/test console and check the running apps, app_common is running. is there some secret release parameter i need to do? right now it’s just {release, {test, "0.1.0"}, [app_a, app_b, sasl]}.

@asabil, yeah, I’ve been simplifying my actual situation in this thread. In reality, I have an umbrella project that has been operating for a long time, and over time some of the applications became dependencies in later projects, and those projects became dependencies in later projects, and now I want replace the running application part of one of the inner applications with something else, and I can’t have two running at the same time… yada yada. Usual code creep. But I would add this is good to understand in general. I want to learn to structure my code appropriately. Most programming environments have a package management and dependency framework in place… erlang’s is a bit more complex in that it combines code dependencies with running process dependencies. The better I understand how the release system handles dependencies, the better my code will be.

In writing this reply, I’ve concluded that the clean way to resolve this is to do a proper separation of concerns and isolate library code into library applications and expect “app” applications to run.

Again, thanks everyone!

Just to explain a bit what happens “under the hood”: the standard way to share modules but not runtime structure in Erlang/OTP is to create a library application. This is done by simply omitting the mod attribute, meaning the application is not implementing the application behavior (and thus not having any processes started). Then you just depend on it as usual. However, it will be started (as an empty placeholder) in your system.

1 Like

Makes sense. This has implications on how to arrange my code though. If an application would start a supervisor that perhaps other code may want to launch independently, then it makes sense to put the supervisor and its code in a separate library application and have the top application be nothing but a single module implementing application that starts the supervisor in a dependency library application. This is fine and makes complete sense, but it’s a departure from what I learned/gleaned from using rebar3 new app which auto-creates a supervisor inside the application. I’m grateful to all of you at erlangforums for being there to teach me :slight_smile:

Right, the case you describe is sometimes used in library applications. With these tools you can mix and match as much as you want. You can have a dependency start some process but not all, start no processes or run and manage all processes that users want. In fact, processes is just one kind of state that an application can manage, you have other things as well such as ETS tables, NIF state, persistent terms etc. that can be managed by the application/supervisor behaviors.

Rebar actually has a template for library applications as well: rebar3 new lib name=mylibapp

This shouldn’t happen I think, because in the docs it says that: Note if you want an optional dependency to be automatically started before the current application whenever it is available, it must be listed on both applications and optional_applications. So, my reasoning is that if you only mention it in optional_applications it shouldn’t get started. I’ll try to investigate this more because I currently think it’s a bug.

I think it’s valuable to have this option with optional_applications because imagine that app_common is not owned by you, but is a dependency :smiley: