create Builder instance for each build operation - close #88

This commit is contained in:
Josh Holtrop 2019-02-10 17:27:40 -05:00
parent 12f1909d35
commit aac32d20b0
28 changed files with 181 additions and 147 deletions

View File

@ -11,7 +11,7 @@ end
build do build do
Environment.new do |env| Environment.new do |env|
env.add_builder(MySource.new) env.add_builder(MySource)
env.MySource('inc.h', []) env.MySource('inc.h', [])
env.Program('program.exe', Dir['*.c']) env.Program('program.exe', Dir['*.c'])
end end

View File

@ -13,7 +13,7 @@ build do
env = Environment.new do |env| env = Environment.new do |env|
env["hdr"] = "inc.h" env["hdr"] = "inc.h"
env["src"] = "program.c" env["src"] = "program.c"
env.add_builder(MySource.new) env.add_builder(MySource)
env.MySource('${hdr}') env.MySource('${hdr}')
env.Program('program.exe', "${src}") env.Program('program.exe', "${src}")
end end

View File

@ -11,7 +11,7 @@ end
build do build do
e1 = Environment.new do |env| e1 = Environment.new do |env|
env.add_builder(MySource.new) env.add_builder(MySource)
env["one"] = "5" env["one"] = "5"
env[:cfg] = {val: "9"} env[:cfg] = {val: "9"}
env["two"] = lambda do |args| env["two"] = lambda do |args|

View File

@ -14,7 +14,7 @@ end
build do build do
Environment.new do |env| Environment.new do |env|
env.add_builder(CHGen.new) env.add_builder(CHGen)
env.CHGen("inc.c", ["program.c"]) env.CHGen("inc.c", ["program.c"])
env.Program("program.exe", %w[program.c inc.c]) env.Program("program.exe", %w[program.c inc.c])
end end

View File

@ -2,7 +2,8 @@ build do
Environment.new do |env| Environment.new do |env|
require 'json' require 'json'
require 'yaml' require 'yaml'
env.add_builder(:JsonToYaml) do |target, sources, cache, env, vars| env.add_builder(:JsonToYaml) do |params|
target, sources, cache, env, vars = params.values_at(:target, :sources, :cache, :env, :vars)
unless cache.up_to_date?(target, :JsonToYaml, sources, env) unless cache.up_to_date?(target, :JsonToYaml, sources, env)
cache.mkdir_p(File.dirname(target)) cache.mkdir_p(File.dirname(target))
File.open(target, 'w') do |f| File.open(target, 'w') do |f|

View File

@ -5,7 +5,7 @@ class TestBuilder < Rscons::Builder
end end
build do build do
Environment.new do |env| Environment.new do |env|
env.add_builder(TestBuilder.new) env.add_builder(TestBuilder)
env.TestBuilder("file") env.TestBuilder("file")
end end
end end

View File

@ -30,7 +30,7 @@ end
build do build do
Environment.new do |env| Environment.new do |env|
env.add_builder(DebugBuilder.new) env.add_builder(DebugBuilder)
if Rscons.vars["new_user_dep"] if Rscons.vars["new_user_dep"]
env.depends("foo.o", "new_dep") env.depends("foo.o", "new_dep")
end end

View File

@ -16,7 +16,7 @@ end
build do build do
Environment.new do |env| Environment.new do |env|
env.add_builder(TestBuilder.new) env.add_builder(TestBuilder)
env.TestBuilder("foo") env.TestBuilder("foo")
end end
end end

View File

@ -1,6 +1,7 @@
build do build do
Environment.new do |env| Environment.new do |env|
env.add_builder(:Checker) do |target, sources, cache, env, vars| env.add_builder(:Checker) do |params|
target, sources, cache, env, vars = params.values_at(:target, :sources, :cache, :env, :vars)
unless cache.up_to_date?(target, :Checker, sources, env) unless cache.up_to_date?(target, :Checker, sources, env)
puts "Checker #{sources.first}" if env.echo != :off puts "Checker #{sources.first}" if env.echo != :off
cache.register_build(target, :Checker, sources, env) cache.register_build(target, :Checker, sources, env)

View File

@ -1,13 +1,13 @@
class MyObject < Rscons::Builder class MyObject < Rscons::Builder
def run(options) def run(options)
target, sources, cache, env, vars = options.values_at(:target, :sources, :cache, :env, :vars) target, sources, cache, env, vars = options.values_at(:target, :sources, :cache, :env, :vars)
env.run_builder(env.builders["Object"], target, sources, cache, vars) env.run_builder(env.builders["Object"].new, target, sources, cache, vars)
end end
end end
build do build do
Environment.new do |env| Environment.new do |env|
env.add_builder(MyObject.new) env.add_builder(MyObject)
env.MyObject("simple.o", "simple.c") env.MyObject("simple.o", "simple.c")
env.Program("simple.exe", "simple.o") env.Program("simple.exe", "simple.o")
end end

