Fl4m3Ph03n1x

Fl4m3Ph03n1x

Background

I have an umbrella project with several apps inside. I want to create a special release that will have additional files inside, like JSON files, configuration, images etc.

I know that in a normal Phoenix project everything inside the priv folder will be in the release under "#{child-app-name}-#{child-app-version}/priv".

Problem

The issue here is that some of my configuration files depend on these assets. So in my config/prod.exs I have tried several ways to get the path dynamically, bu nothing works. The following examples all fail:

config :my_app,
  products: "application-data\\lib\\web_interface-#{Application.spec(:web_interface, :vsn) |> to_string()}\\priv\\persistence\\products.json" 

config :my_app,
  products: "application-data\\lib\\web_interface-#{WebInterface.Mixfile.project()[:version]}\\priv\\persistence\\products.json"

config :my_app,
  products:  "application-data\\lib\\web_interface-#{Application.get_application(:web_interface)}\\priv\\persistence\\products.json"

The one that works is as follows:

products: "application-data\\lib\\web_interface-2.2.0\\priv\\persistence\\products.json",

You can see the issue here is that the version number will change from release to release, and I don’t want to change my prod.exs every time a new release is made.

My solution would be to move the assets I depend on to the root level, so my configuation can access them via "application-data\\assets\\products.json or something similar.

Questions

Is this possible to achieve, given that I am building a tar in my release?

Showing Posts 1 to 10

LostKobrakai

LostKobrakai

You should be using Application.app_dir(:app_name, "priv/dir/…") in your code to reference files in priv/ folder. You can use configuration for the second parameter if needed.

entone

entone

Additionally you can use the :code.priv_dir/1 function

LostKobrakai

LostKobrakai

Yeah, it’s using charlists though and is imo not as nice to work with from elixir.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

Using:

  products:
    "application-data\\lib\\web_interface-#{Application.app_dir(:web_interface, "priv/")}"
    |> IO.inspect(label: "PRODUCTS"),

results in:

** (ArgumentError) unknown application: :web_interface
    (elixir 1.15.6) lib/application.ex:1010: Application.app_dir/1
    (elixir 1.15.6) lib/application.ex:1037: Application.app_dir/2
    (stdlib 5.1.1) erl_eval.erl:746: :erl_eval.do_apply/7
    (stdlib 5.1.1) erl_eval.erl:325: :erl_eval.expr/6
    (stdlib 5.1.1) eval_bits.erl:106: :eval_bits.eval_field/5
    (stdlib 5.1.1) eval_bits.erl:71: :eval_bits.expr_grp1/6
    (stdlib 5.1.1) eval_bits.erl:67: :eval_bits.expr_grp/5
    (stdlib 5.1.1) erl_eval.erl:545: :erl_eval.expr/6

If you mean however, that my application code should reference Application.app_dir(:web_interface, "priv/") then this is not possible. The applications are independent from each other. The only thing each child app requires is a path to a folder with assets.

I cannot tie any child app to another app by mentioning the other app’s name in the code. This is not a solution I can implement, which is why I am trying to understand what can be done at the level of releases or prod.exs files.

Same applies for :code.priv_dir.

LostKobrakai

LostKobrakai

You can configure e.g. {:app_name, rel_path} and transform that to Application.app_dir/2 calls at runtime. That’s e.g. how Plug.Static is to be configured.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

So, instead of:

 products:
    "application-data\\lib\\web_interface-#{Application.app_dir(:web_interface, "priv/")}"
    |> IO.inspect(label: "PRODUCTS"),

I would have:

 products:
    {:web_interface, "priv/..."}",

And then in the code instead of having:

@products_file compile_env!(:my_app, :products)

#....

case File.read(products_file) do 
  # ...
end

I would instead have:

@products_file compile_env!(:my_app, :products)

#....

case products_file |>  Application.app_dir() |> File.read() do 
  # ...
end

Correct?

I can see this working.
However, would this be advised over moving the folders inside of the release tar to a new position?

LostKobrakai

LostKobrakai

That’s the way to go yes.

christhekeele

christhekeele

FWIW these days I register these release-resiliant path concerns in my application’s compile-time config.exs. I find it to be more reliable and easier to reason about if I do it myself:

# config/config.exs
import Config

root_dir = Path.expand("../..", __DIR__)
priv_dir = Path.expand(Path.join(project_root, "priv"))
assets_dir = Path.expand(Path.join(project_root, "assets"))
static_dir = Path.expand(Path.join(priv_dir, "static"))
static_assets_dir = Path.expand(Path.join(static_dir, "assets"))

config :project, :root_dir, root_dir
config :project, :priv_dir, priv_dir
config :project, :assets_dir, assets_dir
config :project, :static_dir, static_dir
config :project, :static_assets_dir, static_assets_dir

# Use variables above to configure things like `:esbuild` in this file

# Use `Application.compile_env!(:project, :static_assets_dir)`
# to configure things like `Plug.Static`

This works well for umbrella apps, since the each have their own config :app_name space.

For often-referenced paths, I’ll even add a shortcut in my project’s main entrypoint module:

# lib/project/application.ex
defmodule Project.Application do
  def app_name, do: :project
  def root_dir, do: Application.compile_env!(app_name(), :root_dir)
  def static_dir, do: Path.relative_to(Application.compile_env!(app_name(), :static_dir), root_dir())
end

Then, for example, configuring Plug.Static can look like:

# lib/project_web/endpoint.ex
plug Plug.Static,
  at: "/",
  from: {Project.Application.name(), Project.Application.static_dir()}
LostKobrakai

LostKobrakai

Tbh that feels a bit bend over backwards to start with relative paths, expand them to absolute paths for the context of the mix project, just to turn them back into relative paths within the application code again. But I do like the approach of having functions return the paths. I’ve used that approach as well in some projects. It puts a clear interface of what paths are provided by an application / which ones are used by downstream consumers.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

I am generally not comfortable having functions that return configuration settings.

To me, configurations are different from the functionality an application offers, they are a prerequisite and should therefore have their own space, instead of polluting the main API that I expose to the public.

Still, if nothing else works, I will definitely use such approaches, I find code that actually does something better than code that is theoretically more perfect, but then does nothing :stuck_out_tongue:

Also, thanks @christhekeele for your input. I appreciate you took the time to read all the threads and then took even more time to add your own opinion, even though the topic is becoming long and I had marked it as solved.

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews