Compare commits

..
13 Commits
7 changed files with 235 additions and 36 deletions

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
rscons (1.6.1)
rscons (1.7.0)
json (~> 1.0)
GEM
+23
View File
@@ -173,6 +173,9 @@ Each build hook block will be invoked for every build operation, so the block
should test the target or sources if its action should only apply to some
subset of build targets or source files.
Build hooks can alter construction variable values for a particular build
operation. Build hooks can also register new build targets.
The `build_op` parameter to the build hook block is a Hash describing the
build operation with the following keys:
* `:builder` - `Builder` instance in use
@@ -317,6 +320,19 @@ cloned_env["CPPPATH"] << "three"
`base_env["CPPPATH"]` will not include "three".
#### Build Hooks
Environments can have build hooks which are added with `env.add_build_hook()`.
Build hooks are invoked immediately before a builder executes.
Build hooks can modify the construction variables in use for the build
operation.
They can also register new build targets.
Environments can also have post-build hooks added with `env.add_post_build_hook()`.
Post-build hooks are only invoked if the build operation was a success.
Post-build hooks can invoke commands using the newly-built files, or register
new build targets.
### Construction Variable Naming
* uppercase strings - the default construction variables that Rscons uses
@@ -329,6 +345,13 @@ http://rubydoc.info/github/holtrop/rscons/frames.
## Release Notes
### v1.7.0
- allow build hooks to register new build targets
- add post-build hooks (register with Environment#add_post_build_hook)
- clear all build targets after processing an Environment
- allow trailing slashes in arguments to Environment#build_dir
### v1.6.1
- add DEPFILESUFFIX construction variable to override dependency file suffix
+4 -8
View File
@@ -8,6 +8,10 @@ end
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "yard"
require "rake/clean"
CLEAN.include %w[build_test_run .yardoc doc coverage]
CLOBBER.include %w[pkg]
RSpec::Core::RakeTask.new(:spec)
@@ -16,11 +20,3 @@ YARD::Rake::YardocTask.new do |yard|
end
task :default => :spec
task :clean do
FileUtils.rm_rf(["build_test_run", "doc", "coverage"])
end
task :clobber => :clean do
FileUtils.rm_rf(["pkg"])
end
+57 -15
View File
@@ -7,7 +7,7 @@ module Rscons
# contains a collection of construction variables, options, builders, and
# rules for building targets.
class Environment
# @return [Hash] Set of \{"builder_name" => builder_object} pairs.
# @return [Hash] Set of !{"builder_name" => builder_object} pairs.
attr_reader :builders
# :command, :short, or :off
@@ -42,7 +42,7 @@ module Rscons
@user_deps = {}
@builders = {}
@build_dirs = []
@build_hooks = []
@build_hooks = {pre: [], post: []}
unless options[:exclude_builders]
DEFAULT_BUILDERS.each do |builder_class_name|
builder_class = Builders.const_get(builder_class_name)
@@ -103,9 +103,12 @@ module Rscons
end
end
if clone.include?(:build_hooks)
@build_hooks.each do |build_hook_block|
@build_hooks[:pre].each do |build_hook_block|
env.add_build_hook(&build_hook_block)
end
@build_hooks[:post].each do |build_hook_block|
env.add_post_build_hook(&build_hook_block)
end
end
if block_given?
@@ -132,6 +135,12 @@ module Rscons
# Add a build hook to the Environment.
#
# Build hooks are Ruby blocks which are invoked immediately before a
# build operation takes place. Build hooks have an opportunity to modify
# the construction variables in use for the build operation based on the
# builder in use, target file name, or sources. Build hooks can also
# register new build targets.
#
# @yield [build_op]
# Invoke the given block with the current build operation.
# @yieldparam build_op [Hash]
@@ -144,19 +153,45 @@ module Rscons
#
# @return [void]
def add_build_hook(&block)
@build_hooks << block
@build_hooks[:pre] << block
end
# Add a post build hook to the Environment.
#
# Post-build hooks are Ruby blocks which are invoked immediately after a
# build operation takes place. Post-build hooks are only invoked if the
# build operation succeeded. Post-build hooks can register new build
# targets.
#
# @since 1.7.0
#
# @yield [build_op]
# Invoke the given block with the current build operation.
# @yieldparam build_op [Hash]
# Hash with keys:
# - :builder - The builder object in use.
# - :target - Target file name.
# - :sources - List of source file(s).
# - :vars - Set of construction variable values in use.
# - :env - The Environment invoking the builder.
#
# @return [void]
def add_post_build_hook(&block)
@build_hooks[:post] << block
end
# Specify a build directory for this Environment.
#
# Source files from src_dir will produce object files under obj_dir.
#
# @param src_dir [String] Path to the source directory.
# @param src_dir [String, Regexp] Path to the source directory.
# @param obj_dir [String] Path to the object directory.
#
# @return [void]
def build_dir(src_dir, obj_dir)
src_dir = src_dir.gsub('\\', '/') if src_dir.is_a?(String)
if src_dir.is_a?(String)
src_dir = src_dir.gsub("\\", "/").sub(%r{/*$}, "")
end
@build_dirs << [src_dir, obj_dir]
end
@@ -216,23 +251,25 @@ module Rscons
#
# @return [void]
def process
unless @targets.empty?
expand_paths!
while @targets.size > 0
targets = @targets
@targets = {}
cache = Cache.instance
cache.clear_checksum_cache!
targets_processed = {}
process_target = proc do |target|
targets_processed[target] ||= begin
@targets[target][:sources].each do |src|
if @targets.include?(src) and not targets_processed.include?(src)
targets[target][:sources].each do |src|
if targets.include?(src) and not targets_processed.include?(src)
process_target.call(src)
end
end
result = run_builder(@targets[target][:builder],
result = run_builder(targets[target][:builder],
target,
@targets[target][:sources],
targets[target][:sources],
cache,
@targets[target][:vars] || {})
targets[target][:vars] || {})
unless result
raise BuildError.new("Failed to build #{target}")
end
@@ -240,7 +277,7 @@ module Rscons
end
end
begin
@targets.each do |target, target_params|
targets.each do |target, target_params|
process_target.call(target)
end
ensure
@@ -410,7 +447,8 @@ module Rscons
# @return [String,false] Return value from the {Builder}'s +run+ method.
def run_builder(builder, target, sources, cache, vars)
vars = @varset.merge(vars)
@build_hooks.each do |build_hook_block|
call_build_hooks = lambda do |sec|
@build_hooks[sec].each do |build_hook_block|
build_operation = {
builder: builder,
target: target,
@@ -420,7 +458,11 @@ module Rscons
}
build_hook_block.call(build_operation)
end
builder.run(target, sources, cache, self, vars)
end
call_build_hooks[:pre]
rv = builder.run(target, sources, cache, self, vars)
call_build_hooks[:post] if rv
rv
end
# Expand a path to be relative to the Environment's build root.
+2 -2
View File
@@ -83,7 +83,7 @@ module Rscons
end
alias_method :clone, :merge
# Replace "$\{var}" variable references in varref with the expanded
# Replace "$!{var}" variable references in varref with the expanded
# variables' values, recursively.
#
# @param varref [nil, String, Array, Proc]
@@ -92,7 +92,7 @@ module Rscons
# Arguments to pass to any lambda variable values to be expanded.
#
# @return [nil, String, Array]
# Expanded value with "$\{var}" variable references replaced.
# Expanded value with "$!{var}" variable references replaced.
def expand_varref(varref, lambda_args)
if varref.is_a?(String)
if varref =~ /^(.*)\$\{([^}]+)\}(.*)$/
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons
# gem version
VERSION = "1.6.1"
VERSION = "1.7.0"
end
+147 -9
View File
@@ -10,6 +10,7 @@ class Dir
end
describe Rscons do
BUILD_TEST_RUN_DIR = "build_test_run"
def rm_rf(dir)
@@ -120,34 +121,43 @@ describe Rscons do
it 'rebuilds a C module when a header it depends on changes' do
test_dir('header')
env = Rscons::Environment.new do |env|
make_env = lambda do
Rscons::Environment.new do |env|
env.Program('header', Dir['*.c'])
end
end
make_env[]
expect(`./header`).to eq "The value is 2\n"
file_sub('header.h') {|line| line.sub(/2/, '5')}
env.process
make_env[]
expect(`./header`).to eq "The value is 5\n"
end
it 'does not rebuild a C module when its dependencies have not changed' do
test_dir('header')
env = Rscons::Environment.new do |env|
make_env = lambda do
Rscons::Environment.new do |env|
env.Program('header', Dir['*.c'])
end
end
env = make_env[]
expect(`./header`).to eq "The value is 2\n"
expect(lines).to eq [
'CC header.o',
"LD header#{env["PROGSUFFIX"]}",
]
env.process
make_env[]
expect(lines).to eq []
end
it "does not rebuild a C module when only the file's timestamp has changed" do
test_dir('header')
env = Rscons::Environment.new do |env|
make_env = lambda do
Rscons::Environment.new do |env|
env.Program('header', Dir['*.c'])
end
end
env = make_env[]
expect(`./header`).to eq "The value is 2\n"
expect(lines).to eq [
'CC header.o',
@@ -155,7 +165,7 @@ describe Rscons do
]
sleep 0.05
file_sub('header.c') {|line| line}
env.process
make_env[]
expect(lines).to eq []
end
@@ -187,6 +197,19 @@ describe Rscons do
expect(File.exists?('build_two/two.o')).to be_truthy
end
it "supports trailing slashes at the end of build_dir sources and destinations" do
test_dir("build_dir")
Rscons::Environment.new do |env|
env.append("CPPPATH" => Dir["src/**/*/"])
env.build_dir("src/one/", "build_one/")
env.build_dir("src/two", "build_two")
env.Program("build_dir", Dir["src/**/*.c"])
end
expect(`./build_dir`).to eq "Hello from two()\n"
expect(File.exists?("build_one/one.o")).to be_truthy
expect(File.exists?("build_two/two.o")).to be_truthy
end
it 'uses build directories before build root' do
test_dir('build_dir')
env = Rscons::Environment.new do |env|
@@ -303,11 +326,14 @@ EOF
end
end
env = Rscons::Environment.new do |env|
make_env = lambda do
Rscons::Environment.new do |env|
env.add_builder(CHGen.new)
env.CHGen("inc.c", ["program.c"])
env.Program("program", Dir["*.c"] + ["inc.c"])
env.Program("program", %w[program.c inc.c])
end
end
env = make_env[]
expect(lines).to eq ["CHGen inc.c", "CC program.o", "CC inc.o", "LD program#{env["PROGSUFFIX"]}"]
expect(File.exists?("inc.c")).to be_truthy
@@ -315,7 +341,7 @@ EOF
expect(`./program`).to eq "The value is 42\n"
File.open("inc.c", "w") {|fh| fh.puts "int THE_VALUE = 33;"}
env.process
make_env[]
expect(lines).to eq ["CHGen inc.c"]
expect(`./program`).to eq "The value is 42\n"
end
@@ -347,12 +373,16 @@ EOF
it 'allows cloning all attributes of an Environment object' do
test_dir('clone_env')
built_targets = []
env1 = Rscons::Environment.new(echo: :command) do |env|
env.build_dir('src', 'build')
env['CFLAGS'] = '-O2'
env.add_build_hook do |build_op|
build_op[:vars]['CPPFLAGS'] = '-DSTRING="Hello"'
end
env.add_post_build_hook do |build_op|
built_targets << build_op[:target]
end
env.Program('program', Dir['src/*.c'])
end
@@ -365,6 +395,12 @@ EOF
%Q{gcc -o program#{env1["PROGSUFFIX"]} build/program.o},
%Q{gcc -o program2#{env2["PROGSUFFIX"]} build/program.o},
]
expect(built_targets).to eq([
"build/program.o",
"program#{env1['PROGSUFFIX']}",
"build/program.o",
"program2#{env1['PROGSUFFIX']}",
])
end
it 'builds a C++ program with one source file' do
@@ -562,4 +598,106 @@ EOF
expect(e2.expand_varref("${computed}")).to eq("-H38xyz")
expect(env.expand_varref("${lambda_recurse}")).to eq("-Hello")
end
it "supports registering build targets from within a build hook" do
test_dir("simple")
Rscons::Environment.new do |env|
env.Program("simple", Dir["*.c"])
env.add_build_hook do |build_op|
if build_op[:target].end_with?(".o")
env.Disassemble("#{build_op[:target]}.txt", build_op[:target])
end
end
end
expect(File.exists?("simple.o")).to be_truthy
expect(File.exists?("simple.o.txt")).to be_truthy
expect(`./simple`).to eq "This is a simple C program\n"
end
it "supports post-build hooks" do
test_dir("simple")
built_targets = []
env = Rscons::Environment.new do |env|
env.Program("simple", Dir["*.c"])
env.add_post_build_hook do |build_op|
built_targets << build_op[:target]
expect(File.exists?(build_op[:target])).to be_truthy
end
end
expect(File.exists?("simple.o")).to be_truthy
expect(`./simple`).to eq "This is a simple C program\n"
expect(built_targets).to eq ["simple.o", "simple#{env["PROGSUFFIX"]}"]
end
it "supports multiple values for CXXSUFFIX" do
test_dir("simple_cc")
File.open("other.cccc", "w") {|fh| fh.puts}
Rscons::Environment.new do |env|
env["CXXSUFFIX"] = %w[.cccc .cc]
env["CXXFLAGS"] += %w[-x c++]
env.Program("simple", Dir["*.cc"] + ["other.cccc"])
end
expect(File.exists?("simple.o")).to be_truthy
expect(File.exists?("other.o")).to be_truthy
expect(`./simple`).to eq "This is a simple C++ program\n"
end
it "supports multiple values for CSUFFIX" do
test_dir("build_dir")
FileUtils.mv("src/one/one.c", "src/one/one.yargh")
Rscons::Environment.new do |env|
env["CSUFFIX"] = %w[.yargh .c]
env["CFLAGS"] += %w[-x c]
env["CPPPATH"] += Dir["src/**/"]
env.Program("build_dir", Dir["src/**/*.{c,yargh}"])
end
expect(File.exists?("src/one/one.o")).to be_truthy
expect(File.exists?("src/two/two.o")).to be_truthy
expect(`./build_dir`).to eq "Hello from two()\n"
end
it "supports multiple values for OBJSUFFIX" do
test_dir("two_sources")
env = Rscons::Environment.new() do |env|
env["OBJSUFFIX"] = %w[.oooo .ooo]
env.Object("one.oooo", "one.c", "CPPFLAGS" => ["-DONE"])
env.Object("two.ooo", "two.c")
env.Program("two_sources", %w[one.oooo two.ooo])
end
expect(File.exists?("two_sources#{env["PROGSUFFIX"]}")).to be_truthy
expect(`./two_sources`).to eq "This is a C program with two sources.\n"
end
it "supports multiple values for LIBSUFFIX" do
test_dir("two_sources")
env = Rscons::Environment.new() do |env|
env["LIBSUFFIX"] = %w[.aaaa .aaa]
env.Library("one.aaaa", "one.c", "CPPFLAGS" => ["-DONE"])
env.Library("two.aaa", "two.c")
env.Program("two_sources", %w[one.aaaa two.aaa])
end
expect(File.exists?("two_sources#{env["PROGSUFFIX"]}")).to be_truthy
expect(`./two_sources`).to eq "This is a C program with two sources.\n"
end
it "supports multiple values for ASSUFFIX" do
test_dir("two_sources")
env = Rscons::Environment.new() do |env|
env["ASSUFFIX"] = %w[.ssss .sss]
env["CFLAGS"] += %w[-S]
env.Object("one.ssss", "one.c", "CPPFLAGS" => ["-DONE"])
env.Object("two.sss", "two.c")
env.Program("two_sources", %w[one.ssss two.sss], "ASFLAGS" => env["ASFLAGS"] + %w[-x assembler])
end
expect(lines).to eq([
"CC one.ssss",
"CC two.sss",
"AS one.o",
"AS two.o",
"LD two_sources#{env["PROGSUFFIX"]}",
])
expect(File.exists?("two_sources#{env["PROGSUFFIX"]}")).to be_truthy
expect(`./two_sources`).to eq "This is a C program with two sources.\n"
end
end