View File

@ -20,8 +20,8 @@ end
build do build do
Environment.new do |env| Environment.new do |env|
env.add_builder(ThreadedTestBuilder.new) env.add_builder(ThreadedTestBuilder)
env.add_builder(NonThreadedTestBuilder.new) env.add_builder(NonThreadedTestBuilder)
env.ThreadedTestBuilder("a") env.ThreadedTestBuilder("a")
env.ThreadedTestBuilder("b") env.ThreadedTestBuilder("b")
env.ThreadedTestBuilder("c") env.ThreadedTestBuilder("c")

View File

@ -16,7 +16,7 @@ end
build do build do
Environment.new do |env| Environment.new do |env|
env.add_builder(TestBuilder.new) env.add_builder(TestBuilder)
env.TestBuilder("one", [], "wait_time" => "3") env.TestBuilder("one", [], "wait_time" => "3")
env.TestBuilder("two", [], "wait_time" => "0") env.TestBuilder("two", [], "wait_time" => "0")
env.depends("two", "one") env.depends("two", "one")

View File

@ -13,7 +13,7 @@ end
build do build do
Environment.new do |env| Environment.new do |env|
env.add_builder(Fail.new) env.add_builder(Fail)
4.times do |i| 4.times do |i|
wait_time = i + 1 wait_time = i + 1
env.Fail("foo_#{wait_time}", [], "wait_time" => wait_time.to_s) env.Fail("foo_#{wait_time}", [], "wait_time" => wait_time.to_s)

View File

@ -16,7 +16,7 @@ end
build do build do
Environment.new(echo: :command) do |env| Environment.new(echo: :command) do |env|
env.add_builder(StrictBuilder.new) env.add_builder(StrictBuilder)
env.Object("one.o", "one.c", "CCFLAGS" => %w[-DONE]) env.Object("one.o", "one.c", "CCFLAGS" => %w[-DONE])
env.Object("two.o", "two.c") env.Object("two.o", "two.c")
sources = File.read("sources", mode: "rb").split(" ") sources = File.read("sources", mode: "rb").split(" ")

View File

@ -1,12 +1,12 @@
class MyObject < Rscons::Builder class MyObject < Rscons::Builder
def run(target, sources, cache, env, vars) def run(target, sources, cache, env, vars)
env.run_builder(env.builders["Object"], target, sources, cache, vars) env.run_builder(env.builders["Object"].new, target, sources, cache, vars)
end end
end end
build do build do
Environment.new(echo: :command) do |env| Environment.new(echo: :command) do |env|
env.add_builder(MyObject.new) env.add_builder(MyObject)
env.append('CPPPATH' => Rscons.glob('src/**')) env.append('CPPPATH' => Rscons.glob('src/**'))
env.add_build_hook do |build_op| env.add_build_hook do |build_op|
if build_op[:builder].name == "MyObject" && build_op[:sources].first =~ %r{one\.c} if build_op[:builder].name == "MyObject" && build_op[:sources].first =~ %r{one\.c}

View File

@ -9,7 +9,7 @@ end
build do build do
Environment.new do |env| Environment.new do |env|
env.echo = :command env.echo = :command
env.add_builder(MyBuilder.new) env.add_builder(MyBuilder)
env.MyBuilder("foo") env.MyBuilder("foo")
end end
end end

View File

@ -3,6 +3,7 @@ require_relative "rscons/application"
require_relative "rscons/basic_environment" require_relative "rscons/basic_environment"
require_relative "rscons/build_target" require_relative "rscons/build_target"
require_relative "rscons/builder" require_relative "rscons/builder"
require_relative "rscons/builder_builder"
require_relative "rscons/cache" require_relative "rscons/cache"
require_relative "rscons/configure_op" require_relative "rscons/configure_op"
require_relative "rscons/environment" require_relative "rscons/environment"

View File

