lib/miga/cli/action/init.rb
# frozen_string_literal: true
require 'miga/cli/action'
require 'shellwords'
class MiGA::Cli::Action::Init < MiGA::Cli::Action
require 'miga/cli/action/init/daemon_helper'
require 'miga/cli/action/init/files_helper'
include MiGA::Cli::Action::Init::DaemonHelper
include MiGA::Cli::Action::Init::FilesHelper
def parse_cli
cli.interactive = true
cli.defaults = {
mytaxa: nil,
rdp: nil,
reads: nil,
optional: nil,
config: File.join(ENV['MIGA_HOME'], '.miga_modules'),
ask: false,
auto: false,
dtype: :bash
}
cli.parse do |opt|
opt.on(
'-c', '--config PATH',
'Path to the Bash configuration file',
"By default: #{cli[:config]}"
) { |v| cli[:config] = v }
opt.on(
'--[no-]mytaxa',
'Should I try setting up MyTaxa and its dependencies?',
'By default: interactive (true if --auto)'
) { |v| cli[:mytaxa] = v }
opt.on(
'--[no-]rdp',
'Should I try setting up the RDP classifier?',
'By default: interactive (true if --auto)'
) { |v| cli[:rdp] = v }
opt.on(
'--[no-]read-processing',
'Should I try setting up read processing software?',
'By default: interactive (true if --auto)'
) { |v| cli[:reads] = v }
opt.on(
'--[no-]optional',
'Should I try setting up the optional software?',
'Automatically sets answers for mytaxa, rdp, and reads'
) { |v| cli[:optional] = v }
opt.on(
'--daemon-type STRING',
'Type of daemon launcher, one of: bash, ssh, qsub, msub, slurm',
"By default: interactive (#{cli[:dtype]} if --auto)"
) { |v| cli[:dtype] = v.to_sym }
%w[R ruby python].each do |bin|
opt.on(
"--path-to-#{bin} STRING",
"Use this path to #{bin} instead of testing for executables"
) { |v| cli[:"path_to_#{bin}"] = v }
end
opt.on(
'--ask-all',
'Ask for the location of all software',
'By default, only the locations missing in PATH are requested'
) { |v| cli[:ask] = v }
end
end
def perform
cli.puts <<~BANNER
===[ Welcome to MiGA, the Microbial Genome Atlas ]===
I'm the initialization script, and I'll sniff around your computer to
make sure you have all the requirements for MiGA data processing.
BANNER
list_requirements
rc_fh = open_rc_file
check_configuration_script(rc_fh)
paths = check_software_requirements(rc_fh)
check_additional_files(paths)
check_libraries(paths)
configure_daemon
close_rc_file(rc_fh)
cli.puts 'Configuration complete. MiGA is ready to work!'
cli.puts ''
end
def empty_action
end
def run_cmd(cli, cmd, opts = {})
opts = { return: :output, source: cli[:config] }.merge(opts)
MiGA::MiGA.run_cmd(cmd, opts)
end
def run_r_cmd(cli, paths, cmd, opts = {})
run_cmd(
cli,
"echo #{cmd.shellescape} | #{paths['R'].shellescape} --vanilla -q",
{ err2out: true, stdout: '/dev/null' }.merge(opts)
)
end
def list_requirements
if cli.ask_user(
'Would you like to see all the requirements before starting?',
'no', %w(yes no)
) == 'yes'
cli.puts ''
req_path = File.join(MiGA.root_path, 'utils', 'requirements.txt')
File.open(req_path, 'r') do |fh|
fh.each_line { |ln| cli.puts ln }
end
cli.puts ''
end
end
private
def check_software_requirements(rc_fh)
cli.puts 'Looking for requirements:'
opt_groups = {
mytaxa: 'MyTaxa',
rdp: 'RDP classifier',
reads: 'read processing'
}
opt_groups.each do |k, v|
ask_for_optional(k, v)
rc_fh.puts "export MIGA_#{k.to_s.upcase}='#{cli[k] ? 'yes' : 'no'}'"
end
paths = {}
rc_fh.puts 'MIGA_PATH=""'
req_path = File.expand_path('utils/requirements.txt', MiGA.root_path)
File.open(req_path, 'r') do |fh|
fh.each_line do |ln|
r = define_software(ln) or next
cli.print "Testing #{r[0]}#{" (#{r[3]})" if r[3]}... "
if cli[:"path_to_#{r[1]}"]
paths[r[1]] = cli[:"path_to_#{r[1]}"]
cli.puts "user-provided: #{paths[r[1]]}"
else
path = find_software(r[1])
paths[r[1]] = File.expand_path(r[1], path).shellescape
end
end
end
rc_fh.puts 'export PATH="$MIGA_PATH$PATH"'
cli.puts ''
paths
end
def define_software(ln)
r = ln.chomp.split(/\t+/)
return if %w[Software --------].include?(r[0])
%i[mytaxa rdp reads].each { |i| return if r[0] =~ /\(#{i}\)$/ && !cli[i] }
r
end
def ask_for_optional(symbol, name)
cli[symbol] = cli[:optional] if !cli[:optional].nil? && cli[symbol].nil?
return cli[symbol] unless cli[symbol].nil?
cli[symbol] =
cli.ask_user(
"Should I include #{name} modules?",
'yes', %w(yes no)
) == 'yes'
end
def find_software(exec)
path = nil
loop do
d_path = File.dirname(run_cmd(cli, ['which', exec], raise: false))
if cli[:ask] || d_path == '.'
path = cli.ask_user('Where can I find it?', d_path, nil, true)
else
path = d_path
cli.puts path
end
if File.executable?(File.expand_path(exec, path))
if d_path != path
rc_fh.puts "MIGA_PATH=\"#{path}:$MIGA_PATH\" # #{exec}"
end
break
end
cli.print "I cannot find #{exec}. "
end
path
end
def check_libraries(paths)
req_libraries = {
r: %w[ape cluster vegan],
ruby: %w[sqlite3 daemons json],
python: %w[numpy sqlite3 pyhmmer pyrodigal]
}
req_libraries.each do |language, libraries|
cli.puts "Looking for #{language.to_s.capitalize} libraries:"
libraries.each do |lib|
check_and_install_library(paths, language, lib)
end
cli.puts ''
end
end
def check_and_install_library(paths, language, library)
cli.print "Testing #{library}... "
if test_library(cli, paths, language, library)
cli.puts 'yes'
else
cli.puts 'no, installing'
out = install_library(cli, paths, language, library)
unless test_library(cli, paths, language, library)
cli.puts out
raise "Cannot install #{language.to_s.capitalize} library: #{library}"
end
end
end
def test_library(cli, paths, language, pkg)
opts = { raise: false, return: :status, stderr: '/dev/null' }
status =
case language
when :r
run_r_cmd(cli, paths, "library('#{pkg}')", opts)
when :ruby
x = [paths['ruby'], '-r', pkg, '-e', '']
run_cmd(cli, x, opts)
when :python
x = [paths['python3'], '-c', "import #{pkg}"]
run_cmd(cli, x, opts)
else
raise "Unrecognized language: #{language}"
end
status.success?
end
def install_library(cli, paths, language, pkg)
case language
when :r
r_cmd = "install.packages('#{pkg}', repos='http://cran.rstudio.com/')"
run_r_cmd(cli, paths, r_cmd)
when :ruby
# This hackey mess is meant to ensure the test and installation are done
# on the configuration Ruby, not on the Ruby currently executing the
# init action
x = [
paths['ruby'], '-r', 'rubygems', '-r', 'rubygems/gem_runner',
'-e', "Gem::GemRunner.new.run %w(install --user #{pkg})"
]
run_cmd(cli, x, err2out: true)
when :python
x = [paths['python3'], '-m', 'pip', 'install', '--user', pkg]
run_cmd(cli, x, err2out: true)
else
raise "Unrecognized language: #{language}"
end
end
end