409 lines
11 KiB
Ruby
409 lines
11 KiB
Ruby
require "set"
|
|
|
|
module Rscons
|
|
|
|
# Functionality for an instance of the rscons application invocation.
|
|
class Application
|
|
|
|
# @return [Array<Hash>]
|
|
# Active variants.
|
|
attr_reader :active_variants
|
|
|
|
# @return [String]
|
|
# Top-level build directory.
|
|
attr_accessor :build_dir
|
|
|
|
# @return [Boolean]
|
|
# Whether to output ANSI color escape sequences.
|
|
attr_accessor :do_ansi_color
|
|
|
|
# @return [Integer]
|
|
# The number of threads to use when scheduling subprocesses.
|
|
attr_accessor :n_threads
|
|
|
|
# @return [Script]
|
|
# Build script.
|
|
attr_reader :script
|
|
|
|
# @return [Boolean]
|
|
# Whether to configure silently.
|
|
attr_accessor :silent_configure
|
|
|
|
# @return [Boolean]
|
|
# Whether to run verbosely.
|
|
attr_accessor :verbose
|
|
|
|
# Create Application instance.
|
|
def _initialize
|
|
@silent_configure = true
|
|
@build_dir = ENV["RSCONS_BUILD_DIR"] || "build"
|
|
ENV.delete("RSCONS_BUILD_DIR")
|
|
@n_threads = Util.determine_n_threads
|
|
@variant_groups = []
|
|
end
|
|
|
|
# Return if Rscons is in the task execution phase.
|
|
#
|
|
# @api private
|
|
#
|
|
# @return [Boolean]
|
|
# If Rscons is in the task execution phase.
|
|
def task_execution_phase?
|
|
@task_execution_phase
|
|
end
|
|
|
|
# Run the application.
|
|
#
|
|
# Execute user-specified tasks.
|
|
#
|
|
# @api private
|
|
#
|
|
# @param rsconscript [String]
|
|
# Build script file name.
|
|
# @param tasks_and_params [Hash<String => Hash<String => String>>]
|
|
# List of task(s) to execute.
|
|
# @param show_tasks [Boolean]
|
|
# Flag to show tasks and exit.
|
|
# @param all_tasks [Boolean]
|
|
# Flag to show all tasks (not just those with a description).
|
|
# @param enabled_variants [String]
|
|
# User-specified variants list.
|
|
#
|
|
# @return [Integer]
|
|
# Process exit code (0 on success).
|
|
def run(rsconscript, tasks_and_params, show_tasks, all_tasks, enabled_variants)
|
|
Cache.instance["failed_commands"] = []
|
|
@tasks_and_params = tasks_and_params
|
|
@enabled_variants = enabled_variants
|
|
if enabled_variants == "" && !tasks_and_params.include?("configure")
|
|
if cache_enabled_variants = Cache.instance["configuration_data"]["enabled_variants"]
|
|
@enabled_variants = cache_enabled_variants
|
|
end
|
|
end
|
|
@script = Script.new
|
|
if should_load_script
|
|
@script.load(rsconscript)
|
|
enable_variants
|
|
end
|
|
if show_tasks
|
|
show_script_tasks(all_tasks)
|
|
return 0
|
|
end
|
|
apply_task_params(false)
|
|
@task_execution_phase = true
|
|
if tasks_and_params.empty?
|
|
check_process_environments
|
|
if Task.tasks["default"]
|
|
Task["default"].check_execute
|
|
end
|
|
else
|
|
tasks_and_params.each do |task_name, params|
|
|
Task[task_name].check_execute
|
|
end
|
|
end
|
|
0
|
|
end
|
|
|
|
# Apply user-specified variant enables and complain if they don't make
|
|
# sense given the build script variant configuration.
|
|
def enable_variants
|
|
unless @_variants_enabled
|
|
if @enabled_variants != ""
|
|
exact = !(@enabled_variants =~ /^(\+|-)/)
|
|
enabled_variants = @enabled_variants.split(",")
|
|
specified_variants = {}
|
|
enabled_variants.each do |enable_variant|
|
|
enable_variant =~ /^(\+|-)?(.*)$/
|
|
enable_disable, variant_name = $1, $2
|
|
specified_variants[variant_name] = enable_disable != "-"
|
|
end
|
|
each_variant do |variant|
|
|
if specified_variants.include?(variant[:name])
|
|
variant[:enabled] = specified_variants[variant[:name]]
|
|
elsif exact
|
|
variant[:enabled] = false
|
|
end
|
|
end
|
|
end
|
|
@_variants_enabled = true
|
|
end
|
|
check_enabled_variants
|
|
end
|
|
|
|
# Show the last failures.
|
|
#
|
|
# @return [void]
|
|
def show_failure
|
|
failed_commands = Cache.instance["failed_commands"]
|
|
failed_commands.each_with_index do |command, i|
|
|
Ansi.write($stdout, :red, "Failed command (#{i + 1}/#{failed_commands.size}):", :reset, "\n")
|
|
$stdout.puts Util.command_to_s(command)
|
|
end
|
|
end
|
|
|
|
# Remove all generated files.
|
|
#
|
|
# @api private
|
|
#
|
|
# @return [void]
|
|
def clean
|
|
cache = Cache.instance
|
|
# remove all built files
|
|
cache.targets(false).each do |target|
|
|
cache.remove_target(target)
|
|
FileUtils.rm_f(target)
|
|
end
|
|
# remove all created directories if they are empty
|
|
cache.directories(false).sort {|a, b| b.size <=> a.size}.each do |directory|
|
|
cache.remove_directory(directory)
|
|
next unless File.directory?(directory)
|
|
if (Dir.entries(directory) - ['.', '..']).empty?
|
|
Dir.rmdir(directory) rescue nil
|
|
end
|
|
end
|
|
cache.write
|
|
end
|
|
|
|
# Remove the build directory and clear the cache.
|
|
#
|
|
# @api private
|
|
#
|
|
# @return [void]
|
|
def distclean
|
|
cache = Cache.instance
|
|
clean
|
|
cache.clear
|
|
FileUtils.rm_rf(@build_dir)
|
|
end
|
|
|
|
# Check if the project needs to be configured.
|
|
#
|
|
# @api private
|
|
#
|
|
# @return [void]
|
|
def check_configure
|
|
apply_task_params(true)
|
|
enable_variants
|
|
unless Cache.instance["configuration_data"]["configured"]
|
|
if @script.autoconf
|
|
configure
|
|
end
|
|
end
|
|
end
|
|
|
|
# Check if environments need to be processed.
|
|
#
|
|
# @api private
|
|
#
|
|
# @return [void]
|
|
def check_process_environments
|
|
unless @_processed_environments
|
|
Environment[].each do |env|
|
|
env.process
|
|
end
|
|
@_processed_environments = true
|
|
end
|
|
end
|
|
|
|
# Configure the project.
|
|
#
|
|
# @api private
|
|
#
|
|
# @return [void]
|
|
def configure
|
|
unless @_configured
|
|
@_configured = true
|
|
co = ConfigureOp.new(@script)
|
|
begin
|
|
@script.configure(co)
|
|
rescue RsconsError => e
|
|
co.close(false)
|
|
raise e
|
|
end
|
|
Cache.instance["configuration_data"]["enabled_variants"] = @enabled_variants
|
|
co.close(true)
|
|
end
|
|
end
|
|
|
|
# Remove installed files.
|
|
#
|
|
# @api private
|
|
#
|
|
# @return [Integer]
|
|
# Exit code.
|
|
#
|
|
# @return [void]
|
|
def uninstall
|
|
cache = Cache.instance
|
|
cache.targets(true).each do |target|
|
|
cache.remove_target(target)
|
|
next unless File.exist?(target)
|
|
puts "Removing #{target}" if verbose
|
|
FileUtils.rm_f(target)
|
|
end
|
|
# remove all created directories if they are empty
|
|
cache.directories(true).sort {|a, b| b.size <=> a.size}.each do |directory|
|
|
cache.remove_directory(directory)
|
|
next unless File.directory?(directory)
|
|
if (Dir.entries(directory) - ['.', '..']).empty?
|
|
puts "Removing #{directory}" if verbose
|
|
Dir.rmdir(directory) rescue nil
|
|
end
|
|
end
|
|
cache.write
|
|
end
|
|
|
|
# Define a variant, or within a with_variants block, query if it is
|
|
# active.
|
|
#
|
|
# @param name [String]
|
|
# Variant name.
|
|
def variant(name, options = {})
|
|
if @active_variants
|
|
!!@active_variants.find {|variant| variant[:name] == name}
|
|
else
|
|
if @variant_groups.empty?
|
|
variant_group
|
|
end
|
|
options = options.dup
|
|
options[:name] = name
|
|
options[:enabled] = options.fetch(:default, true)
|
|
options[:key] = options.fetch(:key, name)
|
|
@variant_groups.last[:variants] << options
|
|
end
|
|
end
|
|
|
|
# Check if a variant is enabled.
|
|
#
|
|
# This can be used, for example, in a configuration block to omit or
|
|
# include configuration checks based on which variants have been
|
|
# configured.
|
|
#
|
|
# @param variant_name [String]
|
|
# Variant name.
|
|
#
|
|
# @return [Boolean]
|
|
# Whether the requested variant is enabled.
|
|
def variant_enabled?(variant_name)
|
|
each_variant do |variant|
|
|
if variant[:name] == variant_name
|
|
return variant[:enabled]
|
|
end
|
|
end
|
|
false
|
|
end
|
|
|
|
# Create a variant group.
|
|
def variant_group(*args, &block)
|
|
if args.first.is_a?(String)
|
|
name = args.slice!(0)
|
|
end
|
|
options = args.first || {}
|
|
@variant_groups << options.merge(name: name, variants: [])
|
|
if block
|
|
block[]
|
|
end
|
|
end
|
|
|
|
# Iterate through enabled variants.
|
|
#
|
|
# The given block is called for each combination of enabled variants
|
|
# across the defined variant groups.
|
|
def with_variants(&block)
|
|
if @active_variants
|
|
raise "with_variants cannot be called within another with_variants block"
|
|
end
|
|
if @variant_groups.empty?
|
|
raise "with_variants cannot be called with no variants defined"
|
|
end
|
|
iter_vgs = lambda do |iter_variants|
|
|
if iter_variants.size == @variant_groups.size
|
|
@active_variants = iter_variants.compact
|
|
block[]
|
|
@active_variants = nil
|
|
else
|
|
@variant_groups[iter_variants.size][:variants].each do |variant|
|
|
if variant[:enabled]
|
|
iter_vgs[iter_variants + [variant]]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
iter_vgs[[]]
|
|
end
|
|
|
|
private
|
|
|
|
def check_enabled_variants
|
|
@variant_groups.each do |variant_group|
|
|
enabled_count = variant_group[:variants].count do |variant|
|
|
variant[:enabled]
|
|
end
|
|
if enabled_count == 0
|
|
message = "No variants enabled for variant group"
|
|
if variant_group[:name]
|
|
message += " #{variant_group[:name].inspect}"
|
|
end
|
|
raise RsconsError.new(message)
|
|
end
|
|
end
|
|
end
|
|
|
|
def each_variant
|
|
@variant_groups.each do |variant_group|
|
|
variant_group[:variants].each do |variant|
|
|
yield variant
|
|
end
|
|
end
|
|
end
|
|
|
|
def show_script_tasks(all_tasks)
|
|
puts "Tasks:"
|
|
Task[].sort.each do |task_name, task|
|
|
if task.description || all_tasks
|
|
puts %[ #{sprintf("%-27s", task_name)} #{task.description}]
|
|
task.params.each do |param_name, param|
|
|
arg_text = "--#{param_name}"
|
|
if param.takes_arg
|
|
arg_text += "=#{param_name.upcase}"
|
|
end
|
|
puts %[ #{sprintf("%-25s", "#{arg_text}")} #{param.description}]
|
|
end
|
|
end
|
|
end
|
|
|
|
unless @variant_groups.empty?
|
|
@variant_groups.each do |variant_group|
|
|
puts "\nVariant group#{variant_group[:name] ? " '#{variant_group[:name]}'" : ""}:"
|
|
variant_group[:variants].each do |variant|
|
|
puts " #{variant[:name]}#{variant[:enabled] ? " (enabled)" : ""}"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def apply_task_params(only_configure)
|
|
@tasks_and_params.each do |task_name, task_params|
|
|
unless only_configure && task_name != "configure"
|
|
task_params.each do |param_name, param_value|
|
|
if param = Task[task_name].params[param_name]
|
|
Task[task_name].set_param_value(param_name, param_value)
|
|
else
|
|
raise RsconsError.new("Unknown parameter #{param_name.inspect} for task #{task_name}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def should_load_script
|
|
return true if @tasks_and_params.empty?
|
|
return true if Cache.instance["configuration_data"]["configured"]
|
|
return false if (@tasks_and_params.keys - %w[distclean clean uninstall]).empty?
|
|
true
|
|
end
|
|
|
|
end
|
|
|
|
end
|