@ -6,13 +6,14 @@ module Rscons
# Class to hold an object that knows how to build a certain type of file. # Class to hold an object that knows how to build a certain type of file.
class Builder class Builder
class << self
# Return the name of the builder. # Return the name of the builder.
# #
# If not overridden this defaults to the last component of the class name. # If not overridden this defaults to the last component of the class name.
# #
# @return [String] The name of the builder. # @return [String] The name of the builder.
def name def name
self.class.name.split(":").last super.split(":").last
end end
# Return a set of build features that this builder provides. # Return a set of build features that this builder provides.
@ -23,6 +24,33 @@ module Rscons
[] []
end end
# Return whether this builder object is capable of producing a given target
# file name from a given source file name.
#
# @param target [String]
# The target file name.
# @param source [String]
# The source file name.
# @param env [Environment]
# The Environment.
#
# @return [Boolean]
# Whether this builder object is capable of producing a given target
# file name from a given source file name.
def produces?(target, source, env)
false
end
end
# Return the name of the builder.
#
# If not overridden this defaults to the last component of the class name.
#
# @return [String] The name of the builder.
def name
self.class.name
end
# Create a BuildTarget object for this build target. # Create a BuildTarget object for this build target.
# #
# Builder sub-classes can override this method to manipulate parameters # Builder sub-classes can override this method to manipulate parameters
@ -43,23 +71,6 @@ module Rscons
BuildTarget.new(options) BuildTarget.new(options)
end end
# Return whether this builder object is capable of producing a given target
# file name from a given source file name.
#
# @param target [String]
# The target file name.
# @param source [String]
# The source file name.
# @param env [Environment]
# The Environment.
#
# @return [Boolean]
# Whether this builder object is capable of producing a given target
# file name from a given source file name.
def produces?(target, source, env)
false
end
# Set up a build operation using this builder. # Set up a build operation using this builder.
# #
# This method is called when a build target is registered using this # This method is called when a build target is registered using this

View File

@ -0,0 +1,33 @@
module Rscons
# A class that knows how to build an instance of another Builder class when
# 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
@builder_class = builder_class
@builder_args = builder_args
@builder_block = builder_block
end
# 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)
end
end
end

View File

@ -43,7 +43,7 @@ module Rscons
# Check the cache and copy if necessary # Check the cache and copy if necessary
unless cache.up_to_date?(dest, :Copy, [src], env) unless cache.up_to_date?(dest, :Copy, [src], env)
unless printed_message unless printed_message
env.print_builder_run_message("#{name} #{target}", nil) env.print_builder_run_message("#{self.class.name} #{target}", nil)
printed_message = true printed_message = true
end end
cache.mkdir_p(File.dirname(dest)) cache.mkdir_p(File.dirname(dest))

View File

@ -53,6 +53,7 @@ module Rscons
'DCCMD' => ['${DC}', '-c', '-o', '${_TARGET}', '${DDEPGEN}', '${INCPREFIX}${D_IMPORT_PATH}', '${DFLAGS}', '${_SOURCES}'], 'DCCMD' => ['${DC}', '-c', '-o', '${_TARGET}', '${DDEPGEN}', '${INCPREFIX}${D_IMPORT_PATH}', '${DFLAGS}', '${_SOURCES}'],
) )
class << self
# Return whether this builder object is capable of producing a given target # Return whether this builder object is capable of producing a given target
# file name from a given source file name. # file name from a given source file name.
# #
@ -72,6 +73,7 @@ module Rscons
source.end_with?(*env[suffix_var]) source.end_with?(*env[suffix_var])
end end
end end
end
# Run the builder to produce a build target. # Run the builder to produce a build target.
# #

View File

@ -36,7 +36,7 @@ module Rscons
env.produces(target, vars['_DEPFILE']) env.produces(target, vars['_DEPFILE'])
# Store vars back into options so new keys are accessible in #finalize. # Store vars back into options so new keys are accessible in #finalize.
options[:vars] = vars options[:vars] = vars
standard_threaded_build("#{name} #{target}", target, command, sources, env, cache) standard_threaded_build("#{self.class.name} #{target}", target, command, sources, env, cache)
end end
# Finalize the build operation. # Finalize the build operation.

View File

@ -14,6 +14,7 @@ module Rscons
'SHLDCMD' => ['${SHLD}', '-o', '${_TARGET}', '${SHLDFLAGS}', '${_SOURCES}', '${SHLIBDIRPREFIX}${LIBPATH}', '${SHLIBLINKPREFIX}${LIBS}'] 'SHLDCMD' => ['${SHLD}', '-o', '${_TARGET}', '${SHLDFLAGS}', '${_SOURCES}', '${SHLIBDIRPREFIX}${LIBPATH}', '${SHLIBLINKPREFIX}${LIBS}']
) )
class << self
# Return a set of build features that this builder provides. # Return a set of build features that this builder provides.
# #
# @return [Array<String>] # @return [Array<String>]
@ -21,6 +22,7 @@ module Rscons
def features def features
%w[shared] %w[shared]
end end
end
# Create a BuildTarget object for this build target. # Create a BuildTarget object for this build target.
# #

