rscons/lib/rscons/util.rb

267 lines
7.5 KiB
Ruby

module Rscons
# A collection of stand-alone utility methods.
module Util
class << self
# Return whether the given path is an absolute filesystem path.
#
# @param path [String] the path to examine.
#
# @return [Boolean] Whether the given path is an absolute filesystem path.
def absolute_path?(path)
if RUBY_PLATFORM =~ /mingw|msys/
path =~ %r{^(?:\w:)?[\\/]}
else
path.start_with?("/")
end
end
# Colorize a builder run message.
#
# @param message [String]
# Builder run message.
#
# @return [Array]
# Colorized message with color codes for {Ansi} module.
def colorize_markup(message)
if message =~ /^(.*?)(<[^>]+>)(.*)$/
prefix, code, suffix = $1, $2, $3
case code
when "<target>"
code = :magenta
when "<source>"
code = :cyan
when "<reset>"
code = :reset
end
[prefix, code, *colorize_markup(suffix)].delete_if {|v| v == ""}
else
[message]
end
end
# Return a string representation of a command.
#
# @param command [Array<String>]
# The command.
#
# @return [String]
# The string representation of the command.
def command_to_s(command)
command.map { |c| c[" "] ? "'#{c.gsub("'", "'\\\\''")}'" : c }.join(" ")
end
# Determine the number of threads to use by default.
#
# @return [Integer]
# The number of threads to use by default.
def determine_n_threads
# If the user specifies the number of threads in the environment, then
# respect that.
if ENV["RSCONS_NTHREADS"] =~ /^(\d+)$/
return $1.to_i
end
# Otherwise try to figure out how many threads are available on the
# host hardware.
begin
case RbConfig::CONFIG["host_os"]
when /linux/
return File.read("/proc/cpuinfo").scan(/^processor\s*:/).size
when /mswin|mingw|msys/
if `wmic cpu get NumberOfLogicalProcessors -value` =~ /NumberOfLogicalProcessors=(\d+)/
return $1.to_i
end
when /darwin/
if `sysctl -n hw.ncpu` =~ /(\d+)/
return $1.to_i
end
end
rescue
end
# If we can't figure it out, default to 1.
1
end
# Return a string showing the path specified, or if more than one, then
# the first path with a "(+D)" afterward, where D is the number of
# remaining paths.
#
# @param paths [Array<String>]
# Paths.
#
# @return [String]
# Condensed path readout.
def short_format_paths(paths)
if paths.size == 1
paths.first
else
"#{paths.first} (+#{paths.size - 1})"
end
end
# Look for an executable.
#
# @return [String, nil]
# Executable path, if found.
def find_executable(name)
if name["/"] or name["\\"]
if File.file?(name) and File.executable?(name)
return name
end
else
path_entries = ENV["PATH"].split(File::PATH_SEPARATOR)
path_entries.find do |path_entry|
if path = test_path_for_executable(path_entry, name)
return path
end
end
end
end
# Format an elapsed time in human-readable format.
#
# @return [String]
# Elapsed time in human-readable format.
def format_elapsed_time(elapsed)
hours = (elapsed / (60 * 60)).to_i
elapsed -= hours * 60 * 60
minutes = (elapsed / 60).to_i
elapsed -= minutes * 60
seconds = elapsed.ceil
result = ""
if hours > 0
result += "#{hours}h "
end
if hours > 0 || minutes > 0
result += "#{minutes}m "
end
result += "#{seconds}s"
result
end
# Make a relative path corresponding to a possibly absolute one.
#
# @param path [String]
# Input path that is possibly absolute.
#
# @return [String]
# Relative path.
def make_relative_path(path)
if absolute_path?(path)
if path =~ %r{^(\w):(.*)$}
"_#{$1}#{$2}"
else
"_#{path}"
end
else
path
end
end
# Parse dependencies from a Makefile or ldc2 dependency file.
#
# This method is used internally by Rscons builders.
#
# @param mf_fname [String]
# File name of the Makefile to read.
#
# @return [Array<String>]
# Paths of dependency files.
def parse_dependency_file(mf_fname)
deps = []
buildup = ''
File.read(mf_fname).each_line do |line|
if line =~ /^(.*)\\\s*$/
buildup += ' ' + $1
else
buildup += ' ' + line
if buildup =~ /^[^:]+\(\S+\)\s*:.*?:[^:]+\((\S+)\)/
# ldc2-style dependency line
deps << $1
elsif buildup =~ /^.*: (.*)$/
# Makefile-style dependency line
mf_deps = $1
deps += mf_deps.split(' ').map(&:strip)
end
buildup = ''
end
end
deps
end
# Wait for any of a number of threads to complete.
#
# @param threads [Array<Thread>]
# Threads to wait for.
#
# @return [Thread]
# The Thread that completed.
def wait_for_thread(*threads)
if threads.empty?
raise "No threads to wait for"
end
queue = Queue.new
threads.each do |thread|
# Create a wait thread for each thread we're waiting for.
Thread.new do
begin
thread.join
ensure
queue.push(thread)
end
end
end
# Wait for any thread to complete.
queue.pop
end
# Command that can be run to execute this instance of rscons from the
# current working directory.
def command_to_execute_me
command = Pathname.new(File.expand_path($0)).relative_path_from(Dir.pwd).to_s
command = "./#{command}" unless command["/"]
command
end
private
# Check if a directory contains a certain executable.
#
# @param path_entry [String]
# Directory to look in.
# @param executable [String]
# Executable to look for.
#
# @return [String, nil]
# Executable found.
def test_path_for_executable(path_entry, executable)
is_executable = lambda do |path|
File.file?(path) and File.executable?(path)
end
if RbConfig::CONFIG["host_os"] =~ /mswin|windows|mingw|msys/i
if File.directory?(path_entry)
executable = executable.downcase
dir_entries = Dir.entries(path_entry)
dir_entries.find do |entry|
path = "#{path_entry}/#{entry}"
entry = entry.downcase
if ((entry == executable) or
(entry == "#{executable}.exe") or
(entry == "#{executable}.com") or
(entry == "#{executable}.bat")) and is_executable[path]
return path
end
end
end
else
path = "#{path_entry}/#{executable}"
return path if is_executable[path]
end
end
end
end
end