require 'rbbt/util/log' require 'stringio' require 'open3' require 'rbbt/util/misc/indiferent_hash' module CMD TOOLS = IndiferentHash.setup({}) def self.tool(tool, claim = nil, test = nil, cmd = nil, &block) TOOLS[tool] = [claim, test, block, cmd] end def self.conda(tool, env = nil, channel = 'bioconda') if env CMD.cmd("bash -l -c '(conda activate #{env} && conda install #{tool} -c #{channel})'") else CMD.cmd("bash -l -c 'conda install #{tool} -c #{channel}'") end end def self.get_tool(tool) return tool.to_s unless TOOLS[tool] @@init_cmd_tool ||= IndiferentHash.setup({}) if !@@init_cmd_tool[tool] claim, test, block, cmd = TOOLS[tool] begin if test CMD.cmd(test + " ") else CMD.cmd("#{tool} --help") end rescue if claim claim.produce else block.call end end version_txt = "" version = nil ["--version", "-version", "--help", ""].each do |f| begin version_txt += CMD.cmd("#{tool} #{f} 2>&1", :nofail => true).read ppp version_txt version = Misc.scan_version_text(version_txt, tool) break if version rescue Log.exception $! end end @@init_cmd_tool[tool] = version || true return cmd if cmd end tool.to_s end def self.versions return {} unless defined? @@init_cmd_tool @@init_cmd_tool.select{|k,v| v =~ /\d+\./ } end def self.gzip_pipe(file) Open.gzip?(file) ? "<(gunzip -c '#{file}')" : "'#{file}'" end def self.bash(cmd) %Q(bash < Log::DEBUG in_content = options.delete(:in) stderr = options.delete(:stderr) pipe = options.delete(:pipe) post = options.delete(:post) log = options.delete(:log) no_fail = options.delete(:no_fail) no_fail = options.delete(:nofail) if no_fail.nil? no_wait = options.delete(:no_wait) xvfb = options.delete(:xvfb) bar = options.delete(:progress_bar) dont_close_in = options.delete(:dont_close_in) log = true if log.nil? if cmd.nil? && ! Symbol === tool cmd = tool else tool = get_tool(tool) if cmd.nil? cmd = tool else cmd = tool + ' ' + cmd end end case xvfb when TrueClass cmd = "xvfb-run --server-args='-screen 0 1024x768x24' --auto-servernum #{cmd}" when String cmd = "xvfb-run --server-args='#{xvfb}' --auto-servernum --server-num=1 #{cmd}" when String end if stderr == true stderr = Log::HIGH end cmd_options = process_cmd_options options if cmd =~ /'\{opt\}'/ cmd.sub!('\'{opt}\'', cmd_options) else cmd << " " << cmd_options end in_content = StringIO.new in_content if String === in_content sin, sout, serr, wait_thr = begin Open3.popen3(ENV, cmd) rescue Log.warn $!.message raise ProcessFailed, cmd unless no_fail return end pid = wait_thr.pid Log.debug{"CMD: [#{pid}] #{cmd}" if log} if in_content.respond_to?(:read) in_thread = Thread.new(Thread.current) do |parent| begin begin while c = in_content.readpartial(Misc::BLOCK_SIZE) sin << c end rescue EOFError end sin.close unless sin.closed? unless dont_close_in in_content.close unless in_content.closed? in_content.join if in_content.respond_to? :join end rescue Log.error "Error in CMD [#{pid}] #{cmd}: #{$!.message}" raise $! end end else in_thread = nil sin.close end pids = [pid] if pipe ConcurrentStream.setup sout, :pids => pids, :autojoin => no_wait, :no_fail => no_fail err_thread = Thread.new do while line = serr.gets bar.process(line) if bar sout.log = line Log.log "STDERR [#{pid}]: " + line, stderr end if Integer === stderr and log serr.close end sout.threads = [in_thread, err_thread, wait_thr].compact sout else err = "" err_thread = Thread.new do while not serr.eof? err << serr.gets if Integer === stderr end serr.close end ConcurrentStream.setup sout, :pids => pids, :threads => [in_thread, err_thread].compact, :autojoin => no_wait, :no_fail => no_fail out = StringIO.new sout.read sout.close unless sout.closed? status = wait_thr.value if not status.success? and not no_fail raise ProcessFailed.new "Command [#{pid}] #{cmd} failed with error status #{status.exitstatus}.\n#{err}" else Log.log err, stderr if Integer === stderr and log end out end end def self.cmd_pid(*args) all_args = *args bar = all_args.last[:progress_bar] if Hash === all_args.last all_args << {} unless Hash === all_args.last level = all_args.last[:log] || 0 level = 0 if TrueClass === level level = 10 if FalseClass === level level = level.to_i all_args.last[:log] = true all_args.last[:pipe] = true io = cmd(*all_args) pid = io.pids.first line = "" if bar while c = io.getc STDERR << c if Log.severity <= level line << c if bar if c == "\n" bar.process(line) if bar if pid Log.logn "STDOUT [#{pid}]: ", level else Log.logn "STDOUT: ", level end line = "" if bar end end begin io.join bar.remove if bar rescue bar.remove(true) if bar raise $! end nil end def self.cmd_log(*args) cmd_pid(*args) nil end end