module Albacore::CrossPlatformCmd

module for normalising slashes across operating systems and running commands

Constants

KILL_TIMEOUT

Attributes

pid[R]

Public Class Methods

new() click to toggle source
# File lib/albacore/cross_platform_cmd.rb, line 45
def initialize
  pid = Process.pid
  at_exit { stop if Process.pid == pid }
end

Public Instance Methods

chdir(wd, &block) click to toggle source
# File lib/albacore/cross_platform_cmd.rb, line 219
def chdir wd, &block
  return block.call if wd.nil?
  Dir.chdir wd do
    debug { "pushd #{wd} [cross_platform_cmd #chdir]" }
    res = block.call
    debug { "popd #{wd} [cross_platform_cmd #chdir]" }
    return res
  end
end
make_command() click to toggle source

create a new command string

# File lib/albacore/cross_platform_cmd.rb, line 182
def make_command
  ::Albacore::Paths.make_command @executable, @parameters
end
normalise_slashes(path) click to toggle source
# File lib/albacore/cross_platform_cmd.rb, line 177
def normalise_slashes path
  ::Albacore::Paths.normalise_slashes path
end
sh(*cmd, &block) click to toggle source

run in shell

# File lib/albacore/cross_platform_cmd.rb, line 140
def sh *cmd, &block
  raise ArgumentError, "cmd is nil" if cmd.nil? # don't allow nothing to be passed
  opts = Map.options((Hash === cmd.last) ? cmd.pop : {}) # same arg parsing as rake

  cmd = cmd.join(' ') # shell needs a single string
  block = handler_with_message cmd unless block_given?

  chdir opts.get(:work_dir) do

    trace { "#sh( ...,  options: #{opts.to_s}) [cross_platform_cmd #sh]" }
    puts cmd unless opts.getopt :silent, false # log cmd verbatim

    lines = ''
    handle_not_found block do
      IO.popen(cmd, 'r') do |io|
        io.each do |line|
          lines << line
          puts line if opts.getopt(:output, true) or not opts.getopt(:silent, false)
        end
      end
    end

    return block.call($? == 0 && lines, $?, lines)
  end
end
shie(*cmd, &block) click to toggle source

shell ignore exit code returns:

[ok, status]
where status:
  #exitstatus : Int
  #pid      : Int
# File lib/albacore/cross_platform_cmd.rb, line 172
def shie *cmd, &block
  block = lambda { |ok, status, output| ok } unless block_given?
  sh *cmd, &block
end
stop() click to toggle source
# File lib/albacore/cross_platform_cmd.rb, line 115
def stop
  if pid
    begin
      Process.kill('TERM', pid)

      begin
        Timeout.timeout(KILL_TIMEOUT) { Process.wait(pid) }
      rescue Timeout::Error
        Process.kill('KILL', pid)
        Process.wait(pid)
      end
    rescue Errno::ESRCH, Errno::ECHILD
      # Zed's dead, baby
    end

    @out_thread.kill
    @pid = nil
  end
end
system(*cmd, &block) click to toggle source

run executable

system(cmd, [args array], Hash(opts), block|ok,status|)

ok => false if bad exit code, or the output otherwise

options are passed as the last argument

options:

work_dir: a file path (default '.')
silent:   whether to supress all output or not (default false)
output:   whether to supress the command's output (default false)
out:      output pipe
err:      error pipe
clr_command:
          whether to include 'mono' in front of the things to run
          if the command is a clr command
# File lib/albacore/cross_platform_cmd.rb, line 67
def system *cmd, &block
  raise ArgumentError, "cmd is nil" if cmd.nil? # don't allow nothing to be passed
  opts = Map.options((Hash === cmd.last) ? cmd.pop : {}). # same arg parsing as rake
    apply({
      :silent => false,
      :output => true,
      :work_dir => FileUtils.pwd,
      :out => Albacore.application.output,
      :err => Albacore.application.output_err,
      :clr_command => false })

  exe, pars, printable, block = prepare_command cmd, (opts.get('clr_command')), &block

  # TODO: figure out how to interleave output and error streams
  out, _, inmem = opts.get(:out), opts.get(:err), StringIO.new

  trace { "system( exe=#{exe}, pars=[#{pars.join(', ')}], options=#{opts.to_s}), in directory: #{opts.getopt(:work_dir, '<<current>>')} [cross_platform_cmd #system]" }

  puts printable unless opts.get :silent, false # log cmd verbatim

  handle_not_found block do
    # create a pipe for the process to work with
    read, write = IO.pipe
    eread, ewrite = IO.pipe

    # this thread chews through the output
    @out_thread = Thread.new {
      while !read.eof?
        data = read.readpartial(1024)
        out.write data
        inmem.write data # to give the block at the end
      end
    }

    debug 'execute the new process, letting it write to the write FD (file descriptor)'
    @pid = Process.spawn(*[exe, *pars],
      { :out => write,
        #:err => ewrite,
        :chdir => opts.get(:work_dir) })

    debug 'waiting for process completion'
    _, status = Process.wait2 @pid

    ret_str = inmem.string.encode 'utf-8', invalid: :replace, undef: :replace, replace: ''
    return block.call(status.success? && ret_str, status, ret_str)
  end
