check-elixir-applications.exs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. #!/usr/bin/env elixir
  2. defmodule CheckElixirApplications do
  3. alias EMQXUmbrella.MixProject
  4. @default_applications [:kernel, :stdlib, :sasl]
  5. def main() do
  6. {:ok, _} = Application.ensure_all_started(:mix)
  7. File.cwd!()
  8. |> Path.join("mix.exs")
  9. |> Code.compile_file()
  10. inputs = MixProject.check_profile!()
  11. profile = Mix.env()
  12. # produce `rebar.config.rendered` to consult
  13. File.cwd!()
  14. |> Path.join("rebar3")
  15. |> System.cmd(["as", to_string(profile)],
  16. env: [{"DEBUG", "1"}]
  17. )
  18. mix_apps = mix_applications(inputs.edition_type)
  19. rebar_apps = rebar_applications(profile)
  20. results = diff_apps(mix_apps, rebar_apps)
  21. report_discrepancy(
  22. results[:missing_apps],
  23. "* There are missing applications in the Elixir release",
  24. fn %{app: app, mode: mode, after: last_app} ->
  25. IO.puts(" * #{app}: #{inspect(mode)} should be placed after #{inspect(last_app)}")
  26. end
  27. )
  28. report_discrepancy(
  29. results[:different_modes],
  30. "* There are applications with different application modes in the Elixir release",
  31. fn %{app: app, rebar_mode: rebar_mode, mix_mode: mix_mode} ->
  32. IO.puts(
  33. " * #{inspect(app)} should have mode #{inspect(rebar_mode)}, but it has mode #{inspect(mix_mode)}"
  34. )
  35. end
  36. )
  37. report_discrepancy(
  38. results[:different_positions],
  39. "* There are applications in the Elixir release in the wrong order",
  40. fn %{app: app, mode: mode, after: last_app} ->
  41. IO.puts(" * #{app}: #{inspect(mode)} should be placed after #{inspect(last_app)}")
  42. end
  43. )
  44. success? =
  45. results
  46. |> Map.take([:missing_apps, :different_modes, :different_positions])
  47. |> Map.values()
  48. |> Enum.concat()
  49. |> Enum.empty?()
  50. if not success? do
  51. System.halt(1)
  52. else
  53. IO.puts(
  54. IO.ANSI.green() <>
  55. "Mix and Rebar applications OK!" <>
  56. IO.ANSI.reset()
  57. )
  58. end
  59. end
  60. defp mix_applications(edition_type) do
  61. EMQXUmbrella.MixProject.applications(edition_type)
  62. end
  63. defp rebar_applications(profile) do
  64. {:ok, props} =
  65. File.cwd!()
  66. |> Path.join("rebar.config.rendered")
  67. |> :file.consult()
  68. props[:profiles][profile][:relx]
  69. |> Enum.find(&(elem(&1, 0) == :release))
  70. |> elem(2)
  71. |> Enum.map(fn
  72. app when is_atom(app) ->
  73. {app, :permanent}
  74. {app, mode} ->
  75. {app, mode}
  76. end)
  77. |> Enum.reject(fn {app, _mode} ->
  78. # Elixir already includes those implicitly
  79. app in @default_applications
  80. end)
  81. end
  82. defp diff_apps(mix_apps, rebar_apps) do
  83. app_names = Keyword.keys(rebar_apps)
  84. mix_apps = Keyword.filter(mix_apps, fn {app, _mode} -> app in app_names end)
  85. acc = %{
  86. mix_apps: mix_apps,
  87. missing_apps: [],
  88. different_positions: [],
  89. different_modes: [],
  90. last_app: nil
  91. }
  92. Enum.reduce(
  93. rebar_apps,
  94. acc,
  95. fn
  96. {rebar_app, rebar_mode}, acc = %{mix_apps: [], last_app: last_app} ->
  97. missing_app = %{
  98. app: rebar_app,
  99. mode: rebar_mode,
  100. after: last_app
  101. }
  102. acc
  103. |> Map.update!(:missing_apps, &[missing_app | &1])
  104. |> Map.put(:last_app, rebar_app)
  105. {rebar_app, rebar_mode},
  106. acc = %{mix_apps: [{mix_app, mix_mode} | rest], last_app: last_app} ->
  107. case {rebar_app, rebar_mode} do
  108. {^mix_app, ^mix_mode} ->
  109. acc
  110. |> Map.put(:mix_apps, rest)
  111. |> Map.put(:last_app, rebar_app)
  112. {^mix_app, _mode} ->
  113. different_mode = %{
  114. app: rebar_app,
  115. rebar_mode: rebar_mode,
  116. mix_mode: mix_mode
  117. }
  118. acc
  119. |> Map.put(:mix_apps, rest)
  120. |> Map.update!(:different_modes, &[different_mode | &1])
  121. |> Map.put(:last_app, rebar_app)
  122. {_app, _mode} ->
  123. case Keyword.pop(rest, rebar_app) do
  124. {nil, _} ->
  125. missing_app = %{
  126. app: rebar_app,
  127. mode: rebar_mode,
  128. after: last_app
  129. }
  130. acc
  131. |> Map.update!(:missing_apps, &[missing_app | &1])
  132. |> Map.put(:last_app, rebar_app)
  133. {^rebar_mode, rest} ->
  134. different_position = %{
  135. app: rebar_app,
  136. mode: rebar_mode,
  137. after: last_app
  138. }
  139. acc
  140. |> Map.update!(:different_positions, &[different_position | &1])
  141. |> Map.put(:last_app, rebar_app)
  142. |> Map.put(:mix_apps, [{mix_app, mix_mode} | rest])
  143. {mode, rest} ->
  144. different_mode = %{
  145. app: rebar_app,
  146. rebar_mode: rebar_mode,
  147. mix_mode: mode
  148. }
  149. different_position = %{
  150. app: rebar_app,
  151. mode: rebar_mode,
  152. after: last_app
  153. }
  154. acc
  155. |> Map.put(:mix_apps, [{mix_app, mix_mode} | rest])
  156. |> Map.update!(:different_modes, &[different_mode | &1])
  157. |> Map.update!(:different_positions, &[different_position | &1])
  158. |> Map.put(:last_app, rebar_app)
  159. end
  160. end
  161. end
  162. )
  163. end
  164. defp report_discrepancy(diffs, header, line_fn) do
  165. unless Enum.empty?(diffs) do
  166. IO.puts(IO.ANSI.red() <> header)
  167. diffs
  168. |> Enum.reverse()
  169. |> Enum.each(line_fn)
  170. IO.puts(IO.ANSI.reset())
  171. end
  172. end
  173. end
  174. CheckElixirApplications.main()