lib/os.rb



require 'rbconfig'
require 'yaml'

# a set of friendly files for determining your Ruby runtime

# treats cygwin as linux

# also treats IronRuby on mono as...linux

class OS
  attr_reader :config

  def self.config
    @config ||= RbConfig::CONFIG
  end

  # true if on windows [and/or jruby]

  # false if on linux or cygwin on windows


  def self.windows?
    @windows ||= begin
      if RUBY_PLATFORM =~ /cygwin/ # i386-cygwin

        false
      elsif ENV['OS'] == 'Windows_NT'
        true
      else
        false
      end
    end

  end

  # true for linux, os x, cygwin

  def self.posix?
    @posix ||=
    begin
      if OS.windows?
        begin
          begin
            # what if we're on interix...

            # untested, of course

            Process.wait fork{}
            true
          rescue NotImplementedError, NoMethodError
            false
          end
        end
      else
        # assume non windows is posix

        true
      end
    end

  end

  # true for linux, false for windows, os x, cygwin

  def self.linux?
    if (host_os =~ /linux/)
      true
    else
      false
    end
  end

  def self.freebsd?
    if (host_os =~ /freebsd/)
      true
    else
      false
    end
  end

  def self.iron_ruby?
   @iron_ruby ||= begin
     if defined?(RUBY_ENGINE) && (RUBY_ENGINE == 'ironruby')
       true
     else
       false
     end
   end
  end

  def self.bits
    @bits ||= begin
      if host_cpu =~ /_64$/ || RUBY_PLATFORM =~ /x86_64/
        64
      elsif RUBY_PLATFORM == 'java' && ENV_JAVA['sun.arch.data.model'] # "32" or "64":http://www.ruby-forum.com/topic/202173#880613

        ENV_JAVA['sun.arch.data.model'].to_i
      elsif host_cpu == 'i386'
        32
      elsif host_os =~ /32$/ # mingw32, mswin32

        32
      else # cygwin only...I think

        if 1.size == 8
          64
        else
          32
        end
      end
    end
  end


  def self.java?
    @java ||= begin
      if RUBY_PLATFORM =~ /java/
        true
      else
        false
      end
    end
  end

  def self.ruby_bin
    @ruby_exe ||= begin
      File::join(config['bindir'], config['ruby_install_name']) + config['EXEEXT']
    end
  end

  def self.mac?
    @mac = begin
      if host_os =~ /darwin/
        true
      else
        false
      end
    end
  end

  def self.osx?
    mac?
  end

  def self.x?
    mac?
  end


  # amount of memory the current process "is using", in RAM

  # (doesn't include any swap memory that it may be using, just that in actual RAM)

  # raises 'unknown' on jruby currently

  def self.rss_bytes
    # attempt to do this in a jruby friendly way

    if OS::Underlying.windows?
      # MRI, Java, IronRuby, Cygwin

      if OS.java?
        # no win32ole on 1.5.x, so leave here for compatibility...maybe for awhile :P

        require 'java'
        mem_bean = java.lang.management.ManagementFactory.getMemoryMXBean
        mem_bean.heap_memory_usage.used + mem_bean.non_heap_memory_usage.used
      else
        wmi = nil
        begin
          require 'win32ole'
          wmi = WIN32OLE.connect("winmgmts://")
        rescue LoadError, NoMethodError => e # NoMethod for IronRuby currently [sigh]

          raise 'rss unknown for this platform ' + e.to_s
        end
        processes = wmi.ExecQuery("select * from win32_process where ProcessId = #{Process.pid}")
        memory_used = nil
        # only allow for one process...

        for process in processes
          raise "multiple processes same pid?" if memory_used
          memory_used = process.WorkingSetSize.to_i
        end
        memory_used
      end
    elsif OS.posix? # linux [though I've heard it works in OS X]

      `ps -o rss= -p #{Process.pid}`.to_i * 1024 # in kiloBytes

    else
      raise 'unknown rss for this platform'
    end
  end

  class Underlying

    def self.bsd?
      OS.osx?
    end

    def self.windows?
      ENV['OS'] == 'Windows_NT'
    end

    def self.linux?
      OS.host_os =~ /linux/ ? true : false
    end

    def self.docker?
      system('grep -q docker /proc/self/cgroup') if OS.linux?
    end

  end

  def self.cygwin?
    @cygwin = begin
      if RUBY_PLATFORM =~ /-cygwin/
        true
      else
        false
      end
    end
  end

  def self.dev_null # File::NULL in 1.9.3+

    @dev_null ||= begin
      if OS.windows?
        "NUL"
      else
        "/dev/null"
      end
    end
  end

  # provides easy way to see the relevant config entries

  def self.report
    relevant_keys = [
      'arch',
      'host',
      'host_cpu',
      'host_os',
      'host_vendor',
      'target',
      'target_cpu',
      'target_os',
      'target_vendor',
    ]
    RbConfig::CONFIG.reject {|key, val| !relevant_keys.include? key }.merge({'RUBY_PLATFORM' => RUBY_PLATFORM}).to_yaml
  end

  def self.cpu_count
    @cpu_count ||=
    case RUBY_PLATFORM
    when /darwin9/
      `hwprefs cpu_count`.to_i
    when /darwin10/
      (hwprefs_available? ? `hwprefs thread_count` : `sysctl -n hw.ncpu`).to_i
    when /linux/
      `cat /proc/cpuinfo | grep processor | wc -l`.to_i
    when /freebsd/
      `sysctl -n hw.ncpu`.to_i
    else
      if RbConfig::CONFIG['host_os'] =~ /darwin/
         (hwprefs_available? ? `hwprefs thread_count` : `sysctl -n hw.ncpu`).to_i
      elsif self.windows?
        # ENV counts hyper threaded...not good.

      	      # out = ENV['NUMBER_OF_PROCESSORS'].to_i

        require 'win32ole'
        wmi = WIN32OLE.connect("winmgmts://")
        cpu = wmi.ExecQuery("select NumberOfCores from Win32_Processor") # don't count hyper-threaded in this

        cpu.to_enum.first.NumberOfCores
      else
        raise 'unknown platform processor_count'
      end
    end
  end

  def self.open_file_command
    if OS.doze? || OS.cygwin?
      "start"
    elsif OS.mac?
      "open"
    else
      # linux...what about cygwin?

      "xdg-open"
    end

  end

  def self.app_config_path(name)
    if OS.doze?
      if ENV['LOCALAPPDATA']
        return File.join(ENV['LOCALAPPDATA'], name)
      end

      File.join ENV['USERPROFILE'], 'Local Settings', 'Application Data', name
    elsif OS.mac?
      File.join ENV['HOME'], 'Library', 'Application Support', name
    else
      if ENV['XDG_CONFIG_HOME']
        return File.join(ENV['XDG_CONFIG_HOME'], name)
      end

      File.join ENV['HOME'], '.config', name
    end
  end

  def self.parse_os_release
    if OS.linux? && File.exist?('/etc/os-release')
      output = {}

      File.read('/etc/os-release').each_line do |line|
        parsed_line = line.chomp.tr('"', '').split('=')
        next if parsed_line.empty?
        output[parsed_line[0].to_sym] = parsed_line[1]
      end
      output
    else
      raise "File /etc/os-release doesn't exists or not Linux"
    end
  end

  class << self
    alias :doze? :windows? # a joke name but I use it and like it :P

    alias :jruby? :java?

    # delegators for relevant config values

    %w(host host_cpu host_os).each do |method_name|
      define_method(method_name) { config[method_name] }
    end

  end

  private

  def self.hwprefs_available?
    `which hwprefs` != ''
  end

end