2024-06-01 14:09:38 +12:00
|
|
|
defmodule Igniter.Install do
|
2024-06-01 14:59:36 +12:00
|
|
|
@moduledoc false
|
2024-06-01 14:09:38 +12:00
|
|
|
@option_schema [
|
|
|
|
switches: [
|
|
|
|
example: :boolean,
|
2024-06-01 14:59:36 +12:00
|
|
|
dry_run: :boolean,
|
|
|
|
yes: :boolean
|
2024-06-01 14:09:38 +12:00
|
|
|
],
|
|
|
|
aliases: [
|
|
|
|
d: :dry_run,
|
2024-06-01 14:59:36 +12:00
|
|
|
e: :example,
|
|
|
|
y: :yes
|
2024-06-01 14:09:38 +12:00
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
# only supports hex installation at the moment
|
|
|
|
def install(install, argv) do
|
2024-06-01 14:59:36 +12:00
|
|
|
install_list = install_list(install)
|
2024-06-01 14:09:38 +12:00
|
|
|
|
|
|
|
Application.ensure_all_started(:req)
|
|
|
|
|
|
|
|
{options, _} =
|
|
|
|
OptionParser.parse!(argv, @option_schema)
|
|
|
|
|
|
|
|
argv = OptionParser.to_argv(options)
|
|
|
|
|
|
|
|
igniter = Igniter.new()
|
|
|
|
|
|
|
|
igniter =
|
|
|
|
Enum.reduce(install_list, igniter, fn install, igniter ->
|
2024-06-01 15:50:29 +12:00
|
|
|
if local_dep?(install) do
|
2024-06-01 14:09:38 +12:00
|
|
|
Mix.shell().info(
|
|
|
|
"Not looking up dependency for #{install}, because a local dependency is detected"
|
|
|
|
)
|
|
|
|
|
|
|
|
igniter
|
|
|
|
else
|
|
|
|
case Req.get!("https://hex.pm/api/packages/#{install}").body do
|
|
|
|
%{
|
|
|
|
"releases" => [
|
|
|
|
%{"version" => version}
|
|
|
|
| _
|
|
|
|
]
|
|
|
|
} ->
|
|
|
|
requirement =
|
|
|
|
version
|
|
|
|
|> Version.parse!()
|
|
|
|
|> case do
|
|
|
|
%Version{major: 0, minor: minor} ->
|
|
|
|
"~> 0.#{minor}"
|
|
|
|
|
|
|
|
%Version{major: major} ->
|
|
|
|
"~> #{major}.0"
|
|
|
|
end
|
|
|
|
|
|
|
|
Igniter.Deps.add_dependency(igniter, install, requirement)
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
Igniter.add_issue(igniter, "No published versions of #{install} on hex")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
confirmation_message =
|
|
|
|
unless options[:dry_run] do
|
|
|
|
"Dependencies changes must go into effect before individual installers can be run. Proceed with changes?"
|
|
|
|
end
|
|
|
|
|
|
|
|
dependency_add_result =
|
2024-06-01 14:59:36 +12:00
|
|
|
Igniter.do_or_dry_run(igniter, argv,
|
2024-06-01 14:09:38 +12:00
|
|
|
title: "Fetching Dependency",
|
|
|
|
quiet_on_no_changes?: true,
|
|
|
|
confirmation_message: confirmation_message
|
|
|
|
)
|
|
|
|
|
|
|
|
if dependency_add_result == :issues do
|
|
|
|
raise "Exiting due to issues found while fetching dependency"
|
|
|
|
end
|
|
|
|
|
|
|
|
if dependency_add_result == :dry_run_with_changes do
|
|
|
|
install_dep_now? =
|
|
|
|
Mix.shell().yes?("""
|
|
|
|
Cannot run any associated installers for the requested packages without
|
|
|
|
commiting changes and fetching dependencies.
|
|
|
|
|
|
|
|
Would you like to do so now? The remaining steps will be displayed as a dry run.
|
|
|
|
""")
|
|
|
|
|
|
|
|
if install_dep_now? do
|
2024-06-01 14:59:36 +12:00
|
|
|
Igniter.do_or_dry_run(igniter, (argv ++ ["--yes"]) -- ["--dry-run"],
|
2024-06-01 14:09:38 +12:00
|
|
|
title: "Fetching Dependency",
|
|
|
|
quiet_on_no_changes?: true
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
Mix.shell().info("running mix deps.get")
|
|
|
|
|
|
|
|
case Mix.shell().cmd("mix deps.get") do
|
|
|
|
0 ->
|
|
|
|
Mix.Task.reenable("compile")
|
|
|
|
Mix.Task.run("compile")
|
|
|
|
|
|
|
|
exit_code ->
|
|
|
|
Mix.shell().info("""
|
|
|
|
mix deps.get returned exited with code: `#{exit_code}`
|
|
|
|
""")
|
|
|
|
end
|
|
|
|
|
|
|
|
all_tasks =
|
2024-06-01 14:59:36 +12:00
|
|
|
Enum.filter(Mix.Task.load_all(), &implements_behaviour?(&1, Igniter.Mix.Task))
|
2024-06-01 14:09:38 +12:00
|
|
|
|
|
|
|
install_list
|
|
|
|
|> Enum.flat_map(fn install ->
|
|
|
|
all_tasks
|
|
|
|
|> Enum.find(fn task ->
|
|
|
|
Mix.Task.task_name(task) == "#{install}.install"
|
|
|
|
end)
|
|
|
|
|> List.wrap()
|
|
|
|
end)
|
|
|
|
|> Enum.reduce(Igniter.new(), fn task, igniter ->
|
|
|
|
Igniter.compose_task(igniter, task, argv)
|
|
|
|
end)
|
2024-06-01 14:59:36 +12:00
|
|
|
|> Igniter.do_or_dry_run(argv)
|
2024-06-01 14:09:38 +12:00
|
|
|
|
|
|
|
:ok
|
|
|
|
end
|
2024-06-01 14:59:36 +12:00
|
|
|
|
|
|
|
defp implements_behaviour?(module, behaviour) do
|
|
|
|
:attributes
|
|
|
|
|> module.module_info()
|
|
|
|
|> Enum.any?(fn
|
|
|
|
{:behaviour, ^behaviour} ->
|
|
|
|
true
|
|
|
|
|
|
|
|
# optimizations, probably extremely minor but this is in a tight loop in some places
|
|
|
|
{:behaviour, [^behaviour | _]} ->
|
|
|
|
true
|
|
|
|
|
|
|
|
{:behaviour, [_, ^behaviour | _]} ->
|
|
|
|
true
|
|
|
|
|
|
|
|
{:behaviour, [_, _, ^behaviour | _]} ->
|
|
|
|
true
|
|
|
|
|
|
|
|
# never seen a module with three behaviours in real life, let alone four.
|
|
|
|
{:behaviour, behaviours} when is_list(behaviours) ->
|
|
|
|
module in behaviours
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end)
|
|
|
|
rescue
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
# sobelow_skip ["DOS.StringToAtom"]
|
|
|
|
defp install_list(install) do
|
|
|
|
install
|
|
|
|
|> String.split(",")
|
|
|
|
|> Enum.map(&String.to_atom/1)
|
|
|
|
end
|
2024-06-01 15:50:29 +12:00
|
|
|
|
|
|
|
defp local_dep?(install) do
|
|
|
|
config = Mix.Project.config()[:deps][install]
|
|
|
|
Keyword.keyword?(config) && config[:path]
|
|
|
|
end
|
2024-06-01 14:09:38 +12:00
|
|
|
end
|