Rework builder interface to only use #run method - close #91
The builder's #run method will be called repeatedly until it returns true or false. The Builder#wait_for method can be used to cause a builder to wait for a Thread, Command, or another Builder.
This commit is contained in:
parent
8426a54a57
commit
b882f8de99
@ -5,7 +5,7 @@ class MySource < Rscons::Builder
|
||||
#define THE_VALUE 5678
|
||||
EOF
|
||||
end
|
||||
@target
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -5,7 +5,7 @@ class MySource < Rscons::Builder
|
||||
#define THE_VALUE 678
|
||||
EOF
|
||||
end
|
||||
@target
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -5,7 +5,7 @@ class MySource < Rscons::Builder
|
||||
#define THE_VALUE #{@env.expand_varref("${the_value}")}
|
||||
EOF
|
||||
end
|
||||
@target
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
|
8
build_tests/custom_builder/error_run_return_value.rb
Normal file
8
build_tests/custom_builder/error_run_return_value.rb
Normal file
@ -0,0 +1,8 @@
|
||||
build do
|
||||
Environment.new do |env|
|
||||
env.add_builder(:MyBuilder) do |options|
|
||||
"hi"
|
||||
end
|
||||
env.MyBuilder("foo")
|
||||
end
|
||||
end
|
8
build_tests/custom_builder/error_wait_for.rb
Normal file
8
build_tests/custom_builder/error_wait_for.rb
Normal file
@ -0,0 +1,8 @@
|
||||
build do
|
||||
Environment.new do |env|
|
||||
env.add_builder(:MyBuilder) do |options|
|
||||
wait_for(1)
|
||||
end
|
||||
env.MyBuilder("foo")
|
||||
end
|
||||
end
|
@ -8,7 +8,7 @@ class CHGen < Rscons::Builder
|
||||
File.open(h_fname, "w") {|fh| fh.puts "extern int THE_VALUE;"}
|
||||
@cache.register_build([c_fname, h_fname], "", @sources, @env)
|
||||
end
|
||||
@target
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
|
21
build_tests/custom_builder/wait_for_thread.rb
Normal file
21
build_tests/custom_builder/wait_for_thread.rb
Normal file
@ -0,0 +1,21 @@
|
||||
class MyBuilder < Rscons::Builder
|
||||
def run(options)
|
||||
if @thread
|
||||
true
|
||||
else
|
||||
@env.print_builder_run_message("#{name} #{target}", nil)
|
||||
@thread = Thread.new do
|
||||
sleep 2
|
||||
FileUtils.touch(@target)
|
||||
end
|
||||
wait_for(@thread)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
build do
|
||||
Environment.new do |env|
|
||||
env.add_builder(MyBuilder)
|
||||
env.MyBuilder("foo")
|
||||
end
|
||||
end
|
@ -10,7 +10,7 @@ build do
|
||||
end
|
||||
@cache.register_build(@target, :JsonToYaml, @sources, @env)
|
||||
end
|
||||
@target
|
||||
true
|
||||
end
|
||||
env.JsonToYaml('foo.yml', 'foo.json')
|
||||
end
|
||||
|
@ -1,6 +1,6 @@
|
||||
class TestBuilder < Rscons::Builder
|
||||
def run(options)
|
||||
target
|
||||
true
|
||||
end
|
||||
end
|
||||
build do
|
||||
|
@ -1,30 +1,30 @@
|
||||
class DebugBuilder < Rscons::Builder
|
||||
def run(options)
|
||||
command = %W[gcc -c -o #{@target} #{@sources.first}]
|
||||
if Rscons.vars["command_change"]
|
||||
command += %w[-Wall]
|
||||
end
|
||||
if Rscons.vars["new_dep"]
|
||||
@sources += ["extra"]
|
||||
end
|
||||
if Rscons.vars["strict_deps1"]
|
||||
@sources += ["extra"]
|
||||
strict_deps = true
|
||||
end
|
||||
if Rscons.vars["strict_deps2"]
|
||||
@sources = ["extra"] + @sources
|
||||
strict_deps = true
|
||||
end
|
||||
if @cache.up_to_date?(@target, command, @sources, @env, debug: true, strict_deps: strict_deps)
|
||||
@target
|
||||
if @command
|
||||
finalize_command
|
||||
else
|
||||
ThreadedCommand.new(command, short_description: "#{name} #{@target}")
|
||||
@command = %W[gcc -c -o #{@target} #{@sources.first}]
|
||||
if Rscons.vars["command_change"]
|
||||
@command += %w[-Wall]
|
||||
end
|
||||
if Rscons.vars["new_dep"]
|
||||
@sources += ["extra"]
|
||||
end
|
||||
if Rscons.vars["strict_deps1"]
|
||||
@sources += ["extra"]
|
||||
strict_deps = true
|
||||
end
|
||||
if Rscons.vars["strict_deps2"]
|
||||
@sources = ["extra"] + @sources
|
||||
strict_deps = true
|
||||
end
|
||||
if @cache.up_to_date?(@target, @command, @sources, @env, debug: true, strict_deps: strict_deps)
|
||||
true
|
||||
else
|
||||
register_command("#{name} #{target}", @command)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def finalize(options)
|
||||
standard_finalize(options)
|
||||
end
|
||||
end
|
||||
|
||||
build do
|
||||
|
@ -9,7 +9,7 @@ class TestBuilder < Rscons::Builder
|
||||
@env.print_builder_run_message(msg, msg)
|
||||
@cache.register_build(@target, command, @sources, @env)
|
||||
end
|
||||
@target
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -5,7 +5,7 @@ build do
|
||||
puts "Checker #{@sources.first}" if @env.echo != :off
|
||||
@cache.register_build(@target, :Checker, @sources, @env)
|
||||
end
|
||||
@target
|
||||
true
|
||||
end
|
||||
env.Program("simple.exe", "simple.c")
|
||||
env.Checker(:checker, "simple.exe")
|
||||
|
@ -1,12 +1,11 @@
|
||||
class ThreadedTestBuilder < Rscons::Builder
|
||||
def run(options)
|
||||
command = ["ruby", "-e", %[sleep 1]]
|
||||
Rscons::ThreadedCommand.new(
|
||||
command,
|
||||
short_description: "ThreadedTestBuilder #{@target}")
|
||||
end
|
||||
def finalize(options)
|
||||
true
|
||||
if @command
|
||||
true
|
||||
else
|
||||
@command = ["ruby", "-e", %[sleep 1]]
|
||||
register_command("ThreadedTestBuilder #{@target}", @command)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -14,7 +13,7 @@ class NonThreadedTestBuilder < Rscons::Builder
|
||||
def run(options)
|
||||
puts "NonThreadedTestBuilder #{@target}"
|
||||
sleep 1
|
||||
@target
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
class TestBuilder < Rscons::Builder
|
||||
def run(options)
|
||||
if @target == "two"
|
||||
return false unless File.exists?("one")
|
||||
if @command
|
||||
true
|
||||
else
|
||||
if @target == "two"
|
||||
return false unless File.exists?("one")
|
||||
end
|
||||
wait_time = @env.expand_varref("${wait_time}", @vars)
|
||||
@command = ["ruby", "-e", "require 'fileutils'; sleep #{wait_time}; FileUtils.touch('#{@target}');"]
|
||||
register_command("TestBuilder", @command)
|
||||
end
|
||||
wait_time = @env.expand_varref("${wait_time}", @vars)
|
||||
command = ["ruby", "-e", "require 'fileutils'; sleep #{wait_time}; FileUtils.touch('#{@target}');"]
|
||||
standard_threaded_build("TestBuilder", @target, command, [], @env, @cache)
|
||||
end
|
||||
|
||||
def finalize(options)
|
||||
standard_finalize(options)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
class Fail < Rscons::Builder
|
||||
def run(options)
|
||||
wait_time = @env.expand_varref("${wait_time}", @vars)
|
||||
ruby_command = %[sleep #{wait_time}; exit 2]
|
||||
command = %W[ruby -e #{ruby_command}]
|
||||
standard_threaded_build("Fail #{@target}", @target, command, [], @env, @cache)
|
||||
end
|
||||
def finalize(options)
|
||||
standard_finalize(options)
|
||||
if @command
|
||||
finalize_command
|
||||
else
|
||||
wait_time = @env.expand_varref("${wait_time}", @vars)
|
||||
ruby_command = %[sleep #{wait_time}; exit 2]
|
||||
@command = %W[ruby -e #{ruby_command}]
|
||||
register_command("Fail #{@target}", @command)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
class StrictBuilder < Rscons::Builder
|
||||
def run(options)
|
||||
command = %W[gcc -o #{@target}] + @sources.sort
|
||||
if @cache.up_to_date?(@target, command, @sources, @env, strict_deps: true)
|
||||
@target
|
||||
if @command
|
||||
finalize_command
|
||||
else
|
||||
ThreadedCommand.new(command, short_description: "#{name} #{@target}")
|
||||
@command = %W[gcc -o #{@target}] + @sources.sort
|
||||
if @cache.up_to_date?(@target, @command, @sources, @env, strict_deps: true)
|
||||
true
|
||||
else
|
||||
register_command("#{name} #{@target}", @command)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def finalize(options)
|
||||
standard_finalize(options)
|
||||
end
|
||||
end
|
||||
|
||||
build do
|
||||
|
@ -1,7 +1,7 @@
|
||||
class MyBuilder < Rscons::Builder
|
||||
def run(options)
|
||||
@env.print_builder_run_message("MyBuilder #{@target}", "MyBuilder #{@target} command")
|
||||
@target
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -6,9 +6,9 @@ require_relative "rscons/builder_builder"
|
||||
require_relative "rscons/builder_set"
|
||||
require_relative "rscons/cache"
|
||||
require_relative "rscons/configure_op"
|
||||
require_relative "rscons/command"
|
||||
require_relative "rscons/environment"
|
||||
require_relative "rscons/script"
|
||||
require_relative "rscons/threaded_command"
|
||||
require_relative "rscons/util"
|
||||
require_relative "rscons/varset"
|
||||
require_relative "rscons/version"
|
||||
|
@ -6,6 +6,7 @@ module Rscons
|
||||
|
||||
# Class to hold an object that knows how to build a certain type of file.
|
||||
class Builder
|
||||
|
||||
class << self
|
||||
# Return the name of the builder.
|
||||
#
|
||||
@ -108,104 +109,97 @@ module Rscons
|
||||
# @param options [Hash]
|
||||
# Run options.
|
||||
#
|
||||
# @return [ThreadedCommand,String,false]
|
||||
# Name of the target file on success or false on failure.
|
||||
# Since 1.10.0, this method may return an instance of {ThreadedCommand}.
|
||||
# In that case, the build operation has not actually been completed yet
|
||||
# but the command to do so will be executed by Rscons in a separate
|
||||
# thread. This allows for build parallelization. If a {ThreadedCommand}
|
||||
# object is returned, the {#finalize} method will be called after the
|
||||
# command has completed. The {#finalize} method should then be used to
|
||||
# record cache info, if needed, and to return the true result of the
|
||||
# build operation. The builder can store information to be passed in to
|
||||
# the {#finalize} method by populating the :builder_info field of the
|
||||
# {ThreadedCommand} object returned here.
|
||||
# @return [Object]
|
||||
# If the build operation fails, this method should return +false+.
|
||||
# If the build operation succeeds, this method should return +true+.
|
||||
# If the build operation is not yet complete and is waiting on other
|
||||
# operations, this method should return the return value from the
|
||||
# {#wait_for} method.
|
||||
def run(options)
|
||||
raise "This method must be overridden in a subclass"
|
||||
end
|
||||
|
||||
# Finalize a build operation.
|
||||
# Create a {Command} object to execute the build command in a thread.
|
||||
#
|
||||
# This method is called after the {#run} method if the {#run} method
|
||||
# returns a {ThreadedCommand} object.
|
||||
#
|
||||
# @since 1.10.0
|
||||
#
|
||||
# @param options [Hash]
|
||||
# Options.
|
||||
# @option options [String] :target
|
||||
# Target file name.
|
||||
# @option options [Array<String>] :sources
|
||||
# Source file name(s).
|
||||
# @option options [Cache] :cache
|
||||
# The Cache object.
|
||||
# @option options [Environment] :env
|
||||
# The Environment executing the builder.
|
||||
# @option options [Hash,VarSet] :vars
|
||||
# Extra construction variables.
|
||||
# @option options [true,false,nil] :command_status
|
||||
# If the {#run} method returns a {ThreadedCommand}, this field will
|
||||
# contain the return value from executing the command with
|
||||
# Kernel.system().
|
||||
# @option options [ThreadedCommand] :tc
|
||||
# The {ThreadedCommand} object that was returned by the #run method.
|
||||
#
|
||||
# @return [String,false]
|
||||
# Name of the target file on success or false on failure.
|
||||
def finalize(options)
|
||||
end
|
||||
|
||||
# Check if the cache is up to date for the target and if not create a
|
||||
# {ThreadedCommand} object to execute the build command in a thread.
|
||||
#
|
||||
# @since 1.10.0
|
||||
#
|
||||
# @param short_cmd_string [String]
|
||||
# @param short_description [String]
|
||||
# Short description of build action to be printed when env.echo ==
|
||||
# :short.
|
||||
# @param target [String] Name of the target file.
|
||||
# @param command [Array<String>]
|
||||
# The command to execute to build the target.
|
||||
# @param sources [Array<String>] Source file name(s).
|
||||
# @param env [Environment] The Environment executing the builder.
|
||||
# @param cache [Cache] The Cache object.
|
||||
# @param options [Hash] Options.
|
||||
# The command to execute.
|
||||
# @param options [Hash]
|
||||
# Options.
|
||||
# @option options [String] :stdout
|
||||
# File name to redirect standard output to.
|
||||
#
|
||||
# @return [String,ThreadedCommand]
|
||||
# The name of the target if it is already up to date or the
|
||||
# {ThreadedCommand} object created to update it.
|
||||
def standard_threaded_build(short_cmd_string, target, command, sources, env, cache, options = {})
|
||||
if cache.up_to_date?(target, command, sources, env)
|
||||
target
|
||||
# @return [Object]
|
||||
# Return value for {#run} method.
|
||||
def register_command(short_description, command, options = {})
|
||||
command_options = {}
|
||||
if options[:stdout]
|
||||
command_options[:system_options] = {out: options[:stdout]}
|
||||
end
|
||||
@env.print_builder_run_message(short_description, @command)
|
||||
wait_for(Command.new(command, self, command_options))
|
||||
end
|
||||
|
||||
# Check if the cache is up to date for the target and if not create a
|
||||
# {Command} object to execute the build command in a thread.
|
||||
#
|
||||
# @param short_description [String]
|
||||
# Short description of build action to be printed when env.echo ==
|
||||
# :short.
|
||||
# @param command [Array<String>]
|
||||
# The command to execute.
|
||||
# @param options [Hash]
|
||||
# Options.
|
||||
# @option options [Array<String>] :sources
|
||||
# Sources to override @sources.
|
||||
# @option options [String] :stdout
|
||||
# File name to redirect standard output to.
|
||||
#
|
||||
# @return [Object]
|
||||
# Return value for {#run} method.
|
||||
def standard_command(short_description, command, options = {})
|
||||
@command = command
|
||||
sources = options[:sources] || @sources
|
||||
if @cache.up_to_date?(@target, @command, sources, @env)
|
||||
true
|
||||
else
|
||||
unless Rscons.phony_target?(target)
|
||||
cache.mkdir_p(File.dirname(target))
|
||||
FileUtils.rm_f(target)
|
||||
unless Rscons.phony_target?(@target)
|
||||
@cache.mkdir_p(File.dirname(@target))
|
||||
FileUtils.rm_f(@target)
|
||||
end
|
||||
tc_options = {short_description: short_cmd_string}
|
||||
if options[:stdout]
|
||||
tc_options[:system_options] = {out: options[:stdout]}
|
||||
end
|
||||
ThreadedCommand.new(command, tc_options)
|
||||
register_command(short_description, @command, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Register build results from a {ThreadedCommand} with the cache.
|
||||
# Register build results from a {Command} with the cache.
|
||||
#
|
||||
# @since 1.10.0
|
||||
#
|
||||
# @param options [Hash]
|
||||
# Builder finalize options.
|
||||
# @option options [String] :stdout
|
||||
# File name to redirect standard output to.
|
||||
#
|
||||
# @return [String, nil]
|
||||
# The target name on success or nil on failure.
|
||||
def standard_finalize(options)
|
||||
if options[:command_status]
|
||||
@cache.register_build(@target, options[:tc].command, @sources, @env)
|
||||
@target
|
||||
end
|
||||
# @return [true]
|
||||
# Return value for {#run} method.
|
||||
def finalize_command(options = {})
|
||||
sources = options[:sources] || @sources
|
||||
@cache.register_build(@target, @command, sources, @env)
|
||||
true
|
||||
end
|
||||
|
||||
# A builder can indicate to Rscons that it needs to wait for a separate
|
||||
# operation to complete by using this method. The return value from this
|
||||
# method should be returned from the builder's #run method.
|
||||
#
|
||||
# @param things [Builder, Command, Thread, Array<Builder, Command, Thread>]
|
||||
# Builder(s) or Command(s) or Thread(s) that this builder needs to wait
|
||||
# for.
|
||||
def wait_for(things)
|
||||
Array(things)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -3,21 +3,15 @@ module Rscons
|
||||
# it is needed.
|
||||
class BuilderBuilder
|
||||
|
||||
# @return [String] Builder name.
|
||||
attr_reader :name
|
||||
|
||||
# Create a BuilderBuilder.
|
||||
#
|
||||
# @param name [String]
|
||||
# Builder name.
|
||||
# @param builder_class [Class]
|
||||
# The {Builder} class to be instantiated.
|
||||
# @param builder_args [Array]
|
||||
# Any extra arguments to be passed to the builder class.
|
||||
# @param builder_block [Proc, nil]
|
||||
# Optional block to be passed to the {Builder} class's #new method.
|
||||
def initialize(name, builder_class, *builder_args, &builder_block)
|
||||
@name = name
|
||||
def initialize(builder_class, *builder_args, &builder_block)
|
||||
@builder_class = builder_class
|
||||
@builder_args = builder_args
|
||||
@builder_block = builder_block
|
||||
@ -26,7 +20,7 @@ module Rscons
|
||||
# Act like a regular {Builder} class object but really instantiate the
|
||||
# requested {Builder} class, potentially with extra arguments and a block.
|
||||
def new(*args)
|
||||
@builder_class.new(*args, *@builder_args, &@builder_block)
|
||||
@builder_class.new(*@builder_args, *args, &@builder_block)
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -21,30 +21,23 @@ module Rscons
|
||||
|
||||
# Run the builder to produce a build target.
|
||||
def run(options)
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @sources
|
||||
cmd =
|
||||
case
|
||||
when @sources.first.end_with?(*@env.expand_varref("${LEXSUFFIX}"))
|
||||
"LEX"
|
||||
when @sources.first.end_with?(*@env.expand_varref("${YACCSUFFIX}"))
|
||||
"YACC"
|
||||
else
|
||||
raise "Unknown source file #{@sources.first.inspect} for CFile builder"
|
||||
end
|
||||
command = @env.build_command("${#{cmd}_CMD}", @vars)
|
||||
standard_threaded_build("#{cmd} #{@target}", @target, command, @sources, @env, @cache)
|
||||
end
|
||||
|
||||
# Finalize a build.
|
||||
#
|
||||
# @param options [Hash]
|
||||
# Finalize options.
|
||||
#
|
||||
# @return [String, nil]
|
||||
# The target name on success or nil on failure.
|
||||
def finalize(options)
|
||||
standard_finalize(options)
|
||||
if @command
|
||||
finalize_command
|
||||
else
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @sources
|
||||
cmd =
|
||||
case
|
||||
when @sources.first.end_with?(*@env.expand_varref("${LEXSUFFIX}"))
|
||||
"LEX"
|
||||
when @sources.first.end_with?(*@env.expand_varref("${YACCSUFFIX}"))
|
||||
"YACC"
|
||||
else
|
||||
raise "Unknown source file #{@sources.first.inspect} for CFile builder"
|
||||
end
|
||||
command = @env.build_command("${#{cmd}_CMD}", @vars)
|
||||
standard_command("#{cmd} #{@target}", command)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
module Rscons
|
||||
module Builders
|
||||
# Execute a command that will produce the given target based on the given
|
||||
# sources.
|
||||
# A builder to execute an arbitrary command that will produce the given
|
||||
# target based on the given sources.
|
||||
#
|
||||
# @since 1.8.0
|
||||
#
|
||||
@ -11,33 +11,20 @@ module Rscons
|
||||
class Command < Builder
|
||||
|
||||
# Run the builder to produce a build target.
|
||||
#
|
||||
# @param options [Hash] Builder run options.
|
||||
#
|
||||
# @return [String, ThreadedCommand]
|
||||
# Target file name if target is up to date or a {ThreadedCommand}
|
||||
# to execute to build the target.
|
||||
def run(options)
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @sources
|
||||
command = @env.build_command("${CMD}", @vars)
|
||||
cmd_desc = @vars["CMD_DESC"] || "Command"
|
||||
options = {}
|
||||
if @vars["CMD_STDOUT"]
|
||||
options[:stdout] = @env.expand_varref("${CMD_STDOUT}", @vars)
|
||||
if @command
|
||||
finalize_command
|
||||
else
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @sources
|
||||
command = @env.build_command("${CMD}", @vars)
|
||||
cmd_desc = @vars["CMD_DESC"] || "Command"
|
||||
options = {}
|
||||
if @vars["CMD_STDOUT"]
|
||||
options[:stdout] = @env.expand_varref("${CMD_STDOUT}", @vars)
|
||||
end
|
||||
standard_command("#{cmd_desc} #{@target}", command, options)
|
||||
end
|
||||
standard_threaded_build("#{cmd_desc} #{@target}", @target, command, @sources, @env, @cache, options)
|
||||
end
|
||||
|
||||
# Finalize a build.
|
||||
#
|
||||
# @param options [Hash]
|
||||
# Finalize options.
|
||||
#
|
||||
# @return [String, nil]
|
||||
# The target name on success or nil on failure.
|
||||
def finalize(options)
|
||||
standard_finalize(options)
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -6,14 +6,14 @@ module Rscons
|
||||
# Run the builder to produce a build target.
|
||||
def run(options)
|
||||
if File.directory?(@target)
|
||||
@target
|
||||
true
|
||||
elsif File.exists?(@target)
|
||||
Ansi.write($stderr, :red, "Error: `#{@target}' already exists and is not a directory", :reset, "\n")
|
||||
false
|
||||
else
|
||||
@env.print_builder_run_message("Directory #{@target}", nil)
|
||||
@cache.mkdir_p(@target)
|
||||
@target
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -11,30 +11,15 @@ module Rscons
|
||||
|
||||
# Run the builder to produce a build target.
|
||||
def run(options)
|
||||
@vars["_SOURCES"] = sources
|
||||
command = @env.build_command("${DISASM_CMD}", @vars)
|
||||
if @cache.up_to_date?(@target, command, @sources, @env)
|
||||
@target
|
||||
if @command
|
||||
finalize_command
|
||||
else
|
||||
@cache.mkdir_p(File.dirname(@target))
|
||||
ThreadedCommand.new(
|
||||
command,
|
||||
short_description: "Disassemble #{@target}",
|
||||
system_options: {out: @target})
|
||||
@vars["_SOURCES"] = @sources
|
||||
command = @env.build_command("${DISASM_CMD}", @vars)
|
||||
standard_command("Disassemble #{target}", command, stdout: @target)
|
||||
end
|
||||
end
|
||||
|
||||
# Finalize a build.
|
||||
#
|
||||
# @param options [Hash]
|
||||
# Finalize options.
|
||||
#
|
||||
# @return [String, nil]
|
||||
# The target name on success or nil on failure.
|
||||
def finalize(options)
|
||||
standard_finalize(options)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -42,7 +42,7 @@ module Rscons
|
||||
end
|
||||
@cache.register_build(dest, :Copy, [src], @env)
|
||||
end
|
||||
@target if (target_is_dir ? Dir.exists?(@target) : File.exists?(@target))
|
||||
(target_is_dir ? Dir.exists?(@target) : File.exists?(@target)) ? true : false
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -11,17 +11,6 @@ module Rscons
|
||||
)
|
||||
|
||||
# Create an instance of the Builder to build a target.
|
||||
#
|
||||
# @param options [Hash]
|
||||
# Options.
|
||||
# @option options [String] :target
|
||||
# Target file name.
|
||||
# @option options [Array<String>] :sources
|
||||
# Source file name(s).
|
||||
# @option options [Environment] :env
|
||||
# The Environment executing the builder.
|
||||
# @option options [Hash,VarSet] :vars
|
||||
# Extra construction variables.
|
||||
def initialize(options)
|
||||
super(options)
|
||||
suffixes = @env.expand_varref(["${OBJSUFFIX}", "${LIBSUFFIX}"], @vars)
|
||||
@ -30,29 +19,15 @@ module Rscons
|
||||
end
|
||||
|
||||
# Run the builder to produce a build target.
|
||||
#
|
||||
# @param options [Hash] Builder run options.
|
||||
#
|
||||
# @return [String,false]
|
||||
# Name of the target file on success or false on failure.
|
||||
def run(options)
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @objects
|
||||
command = @env.build_command("${ARCMD}", @vars)
|
||||
standard_threaded_build("AR #{@target}", @target, command, @objects, @env, @cache)
|
||||
end
|
||||
|
||||
# Finalize a build.
|
||||
#
|
||||
# @param options [Hash]
|
||||
# Finalize options.
|
||||
#
|
||||
# @return [String, nil]
|
||||
# The target name on success or nil on failure.
|
||||
def finalize(options)
|
||||
if options[:command_status]
|
||||
@cache.register_build(@target, options[:tc].command, @objects, @env)
|
||||
@target
|
||||
if @command
|
||||
finalize_command(sources: @objects)
|
||||
true
|
||||
else
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @objects
|
||||
command = @env.build_command("${ARCMD}", @vars)
|
||||
standard_command("AR #{@target}", command, sources: @objects)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -76,40 +76,26 @@ module Rscons
|
||||
end
|
||||
|
||||
# Run the builder to produce a build target.
|
||||
#
|
||||
# @param options [Hash] Builder run options.
|
||||
#
|
||||
# @return [String, ThreadedCommand]
|
||||
# Target file name if target is up to date or a {ThreadedCommand}
|
||||
# to execute to build the target.
|
||||
def run(options)
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @sources
|
||||
@vars["_DEPFILE"] = Rscons.set_suffix(target, env.expand_varref("${DEPFILESUFFIX}", vars))
|
||||
com_prefix = KNOWN_SUFFIXES.find do |compiler, suffix_var|
|
||||
@sources.first.end_with?(*@env.expand_varref("${#{suffix_var}}", @vars))
|
||||
end.tap do |v|
|
||||
v.nil? and raise "Error: unknown input file type: #{@sources.first.inspect}"
|
||||
end.first
|
||||
command = @env.build_command("${#{com_prefix}CMD}", @vars)
|
||||
@env.produces(@target, @vars["_DEPFILE"])
|
||||
standard_threaded_build("#{com_prefix} #{@target}", @target, command, @sources, @env, @cache)
|
||||
end
|
||||
|
||||
# Finalize the build operation.
|
||||
#
|
||||
# @param options [Hash] Builder finalize options.
|
||||
#
|
||||
# @return [String, nil]
|
||||
# Name of the target file on success or nil on failure.
|
||||
def finalize(options)
|
||||
if options[:command_status]
|
||||
if @command
|
||||
deps = @sources
|
||||
if File.exists?(@vars['_DEPFILE'])
|
||||
deps += Util.parse_makefile_deps(@vars['_DEPFILE'])
|
||||
if File.exists?(@vars["_DEPFILE"])
|
||||
deps += Util.parse_makefile_deps(@vars["_DEPFILE"])
|
||||
end
|
||||
@cache.register_build(@target, options[:tc].command, deps.uniq, @env)
|
||||
@target
|
||||
@cache.register_build(@target, @command, deps.uniq, @env)
|
||||
true
|
||||
else
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @sources
|
||||
@vars["_DEPFILE"] = Rscons.set_suffix(target, env.expand_varref("${DEPFILESUFFIX}", vars))
|
||||
com_prefix = KNOWN_SUFFIXES.find do |compiler, suffix_var|
|
||||
@sources.first.end_with?(*@env.expand_varref("${#{suffix_var}}", @vars))
|
||||
end.tap do |v|
|
||||
v.nil? and raise "Error: unknown input file type: #{@sources.first.inspect}"
|
||||
end.first
|
||||
command = @env.build_command("${#{com_prefix}CMD}", @vars)
|
||||
@env.produces(@target, @vars["_DEPFILE"])
|
||||
standard_command("#{com_prefix} #{@target}", command)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -12,44 +12,30 @@ module Rscons
|
||||
)
|
||||
|
||||
# Run the builder to produce a build target.
|
||||
#
|
||||
# @param options [Hash] Builder run options.
|
||||
#
|
||||
# @return [String, ThreadedCommand]
|
||||
# Target file name if target is up to date or a {ThreadedCommand}
|
||||
# to execute to build the target.
|
||||
def run(options)
|
||||
if @sources.find {|s| s.end_with?(*@env.expand_varref("${CXXSUFFIX}", @vars))}
|
||||
pp_cc = "${CXX}"
|
||||
depgen = "${CXXDEPGEN}"
|
||||
else
|
||||
pp_cc = "${CC}"
|
||||
depgen = "${CCDEPGEN}"
|
||||
end
|
||||
@vars["_PREPROCESS_CC"] = pp_cc
|
||||
@vars["_PREPROCESS_DEPGEN"] = depgen
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @sources
|
||||
@vars["_DEPFILE"] = Rscons.set_suffix(target, env.expand_varref("${DEPFILESUFFIX}", vars))
|
||||
command = @env.build_command("${CPP_CMD}", @vars)
|
||||
@env.produces(@target, @vars["_DEPFILE"])
|
||||
standard_threaded_build("#{name} #{@target}", @target, command, @sources, @env, @cache)
|
||||
end
|
||||
|
||||
# Finalize the build operation.
|
||||
#
|
||||
# @param options [Hash] Builder finalize options.
|
||||
#
|
||||
# @return [String, nil]
|
||||
# Name of the target file on success or nil on failure.
|
||||
def finalize(options)
|
||||
if options[:command_status]
|
||||
if @command
|
||||
deps = @sources
|
||||
if File.exists?(@vars['_DEPFILE'])
|
||||
deps += Util.parse_makefile_deps(@vars['_DEPFILE'])
|
||||
if File.exists?(@vars["_DEPFILE"])
|
||||
deps += Util.parse_makefile_deps(@vars["_DEPFILE"])
|
||||
end
|
||||
@cache.register_build(@target, options[:tc].command, deps.uniq, @env)
|
||||
@target
|
||||
@cache.register_build(@target, @command, deps.uniq, @env)
|
||||
true
|
||||
else
|
||||
if @sources.find {|s| s.end_with?(*@env.expand_varref("${CXXSUFFIX}", @vars))}
|
||||
pp_cc = "${CXX}"
|
||||
depgen = "${CXXDEPGEN}"
|
||||
else
|
||||
pp_cc = "${CC}"
|
||||
depgen = "${CCDEPGEN}"
|
||||
end
|
||||
@vars["_PREPROCESS_CC"] = pp_cc
|
||||
@vars["_PREPROCESS_DEPGEN"] = depgen
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @sources
|
||||
@vars["_DEPFILE"] = Rscons.set_suffix(target, env.expand_varref("${DEPFILESUFFIX}", vars))
|
||||
command = @env.build_command("${CPP_CMD}", @vars)
|
||||
@env.produces(@target, @vars["_DEPFILE"])
|
||||
standard_command("#{name} #{@target}", command)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -40,40 +40,26 @@ module Rscons
|
||||
end
|
||||
|
||||
# Run the builder to produce a build target.
|
||||
#
|
||||
# @param options [Hash] Builder run options.
|
||||
#
|
||||
# @return [String,false]
|
||||
# Name of the target file on success or false on failure.
|
||||
def run(options)
|
||||
ld = @env.expand_varref("${LD}", @vars)
|
||||
ld = if ld != ""
|
||||
ld
|
||||
elsif @sources.find {|s| s.end_with?(*@env.expand_varref("${DSUFFIX}", @vars))}
|
||||
"${DC}"
|
||||
elsif @sources.find {|s| s.end_with?(*@env.expand_varref("${CXXSUFFIX}", @vars))}
|
||||
"${CXX}"
|
||||
else
|
||||
"${CC}"
|
||||
end
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @objects
|
||||
@vars["LD"] = ld
|
||||
command = @env.build_command("${LDCMD}", @vars)
|
||||
standard_threaded_build("LD #{@target}", @target, command, @objects, @env, @cache)
|
||||
end
|
||||
|
||||
# Finalize a build.
|
||||
#
|
||||
# @param options [Hash]
|
||||
# Finalize options.
|
||||
#
|
||||
# @return [String, nil]
|
||||
# The target name on success or nil on failure.
|
||||
def finalize(options)
|
||||
if options[:command_status]
|
||||
@cache.register_build(@target, options[:tc].command, @objects, @env)
|
||||
@target
|
||||
if @command
|
||||
finalize_command(sources: @objects)
|
||||
true
|
||||
else
|
||||
ld = @env.expand_varref("${LD}", @vars)
|
||||
ld = if ld != ""
|
||||
ld
|
||||
elsif @sources.find {|s| s.end_with?(*@env.expand_varref("${DSUFFIX}", @vars))}
|
||||
"${DC}"
|
||||
elsif @sources.find {|s| s.end_with?(*@env.expand_varref("${CXXSUFFIX}", @vars))}
|
||||
"${CXX}"
|
||||
else
|
||||
"${CC}"
|
||||
end
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @objects
|
||||
@vars["LD"] = ld
|
||||
command = @env.build_command("${LDCMD}", @vars)
|
||||
standard_command("LD #{@target}", command, sources: @objects)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -25,17 +25,6 @@ module Rscons
|
||||
end
|
||||
|
||||
# Create an instance of the Builder to build a target.
|
||||
#
|
||||
# @param options [Hash]
|
||||
# Options.
|
||||
# @option options [String] :target
|
||||
# Target file name.
|
||||
# @option options [Array<String>] :sources
|
||||
# Source file name(s).
|
||||
# @option options [Environment] :env
|
||||
# The Environment executing the builder.
|
||||
# @option options [Hash,VarSet] :vars
|
||||
# Extra construction variables.
|
||||
def initialize(options)
|
||||
super(options)
|
||||
libprefix = @env.expand_varref("${SHLIBPREFIX}", @vars)
|
||||
@ -52,40 +41,26 @@ module Rscons
|
||||
end
|
||||
|
||||
# Run the builder to produce a build target.
|
||||
#
|
||||
# @param options [Hash] Builder run options.
|
||||
#
|
||||
# @return [String,false]
|
||||
# Name of the target file on success or false on failure.
|
||||
def run(options)
|
||||
ld = @env.expand_varref("${SHLD}", @vars)
|
||||
ld = if ld != ""
|
||||
ld
|
||||
elsif @sources.find {|s| s.end_with?(*@env.expand_varref("${DSUFFIX}", @vars))}
|
||||
"${SHDC}"
|
||||
elsif @sources.find {|s| s.end_with?(*@env.expand_varref("${CXXSUFFIX}", @vars))}
|
||||
"${SHCXX}"
|
||||
else
|
||||
"${SHCC}"
|
||||
end
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @objects
|
||||
@vars["SHLD"] = ld
|
||||
command = @env.build_command("${SHLDCMD}", @vars)
|
||||
standard_threaded_build("SHLD #{@target}", @target, command, @objects, @env, @cache)
|
||||
end
|
||||
|
||||
# Finalize a build.
|
||||
#
|
||||
# @param options [Hash]
|
||||
# Finalize options.
|
||||
#
|
||||
# @return [String, nil]
|
||||
# The target name on success or nil on failure.
|
||||
def finalize(options)
|
||||
if options[:command_status]
|
||||
@cache.register_build(@target, options[:tc].command, @objects, @env)
|
||||
@target
|
||||
if @command
|
||||
finalize_command(sources: @objects)
|
||||
true
|
||||
else
|
||||
ld = @env.expand_varref("${SHLD}", @vars)
|
||||
ld = if ld != ""
|
||||
ld
|
||||
elsif @sources.find {|s| s.end_with?(*@env.expand_varref("${DSUFFIX}", @vars))}
|
||||
"${SHDC}"
|
||||
elsif @sources.find {|s| s.end_with?(*@env.expand_varref("${CXXSUFFIX}", @vars))}
|
||||
"${SHCXX}"
|
||||
else
|
||||
"${SHCC}"
|
||||
end
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @objects
|
||||
@vars["SHLD"] = ld
|
||||
command = @env.build_command("${SHLDCMD}", @vars)
|
||||
standard_command("SHLD #{@target}", command, sources: @objects)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -61,40 +61,26 @@ module Rscons
|
||||
end
|
||||
|
||||
# Run the builder to produce a build target.
|
||||
#
|
||||
# @param options [Hash] Builder run options.
|
||||
#
|
||||
# @return [String, ThreadedCommand]
|
||||
# Target file name if target is up to date or a {ThreadedCommand}
|
||||
# to execute to build the target.
|
||||
def run(options)
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @sources
|
||||
@vars["_DEPFILE"] = Rscons.set_suffix(target, env.expand_varref("${DEPFILESUFFIX}", vars))
|
||||
com_prefix = KNOWN_SUFFIXES.find do |compiler, suffix_var|
|
||||
@sources.first.end_with?(*@env.expand_varref("${#{suffix_var}}", @vars))
|
||||
end.tap do |v|
|
||||
v.nil? and raise "Error: unknown input file type: #{@sources.first.inspect}"
|
||||
end.first
|
||||
command = @env.build_command("${#{com_prefix}CMD}", @vars)
|
||||
@env.produces(@target, @vars["_DEPFILE"])
|
||||
standard_threaded_build("#{com_prefix} #{@target}", @target, command, @sources, @env, @cache)
|
||||
end
|
||||
|
||||
# Finalize the build operation.
|
||||
#
|
||||
# @param options [Hash] Builder finalize options.
|
||||
#
|
||||
# @return [String, nil]
|
||||
# Name of the target file on success or nil on failure.
|
||||
def finalize(options)
|
||||
if options[:command_status]
|
||||
if @command
|
||||
deps = @sources
|
||||
if File.exists?(@vars['_DEPFILE'])
|
||||
deps += Util.parse_makefile_deps(@vars['_DEPFILE'])
|
||||
if File.exists?(@vars["_DEPFILE"])
|
||||
deps += Util.parse_makefile_deps(@vars["_DEPFILE"])
|
||||
end
|
||||
@cache.register_build(@target, options[:tc].command, deps.uniq, @env)
|
||||
@target
|
||||
@cache.register_build(@target, @command, deps.uniq, @env)
|
||||
true
|
||||
else
|
||||
@vars["_TARGET"] = @target
|
||||
@vars["_SOURCES"] = @sources
|
||||
@vars["_DEPFILE"] = Rscons.set_suffix(target, env.expand_varref("${DEPFILESUFFIX}", vars))
|
||||
com_prefix = KNOWN_SUFFIXES.find do |compiler, suffix_var|
|
||||
@sources.first.end_with?(*@env.expand_varref("${#{suffix_var}}", @vars))
|
||||
end.tap do |v|
|
||||
v.nil? and raise "Error: unknown input file type: #{@sources.first.inspect}"
|
||||
end.first
|
||||
command = @env.build_command("${#{com_prefix}CMD}", @vars)
|
||||
@env.produces(@target, @vars["_DEPFILE"])
|
||||
standard_command("#{com_prefix} #{@target}", command)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -5,8 +5,14 @@ module Rscons
|
||||
# @since 1.8.0
|
||||
class SimpleBuilder < Builder
|
||||
|
||||
# @return [String]
|
||||
# User-provided builder name.
|
||||
attr_reader :name
|
||||
|
||||
# Create an instance of the Builder to build a target.
|
||||
#
|
||||
# @param name [String]
|
||||
# User-provided builder name.
|
||||
# @param options [Hash]
|
||||
# Options.
|
||||
# @option options [String] :target
|
||||
@ -20,7 +26,8 @@ module Rscons
|
||||
# @param run_proc [Proc]
|
||||
# A Proc to execute when the builder runs. The provided block must
|
||||
# provide the have the same signature as {Builder#run}.
|
||||
def initialize(options, &run_proc)
|
||||
def initialize(name, options, &run_proc)
|
||||
@name = name
|
||||
super(options)
|
||||
@run_proc = run_proc
|
||||
end
|
||||
|
66
lib/rscons/command.rb
Normal file
66
lib/rscons/command.rb
Normal file
@ -0,0 +1,66 @@
|
||||
module Rscons
|
||||
# Class to keep track of system commands that builders need to execute.
|
||||
# Rscons will manage scheduling these commands to be run in separate threads.
|
||||
class Command
|
||||
|
||||
# @return [Builder]
|
||||
# {Builder} executing this command.
|
||||
attr_reader :builder
|
||||
|
||||
# @return [Array<String>]
|
||||
# The command to execute.
|
||||
attr_reader :command
|
||||
|
||||
# @return [nil, true, false]
|
||||
# true if the command gives zero exit status.
|
||||
# false if the command gives non-zero exit status.
|
||||
# nil if command execution fails.
|
||||
attr_accessor :status
|
||||
|
||||
# @return [Hash]
|
||||
# Environment Hash to pass to Kernel#system.
|
||||
attr_reader :system_env
|
||||
|
||||
# @return [Hash]
|
||||
# Options Hash to pass to Kernel#system.
|
||||
attr_reader :system_options
|
||||
|
||||
# @return [Thread]
|
||||
# The thread waiting on this command to terminate.
|
||||
attr_reader :thread
|
||||
|
||||
# Create a ThreadedCommand object.
|
||||
#
|
||||
# @param command [Array<String>]
|
||||
# The command to execute.
|
||||
# @param builder [Builder]
|
||||
# The {Builder} executing this command.
|
||||
# @param options [Hash]
|
||||
# Optional parameters.
|
||||
# @option options [Hash] :system_env
|
||||
# Environment Hash to pass to Kernel#system.
|
||||
# @option options [Hash] :system_options
|
||||
# Options Hash to pass to Kernel#system.
|
||||
def initialize(command, builder, options = {})
|
||||
@command = command
|
||||
@builder = builder
|
||||
@system_env = options[:system_env]
|
||||
@system_options = options[:system_options]
|
||||
end
|
||||
|
||||
# Start a thread to run the command.
|
||||
#
|
||||
# @return [Thread]
|
||||
# The Thread created to run the command.
|
||||
def run
|
||||
env_args = @system_env ? [@system_env] : []
|
||||
options_args = @system_options ? [@system_options] : []
|
||||
system_args = [*env_args, *Rscons.command_executer, *@command, *options_args]
|
||||
|
||||
@thread = Thread.new do
|
||||
system(*system_args)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -48,10 +48,9 @@ module Rscons
|
||||
attr_reader :build_root
|
||||
|
||||
# @return [Integer]
|
||||
# The number of threads to use for this Environment. If nil (the
|
||||
# default), the global Rscons.application.n_threads default value will be
|
||||
# used.
|
||||
attr_writer :n_threads
|
||||
# The number of threads to use for this Environment. Defaults to the
|
||||
# global Rscons.application.n_threads value.
|
||||
attr_accessor :n_threads
|
||||
|
||||
# Create an Environment object.
|
||||
#
|
||||
@ -67,7 +66,8 @@ module Rscons
|
||||
super(options)
|
||||
@id = self.class.get_id
|
||||
self.class.register(self)
|
||||
@threaded_commands = Set.new
|
||||
# Hash of Thread object => {Command} or {Builder}.
|
||||
@threads = {}
|
||||
@registered_build_dependencies = {}
|
||||
@side_effects = {}
|
||||
@builder_set = BuilderSet.new(@registered_build_dependencies, @side_effects)
|
||||
@ -91,6 +91,7 @@ module Rscons
|
||||
:short
|
||||
end
|
||||
@build_root = "#{Cache.instance.configuration_data["build_dir"]}/e.#{@id}"
|
||||
@n_threads = Rscons.application.n_threads
|
||||
|
||||
if block_given?
|
||||
yield self
|
||||
@ -173,9 +174,12 @@ module Rscons
|
||||
# @return [void]
|
||||
def add_builder(builder_class, &action)
|
||||
if builder_class.is_a?(String) or builder_class.is_a?(Symbol)
|
||||
builder_class = BuilderBuilder.new(builder_class.to_s, Rscons::Builders::SimpleBuilder, &action)
|
||||
name = builder_class.to_s
|
||||
builder_class = BuilderBuilder.new(Rscons::Builders::SimpleBuilder, name, &action)
|
||||
else
|
||||
name = builder_class.name
|
||||
end
|
||||
@builders[builder_class.name] = builder_class
|
||||
@builders[name] = builder_class
|
||||
end
|
||||
|
||||
# Add a build hook to the Environment.
|
||||
@ -254,73 +258,29 @@ module Rscons
|
||||
#
|
||||
# @return [void]
|
||||
def process
|
||||
cache = Cache.instance
|
||||
unless cache.configuration_data["configured"]
|
||||
unless Cache.instance.configuration_data["configured"]
|
||||
raise "Project must be configured before processing an Environment"
|
||||
end
|
||||
failure = nil
|
||||
@process_failure = nil
|
||||
@process_blocking_wait = false
|
||||
@process_commands_waiting_to_run = []
|
||||
@process_builder_waits = {}
|
||||
@process_builders_to_run = []
|
||||
begin
|
||||
while @builder_set.size > 0 or @threaded_commands.size > 0
|
||||
|
||||
if failure
|
||||
while @builder_set.size > 0 or @threads.size > 0 or @process_commands_waiting_to_run.size > 0
|
||||
process_step
|
||||
if @process_failure
|
||||
# On a build failure, do not start any more builders or commands,
|
||||
# but let the threads that have already been started complete.
|
||||
@builder_set.clear
|
||||
builder = nil
|
||||
else
|
||||
targets_still_building = @threaded_commands.map do |tc|
|
||||
tc.builder.target
|
||||
end
|
||||
builder = @builder_set.get_next_builder_to_run(targets_still_building)
|
||||
@process_commands_waiting_to_run.clear
|
||||
end
|
||||
|
||||
# TODO: have Cache determine when checksums may be invalid based on
|
||||
# file size and/or timestamp.
|
||||
cache.clear_checksum_cache!
|
||||
|
||||
if builder
|
||||
result = run_builder(builder, cache)
|
||||
unless result
|
||||
failure = "Failed to build #{builder.target}"
|
||||
Ansi.write($stderr, :red, failure, :reset, "\n")
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
completed_tcs = Set.new
|
||||
# First do a non-blocking wait to pick up any threads that have
|
||||
# completed since last time.
|
||||
while tc = wait_for_threaded_commands(nonblock: true)
|
||||
completed_tcs << tc
|
||||
end
|
||||
|
||||
# If needed, do a blocking wait.
|
||||
if (@threaded_commands.size > 0) and
|
||||
((completed_tcs.empty? and builder.nil?) or (@threaded_commands.size >= n_threads))
|
||||
completed_tcs << wait_for_threaded_commands
|
||||
end
|
||||
|
||||
# Process all completed {ThreadedCommand} objects.
|
||||
completed_tcs.each do |tc|
|
||||
result = finalize_builder(tc)
|
||||
if result
|
||||
@build_hooks[:post].each do |build_hook_block|
|
||||
build_hook_block.call(tc.builder)
|
||||
end
|
||||
else
|
||||
unless @echo == :command
|
||||
print_failed_command(tc.command)
|
||||
end
|
||||
failure = "Failed to build #{tc.builder.target}"
|
||||
Ansi.write($stderr, :red, failure, :reset, "\n")
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
ensure
|
||||
cache.write
|
||||
Cache.instance.write
|
||||
end
|
||||
if failure
|
||||
raise BuildError.new(failure)
|
||||
if @process_failure
|
||||
raise BuildError.new(@process_failure)
|
||||
end
|
||||
end
|
||||
|
||||
@ -505,47 +465,6 @@ module Rscons
|
||||
end
|
||||
end
|
||||
|
||||
# Invoke a builder to build the given target based on the given sources.
|
||||
#
|
||||
# @param builder [Builder] The Builder to use.
|
||||
# @param cache [Cache] The Cache.
|
||||
#
|
||||
# @return [String,false] Return value from the {Builder}'s +run+ method.
|
||||
def run_builder(builder, cache)
|
||||
builder.vars = @varset.merge(builder.vars)
|
||||
build_operation = {}
|
||||
call_build_hooks = lambda do |sec|
|
||||
@build_hooks[sec].each do |build_hook_block|
|
||||
build_hook_block.call(builder)
|
||||
end
|
||||
end
|
||||
|
||||
# Invoke pre-build hooks.
|
||||
call_build_hooks[:pre]
|
||||
|
||||
# Call the builder's #run method.
|
||||
rv = builder.run(build_operation)
|
||||
|
||||
(@side_effects[builder.target] || []).each do |side_effect_file|
|
||||
# Register side-effect files as build targets so that a Cache clean
|
||||
# operation will remove them.
|
||||
cache.register_build(side_effect_file, nil, [], self)
|
||||
end
|
||||
|
||||
if rv.is_a?(ThreadedCommand)
|
||||
# Store the build operation so the post-build hooks can be called
|
||||
# with it when the threaded command completes.
|
||||
rv.builder = builder
|
||||
# TODO: remove
|
||||
rv.build_operation = build_operation
|
||||
start_threaded_command(rv)
|
||||
else
|
||||
call_build_hooks[:post] if rv
|
||||
end
|
||||
|
||||
rv
|
||||
end
|
||||
|
||||
# Expand a path to be relative to the Environment's build root.
|
||||
#
|
||||
# Paths beginning with "^/" are expanded by replacing "^" with the
|
||||
@ -591,15 +510,6 @@ module Rscons
|
||||
end
|
||||
end
|
||||
|
||||
# Get the number of threads to use for parallelized builds in this
|
||||
# Environment.
|
||||
#
|
||||
# @return [Integer]
|
||||
# Number of threads to use for parallelized builds in this Environment.
|
||||
def n_threads
|
||||
@n_threads || Rscons.application.n_threads
|
||||
end
|
||||
|
||||
# Print the builder run message, depending on the Environment's echo mode.
|
||||
#
|
||||
# @param short_description [String]
|
||||
@ -636,49 +546,146 @@ module Rscons
|
||||
|
||||
private
|
||||
|
||||
# Start a threaded command in a new thread.
|
||||
# Signal a build failure to the {#process} method.
|
||||
#
|
||||
# @param tc [ThreadedCommand]
|
||||
# The ThreadedCommand to start.
|
||||
# @param target [String]
|
||||
# Build target name.
|
||||
#
|
||||
# @return [void]
|
||||
def start_threaded_command(tc)
|
||||
print_builder_run_message(tc.short_description, tc.command)
|
||||
|
||||
env_args = tc.system_env ? [tc.system_env] : []
|
||||
options_args = tc.system_options ? [tc.system_options] : []
|
||||
system_args = [*env_args, *Rscons.command_executer, *tc.command, *options_args]
|
||||
|
||||
tc.thread = Thread.new do
|
||||
system(*system_args)
|
||||
end
|
||||
@threaded_commands << tc
|
||||
def process_failure(target)
|
||||
@process_failure = "Failed to build #{target}"
|
||||
Ansi.write($stderr, :red, @process_failure, :reset, "\n")
|
||||
end
|
||||
|
||||
# Wait for threaded commands to complete.
|
||||
# Run a builder and process its return value.
|
||||
#
|
||||
# @param options [Hash]
|
||||
# Options.
|
||||
# @option options [Boolean] :nonblock
|
||||
# Set to true to not block.
|
||||
# @param builder [Builder]
|
||||
# The builder.
|
||||
#
|
||||
# @return [ThreadedCommand, nil]
|
||||
# The {ThreadedCommand} object that is finished.
|
||||
def wait_for_threaded_commands(options = {})
|
||||
threads = @threaded_commands.map(&:thread)
|
||||
if finished_thread = find_finished_thread(threads, options[:nonblock])
|
||||
threaded_command = @threaded_commands.find do |tc|
|
||||
tc.thread == finished_thread
|
||||
# @return [void]
|
||||
def run_builder(builder)
|
||||
# TODO: have Cache determine when checksums may be invalid based on
|
||||
# file size and/or timestamp.
|
||||
Cache.instance.clear_checksum_cache!
|
||||
case result = builder.run({})
|
||||
when Array
|
||||
result.each do |waititem|
|
||||
@process_builder_waits[builder] ||= Set.new
|
||||
@process_builder_waits[builder] << waititem
|
||||
case waititem
|
||||
when Thread
|
||||
@threads[waititem] = builder
|
||||
when Command
|
||||
@process_commands_waiting_to_run << waititem
|
||||
when Builder
|
||||
# No action needed.
|
||||
else
|
||||
raise "Unrecognized #{builder.name} builder return item: #{waititem.inspect}"
|
||||
end
|
||||
end
|
||||
@threaded_commands.delete(threaded_command)
|
||||
threaded_command
|
||||
when false
|
||||
process_failure(builder.target)
|
||||
when true
|
||||
# Register side-effect files as build targets so that a Cache
|
||||
# clean operation will remove them.
|
||||
(@side_effects[builder.target] || []).each do |side_effect_file|
|
||||
Cache.instance.register_build(side_effect_file, nil, [], self)
|
||||
end
|
||||
@build_hooks[:post].each do |build_hook_block|
|
||||
build_hook_block.call(builder)
|
||||
end
|
||||
process_remove_wait(builder)
|
||||
else
|
||||
raise "Unrecognized #{builder.name} builder return value: #{result.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
# Check if any of the requested threads are finished.
|
||||
# Remove an item that a builder may have been waiting on.
|
||||
#
|
||||
# @param waititem [Object]
|
||||
# Item that a builder may be waiting on.
|
||||
#
|
||||
# @return [void]
|
||||
def process_remove_wait(waititem)
|
||||
@process_builder_waits.to_a.each do |builder, waits|
|
||||
if waits.include?(waititem)
|
||||
waits.delete(waititem)
|
||||
end
|
||||
if waits.empty?
|
||||
@process_builder_waits.delete(builder)
|
||||
@process_builders_to_run << builder
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Broken out from {#process} to perform a single operation.
|
||||
#
|
||||
# @return [void]
|
||||
def process_step
|
||||
# Check if a thread has completed since last time.
|
||||
thread = find_finished_thread(true)
|
||||
|
||||
# Check if we need to do a blocking wait for a thread to complete.
|
||||
if thread.nil? and (@threads.size >= n_threads or @process_blocking_wait)
|
||||
thread = find_finished_thread(false)
|
||||
@process_blocking_wait = false
|
||||
end
|
||||
|
||||
if thread
|
||||
# We found a completed thread.
|
||||
process_remove_wait(thread)
|
||||
builder = builder_for_thread(thread)
|
||||
completed_command = @threads[thread]
|
||||
@threads.delete(thread)
|
||||
if completed_command.is_a?(Command)
|
||||
process_remove_wait(completed_command)
|
||||
completed_command.status = thread.value
|
||||
unless completed_command.status
|
||||
unless @echo == :command
|
||||
print_failed_command(completed_command.command)
|
||||
end
|
||||
return process_failure(builder.target)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if @threads.size < n_threads and @process_commands_waiting_to_run.size > 0
|
||||
# There is a command waiting to run and a thread free to run it.
|
||||
command = @process_commands_waiting_to_run.slice!(0)
|
||||
@threads[command.run] = command
|
||||
return
|
||||
end
|
||||
|
||||
unless @process_builders_to_run.empty?
|
||||
# There is a builder waiting to run that was unblocked by its wait
|
||||
# items completing.
|
||||
return run_builder(@process_builders_to_run.slice!(0))
|
||||
end
|
||||
|
||||
# If no builder was found to run yet and there are threads available, try
|
||||
# to get a runnable builder from the builder set.
|
||||
targets_still_building = @threads.reduce([]) do |result, (thread, obj)|
|
||||
result << builder_for_thread(thread).target
|
||||
end
|
||||
builder = @builder_set.get_next_builder_to_run(targets_still_building)
|
||||
|
||||
if builder
|
||||
builder.vars = @varset.merge(builder.vars)
|
||||
@build_hooks[:pre].each do |build_hook_block|
|
||||
build_hook_block.call(builder)
|
||||
end
|
||||
return run_builder(builder)
|
||||
end
|
||||
|
||||
if @threads.size > 0
|
||||
# A runnable builder was not found but there is a thread running,
|
||||
# so next time do a blocking wait for a thread to complete.
|
||||
@process_blocking_wait = true
|
||||
end
|
||||
end
|
||||
|
||||
# Find a finished thread.
|
||||
#
|
||||
# @param threads [Array<Thread>]
|
||||
# The threads to check.
|
||||
# @param nonblock [Boolean]
|
||||
# Whether to be non-blocking. If true, nil will be returned if no thread
|
||||
# is finished. If false, the method will wait until one of the threads
|
||||
@ -686,31 +693,32 @@ module Rscons
|
||||
#
|
||||
# @return [Thread, nil]
|
||||
# The finished thread, if any.
|
||||
def find_finished_thread(threads, nonblock)
|
||||
def find_finished_thread(nonblock)
|
||||
if nonblock
|
||||
threads.find do |thread|
|
||||
@threads.keys.find do |thread|
|
||||
!thread.alive?
|
||||
end
|
||||
else
|
||||
if threads.empty?
|
||||
if @threads.empty?
|
||||
raise "No threads to wait for"
|
||||
end
|
||||
ThreadsWait.new(*threads).next_wait
|
||||
ThreadsWait.new(*@threads.keys).next_wait
|
||||
end
|
||||
end
|
||||
|
||||
# Call a builder's #finalize method after a ThreadedCommand terminates.
|
||||
# Get the {Builder} waiting on the given Thread.
|
||||
#
|
||||
# @param tc [ThreadedCommand]
|
||||
# The ThreadedCommand returned from the builder's #run method.
|
||||
# @param thread [Thread]
|
||||
# The thread.
|
||||
#
|
||||
# @return [String, false]
|
||||
# Result of Builder#finalize.
|
||||
def finalize_builder(tc)
|
||||
tc.builder.finalize(
|
||||
tc.build_operation.merge(
|
||||
command_status: tc.thread.value,
|
||||
tc: tc))
|
||||
# @return [Builder]
|
||||
# The {Builder} waiting on the given thread.
|
||||
def builder_for_thread(thread)
|
||||
if @threads[thread].is_a?(Command)
|
||||
@threads[thread].builder
|
||||
else
|
||||
@threads[thread]
|
||||
end
|
||||
end
|
||||
|
||||
# Find a builder that meets the requested features and produces a target
|
||||
|
@ -1,61 +0,0 @@
|
||||
module Rscons
|
||||
# If a builder returns an instance of this class from its #run method, then
|
||||
# Rscons will execute the command specified in a thread and allow other
|
||||
# builders to continue executing in parallel.
|
||||
class ThreadedCommand
|
||||
|
||||
# @return [Array<String>]
|
||||
# The command to execute.
|
||||
attr_reader :command
|
||||
|
||||
# @return [String]
|
||||
# Short description of the command. This will be printed to standard
|
||||
# output if the Environment's echo mode is :short.
|
||||
attr_reader :short_description
|
||||
|
||||
# @return [Hash]
|
||||
# Environment Hash to pass to Kernel#system.
|
||||
attr_reader :system_env
|
||||
|
||||
# @return [Hash]
|
||||
# Options Hash to pass to Kernel#system.
|
||||
attr_reader :system_options
|
||||
|
||||
# @return [Hash]
|
||||
# Field for Rscons to store the build operation while this threaded
|
||||
# command is executing.
|
||||
attr_accessor :build_operation
|
||||
|
||||
# @return [Builder]
|
||||
# {Builder} executing this command.
|
||||
attr_accessor :builder
|
||||
|
||||
# @return [Thread]
|
||||
# The thread waiting on this command to terminate.
|
||||
attr_accessor :thread
|
||||
|
||||
# Create a ThreadedCommand object.
|
||||
#
|
||||
# @param command [Array<String>]
|
||||
# The command to execute.
|
||||
# @param options [Hash]
|
||||
# Optional parameters.
|
||||
# @option options [Object] :builder_info
|
||||
# Arbitrary object to store builder-specific info. This object value will
|
||||
# be passed back into the builder's #finalize method.
|
||||
# @option options [String] :short_description
|
||||
# Short description of the command. This will be printed to standard
|
||||
# output if the Environment's echo mode is :short.
|
||||
# @option options [Hash] :system_env
|
||||
# Environment Hash to pass to Kernel#system.
|
||||
# @option options [Hash] :system_options
|
||||
# Options Hash to pass to Kernel#system.
|
||||
def initialize(command, options = {})
|
||||
@command = command
|
||||
@short_description = options[:short_description]
|
||||
@system_env = options[:system_env]
|
||||
@system_options = options[:system_options]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -327,6 +327,29 @@ EOF
|
||||
expect(`./program.exe`).to eq "The value is 42\n"
|
||||
end
|
||||
|
||||
it 'raises an error when a custom builder returns an invalid value from #run' do
|
||||
test_dir("custom_builder")
|
||||
result = run_rscons(rsconscript: "error_run_return_value.rb")
|
||||
expect(result.stderr).to match /Unrecognized MyBuilder builder return value: "hi"/
|
||||
expect(result.status).to_not eq 0
|
||||
end
|
||||
|
||||
it 'raises an error when a custom builder returns an invalid value using Builder#wait_for' do
|
||||
test_dir("custom_builder")
|
||||
result = run_rscons(rsconscript: "error_wait_for.rb")
|
||||
expect(result.stderr).to match /Unrecognized MyBuilder builder return item: 1/
|
||||
expect(result.status).to_not eq 0
|
||||
end
|
||||
|
||||
it 'supports a Builder waiting for a custom Thread object' do
|
||||
test_dir "custom_builder"
|
||||
result = run_rscons(rsconscript: "wait_for_thread.rb")
|
||||
expect(result.stderr).to eq ""
|
||||
expect(result.status).to eq 0
|
||||
expect(lines(result.stdout)).to include "MyBuilder foo"
|
||||
expect(File.exists?("foo")).to be_truthy
|
||||
end
|
||||
|
||||
it 'allows cloning Environment objects' do
|
||||
test_dir('clone_env')
|
||||
result = run_rscons
|
||||
|
@ -1,12 +0,0 @@
|
||||
module Rscons
|
||||
module Builders
|
||||
describe SimpleBuilder do
|
||||
let(:env) {Environment.new}
|
||||
|
||||
it "should create a new builder with the given action" do
|
||||
builder = Rscons::Builders::SimpleBuilder.new({}) { 0x1234 }
|
||||
expect(builder.run(1,2,3,4,5)).to eq(0x1234)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -230,7 +230,7 @@ module Rscons
|
||||
describe "#find_finished_thread" do
|
||||
it "raises an error if called with nonblock=false and no threads to wait for" do
|
||||
env = Environment.new
|
||||
expect {env.__send__(:find_finished_thread, [], false)}.to raise_error /No threads to wait for/
|
||||
expect {env.__send__(:find_finished_thread, false)}.to raise_error /No threads to wait for/
|
||||
end
|
||||
end
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user