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 [
|
2024-06-04 05:13:49 +12:00
|
|
|
strict: [
|
2024-06-01 14:09:38 +12:00
|
|
|
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
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
def install(install, argv) do
|
2024-06-06 02:12:07 +12:00
|
|
|
install_list =
|
|
|
|
if is_binary(install) do
|
|
|
|
String.split(install, ",")
|
|
|
|
else
|
|
|
|
Enum.map(List.wrap(install), &to_string/1)
|
|
|
|
end
|
2024-06-01 14:09:38 +12:00
|
|
|
|
|
|
|
Application.ensure_all_started(:req)
|
|
|
|
|
2024-06-06 02:12:07 +12:00
|
|
|
{options, _errors, _unprocessed_argv} =
|
2024-06-04 05:13:49 +12:00
|
|
|
OptionParser.parse(argv, @option_schema)
|
2024-06-01 14:09:38 +12:00
|
|
|
|
|
|
|
igniter = Igniter.new()
|
|
|
|
|
2024-06-04 15:14:36 +12:00
|
|
|
{igniter, install_list} =
|
|
|
|
install_list
|
|
|
|
|> Enum.reduce({igniter, []}, fn install, {igniter, install_list} ->
|
|
|
|
case determine_dep_type_and_version(install) do
|
|
|
|
{install, requirement} ->
|
|
|
|
install = String.to_atom(install)
|
|
|
|
|
|
|
|
if local_dep?(install) do
|
|
|
|
Mix.shell().info(
|
|
|
|
"Not looking up dependency for #{install}, because a local dependency is detected"
|
|
|
|
)
|
|
|
|
|
|
|
|
{igniter, [install | install_list]}
|
|
|
|
else
|
|
|
|
{Igniter.Deps.add_dependency(igniter, install, requirement, "--yes" in argv),
|
|
|
|
[install | install_list]}
|
|
|
|
end
|
|
|
|
|
|
|
|
:error ->
|
|
|
|
{Igniter.add_issue(
|
|
|
|
igniter,
|
|
|
|
"Could not determine source for requested package #{install}"
|
|
|
|
), install_list}
|
2024-06-01 14:09:38 +12:00
|
|
|
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
|
|
|
|
2024-06-04 15:14:36 +12:00
|
|
|
igniter =
|
|
|
|
Igniter.new()
|
|
|
|
|> Igniter.assign(%{manually_installed: install_list})
|
|
|
|
|
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)
|
2024-06-04 15:14:36 +12:00
|
|
|
|> Enum.reduce(igniter, fn task, igniter ->
|
2024-06-01 14:09:38 +12:00
|
|
|
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
|
|
|
|
|
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-04 15:14:36 +12:00
|
|
|
|
|
|
|
defp determine_dep_type_and_version(requirement) do
|
|
|
|
case String.split(requirement, "@", trim: true) do
|
|
|
|
[package] ->
|
|
|
|
if Regex.match?(~r/^[a-z][a-z0-9_]*$/, package) do
|
|
|
|
case Req.get!("https://hex.pm/api/packages/#{package}").body do
|
|
|
|
%{
|
|
|
|
"releases" => [
|
|
|
|
%{"version" => version}
|
|
|
|
| _
|
|
|
|
]
|
|
|
|
} ->
|
|
|
|
{package, Igniter.Version.version_string_to_general_requirement!(version)}
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
:error
|
|
|
|
end
|
|
|
|
else
|
|
|
|
:error
|
|
|
|
end
|
|
|
|
|
|
|
|
[package, version] ->
|
|
|
|
case version do
|
|
|
|
"git:" <> requirement ->
|
|
|
|
if String.contains?(requirement, "@") do
|
|
|
|
case String.split(requirement, ["@"], trim: true) do
|
|
|
|
[url, ref] ->
|
|
|
|
[git: url, ref: ref]
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
:error
|
|
|
|
end
|
|
|
|
else
|
|
|
|
[git: requirement]
|
|
|
|
end
|
|
|
|
|
|
|
|
"github:" <> requirement ->
|
|
|
|
if String.contains?(requirement, "@") do
|
|
|
|
case String.split(requirement, ["/", "@"], trim: true) do
|
|
|
|
[org, project, ref] ->
|
|
|
|
[github: "#{org}/#{project}", ref: ref]
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
:error
|
|
|
|
end
|
|
|
|
else
|
|
|
|
[github: requirement]
|
|
|
|
end
|
|
|
|
|
|
|
|
"local:" <> requirement ->
|
|
|
|
[path: requirement]
|
|
|
|
|
|
|
|
"~>" <> version ->
|
|
|
|
"~> #{version}"
|
|
|
|
|
|
|
|
"==" <> version ->
|
|
|
|
"== #{version}"
|
|
|
|
|
|
|
|
">=" <> version ->
|
|
|
|
">= #{version}"
|
|
|
|
|
|
|
|
version ->
|
|
|
|
case Version.parse(version) do
|
|
|
|
{:ok, version} ->
|
|
|
|
"== #{version}"
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
case Igniter.Version.version_string_to_general_requirement(version) do
|
|
|
|
{:ok, requirement} ->
|
|
|
|
requirement
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
:error
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|> case do
|
|
|
|
:error ->
|
|
|
|
:error
|
|
|
|
|
|
|
|
requirement ->
|
|
|
|
{package, requirement}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2024-06-01 14:09:38 +12:00
|
|
|
end
|