(In 2024 this approach is outdated)



This explains some details on the mix configuration cycle and how that changes with release. In the way I am assembling applications to create a release, I needed a way to use Config.import_config so that it would only import a config once. Depending on the system being built the imported configs are a sensible, but because one config can import another an apparent cyclicality arises even though the path of configuration for a single component is not.



To address this problem I wrote import_once. Which looks like this:

  defmacro import_once( atom ) do
    quote do
      if not Enum.member?( Process.get( { Config,:files } ),
            Path.expand( "#{unquote( atom )}.exs", __DIR__ ) ) do
        Config.import_config( "#{unquote( atom )}.exs" )
      end
    end
  end

As Config works through files it adds their full paths to the process dictionary under the key {Config,:files}. It's the way Config knows that it already consumed that config and raises an error if it has. This import_once uses the same process key to conditionally import only if it is previously unseen.

Originally I was using:

Process.put( { Config, unquote( atom ) }, true )

for each config visited. This works during development. During releases it breaks because the mix release runs the configuration cycle twice. Once to set itself up and then again to build the sys.config. On the second pass the resulting sys.config was missing all imported config files. When release begins the config cycle the second time, it wipes out the {Config,:config} and the {Config,:files}, but since I was using other keys, my accounting indicated that the configuration was already done.

By changing to using member? on the {Config,:files} the accounting was both done for me and was reset when release started the second cycle.