end
which(executable) click to toggle source
# File lib/albacore/cross_platform_cmd.rb, line 186
def which executable
  raise ArgumentError, "executable to #which is nil" unless executable

  dir = File.dirname executable
  file = File.basename executable

  cmd = ::Rake::Win32.windows? ? 'where' : 'which'
  parameters = []
  pattern = if dir == '.' then file
  elsif ::Rake::Win32.windows? then "#{dir}:#{file}"
  else executable
  end
  parameters << Paths.normalise_slashes("\"#{pattern}\"")
  parameters << '2> nul' if ::Rake::Win32.windows?
  cmd, parameters = Paths.normalise cmd, parameters
  cmd = "#{cmd} #{parameters.join(' ')}"

  trace { "#{cmd} [cross_platform_cmd #which]" }

  res = IO.popen(cmd) do |io|
    io.read.chomp
  end

  unless $? == 0
    nil
  else
    res
  end
rescue Errno::ENOENT => e
  trace "which/where returned #{$?}: #{e} [cross_platform_cmd #which]"
  nil
end

Private Instance Methods

format_failure(cmd, status) click to toggle source
# File lib/albacore/cross_platform_cmd.rb, line 267
  def format_failure cmd, status
    if knowns.has_key? status.exitstatus
      %{Command failed with status (#{status.exitstatus}) - #{knowns[status.exitstatus]}:
#{cmd}}
    else
      %{Command failed with status (#{status.exitstatus}):
#{cmd}}
    end
  end
handle_not_found(rescue_block) { || ... } click to toggle source

handles the errors from not finding the executable on the system

# File lib/albacore/cross_platform_cmd.rb, line 246
def handle_not_found rescue_block
  yield
rescue Errno::ENOENT => e
  rescue_block.call(nil, PseudoStatus.new(127), e.to_s)
rescue IOError => e # rescue for JRuby
  rescue_block.call(nil, PseudoStatus.new(127), e.to_s)
end
handler_with_message(printable) click to toggle source
# File lib/albacore/cross_platform_cmd.rb, line 241
def handler_with_message printable
  lambda { |ok, status, output| ok or raise_failure(printable, status, output) }
end
knowns() click to toggle source
# File lib/albacore/cross_platform_cmd.rb, line 255
def knowns
  { 127 => 'number 127 in particular means that the operating system could not find the executable' }
end
mono_command() click to toggle source

shuffle the executable to be a parameter to mono, if not on windows.

# File lib/albacore/cross_platform_cmd.rb, line 279
def mono_command
  unless ::Rake::Win32.windows?
    trace 'detected running on mono -- unshifting exe file for mono'
    executable = @executable
    @executable = "mono"
    @parameters.unshift executable
  end
end
prepare_command(cmd, clr_command = false, &block) click to toggle source
# File lib/albacore/cross_platform_cmd.rb, line 231
def prepare_command cmd, clr_command = false, &block
  cmd = cmd.unshift 'mono' if clr_command && ! ::Albacore.windows?
  pars = cmd[1..-1].flatten
  raise ArgumentError, "arguments 1..-1 must be an array" unless pars.is_a? Array
  exe, pars = ::Albacore::Paths.normalise cmd[0], pars
  printable = %Q{#{exe} #{pars.join(' ')}}
  handler = block_given? ? block : handler_with_message(printable)
  [exe, pars, printable, handler]
end
raise_failure(cmd, status, output) click to toggle source
# File lib/albacore/cross_platform_cmd.rb, line 259
def raise_failure cmd, status, output
  if status.exitstatus == 127
    raise CommandNotFoundError.new(format_failure(cmd, status), cmd)
  else
    raise CommandFailedError.new(format_failure(cmd, status), cmd, output)
  end
end