zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

project.ex (10132B)


      1 defmodule Dialyxir.Project do
      2   @moduledoc false
      3   import Dialyxir.Output, only: [info: 1, error: 1]
      4 
      5   alias Dialyxir.FilterMap
      6 
      7   def plts_list(deps, include_project \\ true, exclude_core \\ false) do
      8     elixir_apps = [:elixir]
      9     erlang_apps = [:erts, :kernel, :stdlib, :crypto]
     10 
     11     core_plts =
     12       if exclude_core do
     13         []
     14       else
     15         [{elixir_plt(), elixir_apps}, {erlang_plt(), erlang_apps}]
     16       end
     17 
     18     if include_project do
     19       [{plt_file(), deps ++ elixir_apps ++ erlang_apps} | core_plts]
     20     else
     21       core_plts
     22     end
     23   end
     24 
     25   def plt_file() do
     26     plt_path(dialyzer_config()[:plt_file]) || deps_plt()
     27   end
     28 
     29   defp plt_path(file) when is_binary(file), do: Path.expand(file)
     30   defp plt_path({:no_warn, file}) when is_binary(file), do: Path.expand(file)
     31   defp plt_path(_), do: false
     32 
     33   def check_config do
     34     if is_binary(dialyzer_config()[:plt_file]) do
     35       info("""
     36       Notice: :plt_file is deprecated as Dialyxir now uses project-private PLT files by default.
     37       If you want to use this setting without seeing this warning, provide it in a pair
     38       with the :no_warn key e.g. `dialyzer: plt_file: {:no_warn, "~/mypltfile"}`
     39       """)
     40     end
     41   end
     42 
     43   def cons_apps do
     44     # compile & load all deps paths
     45     Mix.Tasks.Deps.Loadpaths.run([])
     46     # compile & load current project paths
     47     Mix.Task.run("compile")
     48     apps = plt_apps() || plt_add_apps() ++ include_deps()
     49 
     50     apps
     51     |> Enum.sort()
     52     |> Enum.uniq()
     53     |> Kernel.--(plt_ignore_apps())
     54   end
     55 
     56   def dialyzer_files do
     57     beam_files =
     58       dialyzer_paths()
     59       |> Enum.flat_map(&beam_files_with_paths/1)
     60       |> Map.new()
     61 
     62     consolidated_files =
     63       Mix.Project.consolidation_path()
     64       |> beam_files_with_paths()
     65       |> Enum.filter(fn {file_name, _path} -> beam_files |> Map.has_key?(file_name) end)
     66       |> Map.new()
     67 
     68     beam_files
     69     |> Map.merge(consolidated_files)
     70     |> Enum.map(fn {_file, path} -> path end)
     71     |> reject_exclude_files()
     72     |> Enum.map(&to_charlist(&1))
     73   end
     74 
     75   defp reject_exclude_files(files) do
     76     file_exclusions = dialyzer_config()[:exclude_files] || []
     77 
     78     Enum.reject(files, fn file ->
     79       :lists.any(
     80         fn reject_file_pattern ->
     81           re = <<reject_file_pattern::binary, "$">>
     82           result = :re.run(file, re)
     83 
     84           case result do
     85             {:match, _captured} -> true
     86             :nomatch -> false
     87           end
     88         end,
     89         file_exclusions
     90       )
     91     end)
     92   end
     93 
     94   defp dialyzer_paths do
     95     paths = dialyzer_config()[:paths] || default_paths()
     96     excluded_paths = dialyzer_config()[:excluded_paths] || []
     97     Enum.map(paths -- excluded_paths, &String.to_charlist/1)
     98   end
     99 
    100   defp beam_files_with_paths(path) do
    101     path |> Path.join("*.beam") |> Path.wildcard() |> Enum.map(&{Path.basename(&1), &1})
    102   end
    103 
    104   def dialyzer_removed_defaults do
    105     dialyzer_config()[:remove_defaults] || []
    106   end
    107 
    108   def dialyzer_flags do
    109     Mix.Project.config()[:dialyzer][:flags] || []
    110   end
    111 
    112   defp skip?({file, warning, line}, {file, warning, line, _}), do: true
    113   defp skip?({file, warning}, {file, warning, _, _}), do: true
    114   defp skip?({file}, {file, _, _, _}), do: true
    115   defp skip?({short_description, warning, line}, {_, warning, line, short_description}), do: true
    116   defp skip?({short_description, warning}, {_, warning, _, short_description}), do: true
    117   defp skip?({short_description}, {_, _, _, short_description}), do: true
    118 
    119   defp skip?(%Regex{} = pattern, {_, _, _, short_description}) do
    120     Regex.match?(pattern, short_description)
    121   end
    122 
    123   defp skip?(_, _), do: false
    124 
    125   def filter_warning?({file, warning, line, short_description}, filter_map = %FilterMap{}) do
    126     {matching_filters, _non_matching_filters} =
    127       filter_map
    128       |> FilterMap.filters()
    129       |> Enum.split_with(&skip?(&1, {file, warning, line, short_description}))
    130 
    131     {not Enum.empty?(matching_filters), matching_filters}
    132   end
    133 
    134   def filter_map(args) do
    135     cond do
    136       legacy_ignore_warnings?() ->
    137         %FilterMap{}
    138 
    139       dialyzer_ignore_warnings() == nil && !File.exists?(default_ignore_warnings()) ->
    140         %FilterMap{}
    141 
    142       true ->
    143         ignore_file = dialyzer_ignore_warnings() || default_ignore_warnings()
    144 
    145         FilterMap.from_file(ignore_file, list_unused_filters?(args), ignore_exit_status?(args))
    146     end
    147   end
    148 
    149   def filter_legacy_warnings(output) do
    150     ignore_file = dialyzer_ignore_warnings()
    151 
    152     if legacy_ignore_warnings?() do
    153       pattern = File.read!(ignore_file)
    154       filter_legacy_warnings(output, pattern)
    155     else
    156       output
    157     end
    158   end
    159 
    160   def filter_legacy_warnings(output, nil), do: output
    161   def filter_legacy_warnings(output, ""), do: output
    162 
    163   def filter_legacy_warnings(output, pattern) do
    164     lines = Enum.map(output, &String.trim_trailing/1)
    165 
    166     patterns =
    167       pattern
    168       |> String.trim_trailing("\n")
    169       |> String.split("\n")
    170       |> Enum.reject(&(&1 == ""))
    171 
    172     try do
    173       Enum.reject(lines, fn line ->
    174         Enum.any?(patterns, &String.contains?(line, &1))
    175       end)
    176     rescue
    177       _ ->
    178         output
    179     end
    180   end
    181 
    182   @spec legacy_ignore_warnings?() :: boolean
    183   defp legacy_ignore_warnings?() do
    184     case dialyzer_ignore_warnings() do
    185       nil ->
    186         false
    187 
    188       ignore_file ->
    189         !String.ends_with?(ignore_file, ".exs")
    190     end
    191   end
    192 
    193   def default_ignore_warnings() do
    194     ".dialyzer_ignore.exs"
    195   end
    196 
    197   def dialyzer_ignore_warnings() do
    198     dialyzer_config()[:ignore_warnings]
    199   end
    200 
    201   def list_unused_filters?(args) do
    202     case Keyword.fetch(args, :list_unused_filters) do
    203       {:ok, list_unused_filters} when not is_nil(list_unused_filters) ->
    204         list_unused_filters
    205 
    206       _else ->
    207         dialyzer_config()[:list_unused_filters]
    208     end
    209   end
    210 
    211   defp ignore_exit_status?(args) do
    212     args[:ignore_exit_status]
    213   end
    214 
    215   def elixir_plt() do
    216     global_plt("erlang-#{otp_vsn()}_elixir-#{System.version()}")
    217   end
    218 
    219   def erlang_plt(), do: global_plt("erlang-" <> otp_vsn())
    220 
    221   defp otp_vsn() do
    222     major = :erlang.system_info(:otp_release) |> List.to_string()
    223     vsn_file = Path.join([:code.root_dir(), "releases", major, "OTP_VERSION"])
    224 
    225     try do
    226       {:ok, contents} = File.read(vsn_file)
    227       String.split(contents, "\n", trim: true)
    228     else
    229       [full] ->
    230         full
    231 
    232       _ ->
    233         major
    234     catch
    235       :error, _ ->
    236         major
    237     end
    238   end
    239 
    240   def deps_plt() do
    241     name = "erlang-#{otp_vsn()}_elixir-#{System.version()}_deps-#{build_env()}"
    242     local_plt(name)
    243   end
    244 
    245   defp build_env() do
    246     config = Mix.Project.config()
    247 
    248     case Keyword.fetch!(config, :build_per_environment) do
    249       true -> Atom.to_string(Mix.env())
    250       false -> "shared"
    251     end
    252   end
    253 
    254   defp global_plt(name) do
    255     Path.join(core_path(), "dialyxir_" <> name <> ".plt")
    256   end
    257 
    258   defp core_path(), do: dialyzer_config()[:plt_core_path] || Mix.Utils.mix_home()
    259 
    260   defp local_plt(name) do
    261     Path.join(local_path(), "dialyxir_" <> name <> ".plt")
    262   end
    263 
    264   defp local_path(), do: dialyzer_config()[:plt_local_path] || Mix.Project.build_path()
    265 
    266   defp default_paths() do
    267     reduce_umbrella_children([], fn paths ->
    268       [Mix.Project.compile_path() | paths]
    269     end)
    270   end
    271 
    272   defp plt_apps, do: dialyzer_config()[:plt_apps] |> load_apps()
    273   defp plt_add_apps, do: dialyzer_config()[:plt_add_apps] || [] |> load_apps()
    274   defp plt_ignore_apps, do: dialyzer_config()[:plt_ignore_apps] || []
    275 
    276   defp load_apps(nil), do: nil
    277 
    278   defp load_apps(apps) do
    279     Enum.each(apps, &Application.load/1)
    280     apps
    281   end
    282 
    283   defp include_deps do
    284     method = dialyzer_config()[:plt_add_deps]
    285 
    286     reduce_umbrella_children([], fn deps ->
    287       deps ++
    288         case method do
    289           false ->
    290             []
    291 
    292           # compatibility
    293           true ->
    294             deps_project() ++ deps_app(false)
    295 
    296           :project ->
    297             info(
    298               "Dialyxir has deprecated plt_add_deps: :project in favor of apps_direct, which includes only runtime dependencies."
    299             )
    300 
    301             deps_project() ++ deps_app(false)
    302 
    303           :apps_direct ->
    304             deps_app(false)
    305 
    306           :transitive ->
    307             info(
    308               "Dialyxir has deprecated plt_add_deps: :transitive in favor of app_tree, which includes only runtime dependencies."
    309             )
    310 
    311             deps_transitive() ++ deps_app(true)
    312 
    313           _app_tree ->
    314             deps_app(true)
    315         end
    316     end)
    317   end
    318 
    319   defp deps_project do
    320     Mix.Project.config()[:deps]
    321     |> Enum.filter(&env_dep(&1))
    322     |> Enum.map(&elem(&1, 0))
    323   end
    324 
    325   defp deps_transitive do
    326     Mix.Project.deps_paths()
    327     |> Map.keys()
    328   end
    329 
    330   @spec deps_app(boolean()) :: [atom]
    331   defp deps_app(recursive) do
    332     app = Mix.Project.config()[:app]
    333     deps_app(app, recursive)
    334   end
    335 
    336   @spec deps_app(atom(), boolean()) :: [atom]
    337   defp deps_app(app, recursive) do
    338     with_each =
    339       if recursive do
    340         &deps_app(&1, true)
    341       else
    342         fn _ -> [] end
    343       end
    344 
    345     case Application.load(app) do
    346       :ok ->
    347         nil
    348 
    349       {:error, {:already_loaded, _}} ->
    350         nil
    351 
    352       {:error, err} ->
    353         nil
    354         error("Error loading #{app}, dependency list may be incomplete.\n #{inspect(err)}")
    355     end
    356 
    357     case Application.spec(app, :applications) do
    358       [] ->
    359         []
    360 
    361       nil ->
    362         []
    363 
    364       this_apps ->
    365         Enum.map(this_apps, with_each)
    366         |> List.flatten()
    367         |> Enum.concat(this_apps)
    368     end
    369   end
    370 
    371   defp env_dep(dep) do
    372     only_envs = dep_only(dep)
    373     only_envs == nil || Mix.env() in List.wrap(only_envs)
    374   end
    375 
    376   defp dep_only({_, opts}) when is_list(opts), do: opts[:only]
    377   defp dep_only({_, _, opts}) when is_list(opts), do: opts[:only]
    378   defp dep_only(_), do: nil
    379 
    380   @spec reduce_umbrella_children(list(), (list() -> list())) :: list()
    381   defp reduce_umbrella_children(acc, f) do
    382     if Mix.Project.umbrella?() do
    383       children = Mix.Dep.Umbrella.loaded()
    384 
    385       Enum.reduce(children, acc, fn child, acc ->
    386         Mix.Project.in_project(child.app, child.opts[:path], fn _ ->
    387           reduce_umbrella_children(acc, f)
    388         end)
    389       end)
    390     else
    391       f.(acc)
    392     end
    393   end
    394 
    395   defp dialyzer_config(), do: Mix.Project.config()[:dialyzer]
    396 end