View File

@ -30,6 +30,7 @@ module Rscons
'SHDCCMD' => ['${SHDC}', '-c', '-o', '${_TARGET}', '${INCPREFIX}${D_IMPORT_PATH}', '${SHDFLAGS}', '${_SOURCES}'], 'SHDCCMD' => ['${SHDC}', '-c', '-o', '${_TARGET}', '${INCPREFIX}${D_IMPORT_PATH}', '${SHDFLAGS}', '${_SOURCES}'],
) )
class << self
# Return a set of build features that this builder provides. # Return a set of build features that this builder provides.
# #
# @return [Array<String>] # @return [Array<String>]
@ -57,6 +58,7 @@ module Rscons
source.end_with?(*env[suffix_var]) source.end_with?(*env[suffix_var])
end end
end end
end
# Run the builder to produce a build target. # Run the builder to produce a build target.
# #

View File

@ -1,39 +1,26 @@
module Rscons module Rscons
module Builders module Builders
# A Generic builder class whose name and operation is defined at # A Generic builder which has its operation is defined at instantiation.
# instantiation.
# #
# @since 1.8.0 # @since 1.8.0
class SimpleBuilder < Builder class SimpleBuilder < Builder
# @return [String] The name of this builder.
attr_reader :name
# Create a new builder with the given name and action. # Create a new builder with the given action.
# #
# @param name [String,Symbol] The name of the builder when registered. # @param run_proc [Proc]
# @param block [Block] # A Proc to execute when the builder runs. The provided block must
# The action to perform when the builder is processed. The provided # provide the have the same signature as {Builder#run}.
# block must return the target file on success or false on failure. def initialize(&run_proc)
# The provided block should have the same signature as {Builder#run}. @run_proc = run_proc
def initialize(name, &block)
@name = name.to_s
@block = block
end end
# Run the builder to produce a build target. # Run the builder to produce a build target.
# #
# @param target [String] Target file name. # Method signature described by {Builder#run}.
# @param sources [Array<String>] Source file name(s). def run(*args)
# @param cache [Cache] The Cache object. @run_proc.call(*args)
# @param env [Environment] The Environment executing the builder.
# @param vars [Hash,VarSet] Extra construction variables.
#
# @return [String,false]
# Name of the target file on success or false on failure.
def run(target, sources, cache, env, vars)
@block.call(target, sources, cache, env, vars)
end
end
end
end end
end
end
end

View File

