From 7db0ea529074b6fd10871fdf86822cd2bda81077 Mon Sep 17 00:00:00 2001 From: Milhouse Date: Fri, 15 May 2026 11:42:42 -0300 Subject: [PATCH] Narrow compile_env to leaf keys in :persistence and :cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Application.compile_env/3` was previously called on the entire `:persistence` and `:cache` keyword lists, which caused every key inside those lists to be tracked at compile time — including keys that the library only ever reads at runtime (`:repo`, `:adapter`, `:ttl`). Any host application that legitimately overrode those keys in `runtime.exs` would trip Elixir's release-boot `validate_compile_env` check and abort. Only three leaves are actually consumed at compile time: * `:persistence.ecto_table_name` — interpolated into `schema(...)` in `FunWithFlags.Store.Persistent.Ecto.Record`. * `:persistence.ecto_primary_key_type` — used in the `@primary_key` attribute in the same module. * `:cache.enabled` — used by `store_module_determined_at_compile_time/0` to pick between `FunWithFlags.Store` and `FunWithFlags.SimpleStore`. This change uses path-style `Application.compile_env/3` to track only those leaves. All other keys in `:persistence` and `:cache` are once again free to be overridden at runtime without causing a boot failure, restoring the v1.6.0 intent of not compiling in the Ecto repo while keeping the v1.9.0 safety check for the keys that actually need it. --- CHANGELOG.md | 1 + lib/fun_with_flags/config.ex | 60 ++++++++++++++++++++---------------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81c83ca3..c66f06fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v1.14.0 (unreleased) * Drop support for Erlang/OTP 25, and Erlang/OTP >= 26 is now required. Dropping support for older versions of Erlang/OTP simply means that this package is not tested with them in CI, and that no compatibility issues are considered bugs. +* Fix: runtime overrides of `:repo`, `:adapter` (in `:persistence`) and `:ttl` (in `:cache`) via `runtime.exs` no longer cause Elixir's release-boot `validate_compile_env` check to abort. Only the keys that are genuinely baked into compiled code (`:ecto_table_name`, `:ecto_primary_key_type`, and `:cache` → `:enabled`) are now tracked at compile time. No changes required in host applications. ## v1.13.0 diff --git a/lib/fun_with_flags/config.ex b/lib/fun_with_flags/config.ex index 02667be2..ca38ae08 100644 --- a/lib/fun_with_flags/config.ex +++ b/lib/fun_with_flags/config.ex @@ -65,18 +65,17 @@ defmodule FunWithFlags.Config do # Used to determine the store module at compile time, which is stored in a # module attribute. `Application.compile_env` cannot be used in functions, # so here we are. - @compile_time_cache_config Application.compile_env(:fun_with_flags, :cache, []) + @compile_time_cache_enabled Application.compile_env( + :fun_with_flags, + [:cache, :enabled], + Keyword.fetch!(@default_cache_config, :enabled) + ) # If we're not using the cache, then don't bother with # the 2-level logic in the default Store module. # def store_module_determined_at_compile_time do - cache_conf = Keyword.merge( - @default_cache_config, - @compile_time_cache_config - ) - - if Keyword.get(cache_conf, :enabled) do + if @compile_time_cache_enabled do FunWithFlags.Store else FunWithFlags.SimpleStore @@ -84,26 +83,33 @@ defmodule FunWithFlags.Config do end - # Used to determine the Ecto table name at compile time. - @compile_time_persistence_config Application.compile_env(:fun_with_flags, :persistence, []) - - - def ecto_table_name_determined_at_compile_time do - pers_conf = Keyword.merge( - @default_persistence_config, - @compile_time_persistence_config - ) - Keyword.get(pers_conf, :ecto_table_name) - end - - - def ecto_primary_key_type_determined_at_compile_time do - pers_conf = Keyword.merge( - @default_persistence_config, - @compile_time_persistence_config - ) - Keyword.get(pers_conf, :ecto_primary_key_type) - end + # Only the specific keys that are read at compile time are tracked here. + # `:ecto_table_name` is interpolated into the `schema(...)` macro call in + # `FunWithFlags.Store.Persistent.Ecto.Record`, and `:ecto_primary_key_type` + # is used in the `@primary_key` module attribute in the same module — both + # must be compile-time constants. + # + # Other keys in the `:persistence` config (`:repo`, `:adapter`) are read at + # runtime via `persistence_config/0` and must NOT be captured here. Snapshotting + # the whole keyword list would cause Elixir's release-boot `validate_compile_env` + # check to abort whenever a host application legitimately overrides a runtime-only + # key — for example pointing `:repo` at a read replica in a specific app profile. + @compile_time_ecto_table_name Application.compile_env( + :fun_with_flags, + [:persistence, :ecto_table_name], + Keyword.fetch!(@default_persistence_config, :ecto_table_name) + ) + + @compile_time_ecto_primary_key_type Application.compile_env( + :fun_with_flags, + [:persistence, :ecto_primary_key_type], + Keyword.fetch!(@default_persistence_config, :ecto_primary_key_type) + ) + + + def ecto_table_name_determined_at_compile_time, do: @compile_time_ecto_table_name + + def ecto_primary_key_type_determined_at_compile_time, do: @compile_time_ecto_primary_key_type defp persistence_config do