267 lines
7.5 KiB
Ruby
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 =~ /\s/ ? "'#{c}'" : 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
|