@ -72,13 +72,14 @@ module Rscons
@side_effects = {} @side_effects = {}
@job_set = JobSet.new(@registered_build_dependencies, @side_effects) @job_set = JobSet.new(@registered_build_dependencies, @side_effects)
@user_deps = {} @user_deps = {}
# Hash of builder name (String) => builder class (Class).
@builders = {} @builders = {}
@build_hooks = {pre: [], post: []} @build_hooks = {pre: [], post: []}
unless options[:exclude_builders] unless options[:exclude_builders]
DEFAULT_BUILDERS.each do |builder_class_name| DEFAULT_BUILDERS.each do |builder_class_name|
builder_class = Builders.const_get(builder_class_name) builder_class = Builders.const_get(builder_class_name)
builder_class or raise "Could not find builder class #{builder_class_name}" builder_class or raise "Could not find builder class #{builder_class_name}"
add_builder(builder_class.new) add_builder(builder_class)
end end
end end
@echo = @echo =
@ -146,22 +147,22 @@ module Rscons
env env
end end
# Add a {Builder} object to the Environment. # Add a {Builder} to the Environment.
# #
# @overload add_builder(builder) # @overload add_builder(builder_class)
# #
# Add the given builder to the Environment. # Add the given builder to the Environment.
# #
# @param builder [Builder] An instance of the builder to register. # @param builder_class [Class] A builder class to register.
# #
# @overload add_builder(builder,&action) # @overload add_builder(name,&action)
# #
# Create a new {Builders::SimpleBuilder} instance and add it to the # Create a new {Builders::SimpleBuilder} instance and add it to the
# environment. # environment.
# #
# @since 1.8.0 # @since 1.8.0
# #
# @param builder [String,Symbol] # @param name [String,Symbol]
# The name of the builder to add. # The name of the builder to add.
# #
# @param action [Block] # @param action [Block]
@ -170,11 +171,11 @@ module Rscons
# {Rscons::Builder#run}. # {Rscons::Builder#run}.
# #
# @return [void] # @return [void]
def add_builder(builder, &action) def add_builder(builder_class, &action)
if not builder.is_a? Rscons::Builder if builder_class.is_a?(String) or builder_class.is_a?(Symbol)
builder = Rscons::Builders::SimpleBuilder.new(builder, &action) builder_class = BuilderBuilder.new(builder_class.to_s, Rscons::Builders::SimpleBuilder, &action)
end end
@builders[builder.name] = builder @builders[builder_class.name] = builder_class
end end
# Add a build hook to the Environment. # Add a build hook to the Environment.
@ -351,7 +352,7 @@ module Rscons
unless vars.is_a?(Hash) or vars.is_a?(VarSet) unless vars.is_a?(Hash) or vars.is_a?(VarSet)
raise "Unexpected construction variable set: #{vars.inspect}" raise "Unexpected construction variable set: #{vars.inspect}"
end end
builder = @builders[method.to_s] builder = @builders[method.to_s].new
target = expand_path(expand_varref(target)) target = expand_path(expand_varref(target))
sources = Array(sources).map do |source| sources = Array(sources).map do |source|
expand_path(expand_varref(source)) expand_path(expand_varref(source))
@ -789,22 +790,22 @@ module Rscons
# @return [Builder, nil] # @return [Builder, nil]
# The builder found, if any. # The builder found, if any.
def find_builder_for(target, source, features) def find_builder_for(target, source, features)
@builders.values.find do |builder| @builders.values.find do |builder_class|
features_met?(builder, features) and builder.produces?(target, source, self) features_met?(builder_class, features) and builder_class.produces?(target, source, self)
end end
end end
# Determine if a builder meets the requested features. # Determine if a builder meets the requested features.
# #
# @param builder [Builder] # @param builder_class [Class]
# The builder. # The builder.
# @param features [Array<String>] # @param features [Array<String>]
# See {#register_builds}. # See {#register_builds}.
# #
# @return [Boolean] # @return [Boolean]
# Whether the builder meets the requested features. # Whether the builder meets the requested features.
def features_met?(builder, features) def features_met?(builder_class, features)
builder_features = builder.features builder_features = builder_class.features
features.all? do |feature| features.all? do |feature|
want_feature = true want_feature = true
if feature =~ /^-(.*)$/ if feature =~ /^-(.*)$/

View File

@ -3,15 +3,8 @@ module Rscons
describe SimpleBuilder do describe SimpleBuilder do
let(:env) {Environment.new} let(:env) {Environment.new}
it "should create a new builder with the given name (as a symbol) and action" do it "should create a new builder with the given action" do
builder = Rscons::Builders::SimpleBuilder.new(:Foo) { 0x1234 } builder = Rscons::Builders::SimpleBuilder.new { 0x1234 }
expect(builder.name).to eq("Foo")
expect(builder.run(1,2,3,4,5)).to eq(0x1234)
end
it "should create a new builder with the given name (as a string) and action" do
builder = Rscons::Builders::SimpleBuilder.new("Foo") { 0x1234 }
expect(builder.name).to eq("Foo")
expect(builder.run(1,2,3,4,5)).to eq(0x1234) expect(builder.run(1,2,3,4,5)).to eq(0x1234)
end end
end end

View File

@ -4,7 +4,7 @@ module Rscons
it "adds the default builders when they are not excluded" do it "adds the default builders when they are not excluded" do
env = Environment.new env = Environment.new
expect(env.builders.size).to be > 0 expect(env.builders.size).to be > 0
expect(env.builders.map {|name, builder| builder.is_a?(Builder)}.all?).to be_truthy expect(env.builders.map {|name, builder| builder.is_a?(Class)}.all?).to be_truthy
expect(env.builders.find {|name, builder| name == "Object"}).to_not be_nil expect(env.builders.find {|name, builder| name == "Object"}).to_not be_nil
expect(env.builders.find {|name, builder| name == "Program"}).to_not be_nil expect(env.builders.find {|name, builder| name == "Program"}).to_not be_nil
expect(env.builders.find {|name, builder| name == "Library"}).to_not be_nil expect(env.builders.find {|name, builder| name == "Library"}).to_not be_nil
@ -52,7 +52,7 @@ module Rscons
it "adds the builder to the list of builders" do it "adds the builder to the list of builders" do
env = Environment.new(exclude_builders: true) env = Environment.new(exclude_builders: true)
expect(env.builders.keys).to eq [] expect(env.builders.keys).to eq []
env.add_builder(Rscons::Builders::Object.new) env.add_builder(Rscons::Builders::Object)
expect(env.builders.keys).to eq ["Object"] expect(env.builders.keys).to eq ["Object"]
end end