Compare commits

..
8 Commits
8 changed files with 194 additions and 69 deletions

No files matched your search

+34
View File
@@ -110,6 +110,29 @@ Rscons::Environment.new do |env|
end end
``` ```
### Example: Custom Builder That Generates Multiple Output Files
```ruby
class CModuleGenerator < Rscons::Builder
def run(target, sources, cache, env, vars)
c_fname = target
h_fname = target.sub(/\.c$/, ".h")
cmd = ["generate_c_and_h", sources.first, c_fname, h_fname]
unless cache.up_to_date?([c_fname, h_fname], cmd, sources, env)
cache.mkdir_p(File.dirname(target))
system(cmd)
cache.register_build([c_fname, h_fname], cmd, sources, env)
end
target
end
end
Rscons::Environment.new do |env|
env.add_builder(CModuleGenerator.new)
env.CModuleGenerator("build/foo.c", "foo_gen.cfg")
end
```
### Example: Custom Builder Using Builder#standard_build() ### Example: Custom Builder Using Builder#standard_build()
The `standard_build` method from the `Rscons::Builder` base class can be used The `standard_build` method from the `Rscons::Builder` base class can be used
@@ -218,6 +241,17 @@ cloned_env["CPPPATH"] << "three"
Documentation for the complete Rscons API can be found at Documentation for the complete Rscons API can be found at
http://rubydoc.info/github/holtrop/rscons/frames. http://rubydoc.info/github/holtrop/rscons/frames.
## Release Notes
### v1.2.0
- add :clone option to Environment#clone to control exactly which Environment attributes are cloned
- allow nil to be passed in to Environment#build_root=
### v1.1.0
- Change Cache#up_to_date?() and #register_build() to accept a single target
file or an array of target file names
## Contributing ## Contributing
1. Fork it 1. Fork it
+1 -1
View File
@@ -1,6 +1,6 @@
module Rscons module Rscons
module Builders module Builders
# A default RScons builder that produces a static library archive. # A default Rscons builder that produces a static library archive.
class Rscons::Builders::Library < Rscons::Builder class Rscons::Builders::Library < Rscons::Builder
def default_variables(env) def default_variables(env)
{ {
+1 -1
View File
@@ -1,6 +1,6 @@
module Rscons module Rscons
module Builders module Builders
# A default RScons builder which knows how to produce an object file from # A default Rscons builder which knows how to produce an object file from
# various types of source files. # various types of source files.
class Object < Builder class Object < Builder
KNOWN_SUFFIXES = { KNOWN_SUFFIXES = {
+1 -1
View File
@@ -1,6 +1,6 @@
module Rscons module Rscons
module Builders module Builders
# A default RScons builder that knows how to link object files into an # A default Rscons builder that knows how to link object files into an
# executable program. # executable program.
class Rscons::Builders::Program < Rscons::Builder class Rscons::Builders::Program < Rscons::Builder
def default_variables(env) def default_variables(env)
+55 -50
View File
@@ -85,16 +85,17 @@ module Rscons
end end
end end
# Check if a target is up to date # Check if target(s) are up to date
# @param target [String] The name of the target file. # @param targets [String, Array] The name(s) of the target file(s).
# @param command [Array] The command used to build the target. # @param command [String, Array] The command used to build the target.
# @param deps [Array] List of the target's dependency files. # @param deps [Array] List of the target's dependency files.
# @param env [Environment] The Rscons::Environment. # @param env [Environment] The Rscons::Environment.
# @param options [Hash] Optional options. Can contain the following keys: # @param options [Hash] Optional options. Can contain the following keys:
# :strict_deps:: # :strict_deps::
# Only consider a target up to date if its list of dependencies is # Only consider a target up to date if its list of dependencies is
# exactly equal (including order) to the cached list of dependencies # exactly equal (including order) to the cached list of dependencies
# @return true value if the target is up to date, meaning that: # @return true if the targets are all up to date, meaning that,
# for each target:
# - the target exists on disk # - the target exists on disk
# - the cache has information for the target # - the cache has information for the target
# - the target's checksum matches its checksum when it was last built # - the target's checksum matches its checksum when it was last built
@@ -104,65 +105,69 @@ module Rscons
# exactly equal to those cached # exactly equal to those cached
# - each cached dependency file's current checksum matches the checksum # - each cached dependency file's current checksum matches the checksum
# stored in the cache file # stored in the cache file
def up_to_date?(target, command, deps, env, options = {}) def up_to_date?(targets, command, deps, env, options = {})
# target file must exist on disk Array(targets).each do |target|
return false unless File.exists?(target) # target file must exist on disk
return false unless File.exists?(target)
# target must be registered in the cache # target must be registered in the cache
return false unless @cache[:targets].has_key?(target) return false unless @cache[:targets].has_key?(target)
# target must have the same checksum as when it was built last # target must have the same checksum as when it was built last
return false unless @cache[:targets][target][:checksum] == lookup_checksum(target) return false unless @cache[:targets][target][:checksum] == lookup_checksum(target)
# command used to build target must be identical # command used to build target must be identical
return false unless @cache[:targets][target][:command] == command return false unless @cache[:targets][target][:command] == command
cached_deps = @cache[:targets][target][:deps] || [] cached_deps = @cache[:targets][target][:deps] || []
cached_deps_fnames = cached_deps.map { |dc| dc[:fname] } cached_deps_fnames = cached_deps.map { |dc| dc[:fname] }
if options[:strict_deps] if options[:strict_deps]
# depedencies passed in must exactly equal those in the cache # depedencies passed in must exactly equal those in the cache
return false unless deps == cached_deps_fnames return false unless deps == cached_deps_fnames
else else
# all dependencies passed in must exist in cache (but cache may have more) # all dependencies passed in must exist in cache (but cache may have more)
return false unless (Set.new(deps) - Set.new(cached_deps_fnames)).empty? return false unless (Set.new(deps) - Set.new(cached_deps_fnames)).empty?
end end
# set of user dependencies must match # set of user dependencies must match
user_deps = env.get_user_deps(target) || [] user_deps = env.get_user_deps(target) || []
cached_user_deps = @cache[:targets][target][:user_deps] || [] cached_user_deps = @cache[:targets][target][:user_deps] || []
cached_user_deps_fnames = cached_user_deps.map { |dc| dc[:fname] } cached_user_deps_fnames = cached_user_deps.map { |dc| dc[:fname] }
return false unless user_deps == cached_user_deps_fnames return false unless user_deps == cached_user_deps_fnames
# all cached dependencies must have their checksums match # all cached dependencies must have their checksums match
(cached_deps + cached_user_deps).each do |dep_cache| (cached_deps + cached_user_deps).each do |dep_cache|
return false unless dep_cache[:checksum] == lookup_checksum(dep_cache[:fname]) return false unless dep_cache[:checksum] == lookup_checksum(dep_cache[:fname])
end
end end
true true
end end
# Store cache information about a target built by a builder # Store cache information about target(s) built by a builder
# @param target [String] The name of the target. # @param targets [String, Array] The name of the target(s) built.
# @param command [Array] The command used to build the target. # @param command [String, Array] The command used to build the target.
# @param deps [Array] List of dependencies for the target. # @param deps [Array] List of dependencies for the target.
# @param env [Environment] The Rscons::Environment. # @param env [Environment] The Rscons::Environment.
def register_build(target, command, deps, env) def register_build(targets, command, deps, env)
@cache[:targets][target.encode(__ENCODING__)] = { Array(targets).each do |target|
command: command, @cache[:targets][target.encode(__ENCODING__)] = {
checksum: calculate_checksum(target), command: command,
deps: deps.map do |dep| checksum: calculate_checksum(target),
{ deps: deps.map do |dep|
fname: dep.encode(__ENCODING__), {
checksum: lookup_checksum(dep), fname: dep.encode(__ENCODING__),
} checksum: lookup_checksum(dep),
end, }
user_deps: (env.get_user_deps(target) || []).map do |dep| end,
{ user_deps: (env.get_user_deps(target) || []).map do |dep|
fname: dep.encode(__ENCODING__), {
checksum: lookup_checksum(dep), fname: dep.encode(__ENCODING__),
} checksum: lookup_checksum(dep),
end, }
} end,
}
end
end end
# Return a list of targets that have been built # Return a list of targets that have been built
+43 -13
View File
@@ -2,7 +2,7 @@ require 'set'
require 'fileutils' require 'fileutils'
module Rscons module Rscons
# The Environment class is the main programmatic interface to RScons. It # The Environment class is the main programmatic interface to Rscons. It
# contains a collection of construction variables, options, builders, and # contains a collection of construction variables, options, builders, and
# rules for building targets. # rules for building targets.
class Environment class Environment
@@ -15,7 +15,8 @@ module Rscons
# String or +nil+ # String or +nil+
attr_reader :build_root attr_reader :build_root
def build_root=(build_root) def build_root=(build_root)
@build_root = build_root.gsub('\\', '/') @build_root = build_root
@build_root.gsub!('\\', '/') if @build_root
end end
# Create an Environment object. # Create an Environment object.
@@ -50,24 +51,53 @@ module Rscons
end end
# Make a copy of the Environment object. # Make a copy of the Environment object.
# The cloned environment will contain a copy of all environment options, #
# construction variables, and builders (unless :exclude_builders => true is # By default, a cloned environment will contain a copy of all environment
# passed as an option). It will not contain a copy of the targets, build # options, construction variables, and builders, but not a copy of the
# hooks, build directories, or the build root. If a block is given, the # targets, build hooks, build directories, or the build root.
# Environment object is yielded to the block and when the block returns, #
# the {#process} method is automatically called. The possible options keys # Exactly which items are cloned are controllable via the optional :clone
# match those documented in the #initialize method. # parameter, which can be :none, :all, or a set or array of any of the
# following:
# - :variables to clone construction variables (on by default)
# - :builders to clone the builders (on by default)
# - :build_root to clone the build root (off by default)
# - :build_dirs to clone the build directories (off by default)
# - :build_hooks to clone the build hooks (off by default)
#
# If a block is given, the Environment object is yielded to the block and
# when the block returns, the {#process} method is automatically called.
#
# Any options that #initialize receives can also be specified here.
#
# @return a new {Environment} object.
def clone(options = {}) def clone(options = {})
clone = options[:clone] || Set[:variables, :builders]
clone = Set[:variables, :builders, :build_root, :build_dirs, :build_hooks] if clone == :all
clone = Set[] if clone == :none
clone = Set.new(clone) if clone.is_a?(Array)
clone.delete(:builders) if options[:exclude_builders]
env = self.class.new( env = self.class.new(
echo: options[:echo] || @echo, echo: options[:echo] || @echo,
build_root: options[:build_root], build_root: options[:build_root],
exclude_builders: true) exclude_builders: true)
unless options[:exclude_builders] if clone.include?(:builders)
@builders.each do |builder_name, builder| @builders.each do |builder_name, builder|
env.add_builder(builder) env.add_builder(builder)
end end
end end
env.append(@varset.clone) env.append(@varset.clone) if clone.include?(:variables)
env.build_root = @build_root if clone.include?(:build_root)
if clone.include?(:build_dirs)
@build_dirs.each do |src_dir, obj_dir|
env.build_dir(src_dir, obj_dir)
end
end
if clone.include?(:build_hooks)
@build_hooks.each do |build_hook_block|
env.add_build_hook(&build_hook_block)
end
end
if block_given? if block_given?
yield env yield env
@@ -246,7 +276,7 @@ module Rscons
# Build a list of source files into files containing one of the suffixes # Build a list of source files into files containing one of the suffixes
# given by suffixes. # given by suffixes.
# This method is used internally by RScons builders. # This method is used internally by Rscons builders.
# @param sources [Array] List of source files to build. # @param sources [Array] List of source files to build.
# @param suffixes [Array] List of suffixes to try to convert source files into. # @param suffixes [Array] List of suffixes to try to convert source files into.
# @param cache [Cache] The Cache. # @param cache [Cache] The Cache.
@@ -294,7 +324,7 @@ module Rscons
end end
# Parse dependencies for a given target from a Makefile. # Parse dependencies for a given target from a Makefile.
# This method is used internally by RScons builders. # This method is used internally by Rscons builders.
# @param mf_fname [String] File name of the Makefile to read. # @param mf_fname [String] File name of the Makefile to read.
# @param target [String] Name of the target to gather dependencies for. # @param target [String] Name of the target to gather dependencies for.
def self.parse_makefile_deps(mf_fname, target) def self.parse_makefile_deps(mf_fname, target)
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons module Rscons
# gem version # gem version
VERSION = "1.0.0" VERSION = "1.2.0"
end end
+58 -2
View File
@@ -218,9 +218,9 @@ describe Rscons do
end end
it 'allows Ruby classes as custom builders to be used to construct files' do it 'allows Ruby classes as custom builders to be used to construct files' do
test_dir('custom_builder') test_dir('custom_builder')
class MySource < Rscons::Builder class MySource < Rscons::Builder
def run(target, sources, user_deps, cache, env, vars = {}) def run(target, sources, cache, env, vars)
File.open(target, 'w') do |fh| File.open(target, 'w') do |fh|
fh.puts <<EOF fh.puts <<EOF
#define THE_VALUE 5678 #define THE_VALUE 5678
@@ -241,6 +241,39 @@ EOF
`./program`.should == "The value is 5678\n" `./program`.should == "The value is 5678\n"
end end
it 'supports custom builders with multiple targets' do
test_dir('custom_builder')
class CHGen < Rscons::Builder
def run(target, sources, cache, env, vars)
c_fname = target
h_fname = target.sub(/\.c$/, ".h")
unless cache.up_to_date?([c_fname, h_fname], "", sources, env)
puts "CHGen #{c_fname}"
File.open(c_fname, "w") {|fh| fh.puts "int THE_VALUE = 42;"}
File.open(h_fname, "w") {|fh| fh.puts "extern int THE_VALUE;"}
cache.register_build([c_fname, h_fname], "", sources, env)
end
target
end
end
env = Rscons::Environment.new do |env|
env.add_builder(CHGen.new)
env.CHGen("inc.c", ["program.c"])
env.Program("program", Dir["*.c"] + ["inc.c"])
end
lines.should == ["CHGen inc.c", "CC program.o", "CC inc.o", "LD program"]
File.exists?("inc.c").should be_true
File.exists?("inc.h").should be_true
`./program`.should == "The value is 42\n"
File.open("inc.c", "w") {|fh| fh.puts "int THE_VALUE = 33;"}
env.process
lines.should == ["CHGen inc.c"]
`./program`.should == "The value is 42\n"
end
it 'allows cloning Environment objects' do it 'allows cloning Environment objects' do
test_dir('clone_env') test_dir('clone_env')
@@ -265,6 +298,29 @@ EOF
] ]
end end
it 'allows cloning all attributes of an Environment object' do
test_dir('clone_env')
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.Program('program', Dir['src/*.c'])
end
env2 = env1.clone(clone: :all) do |env|
env.Program('program2', Dir['src/*.c'])
end
lines.should == [
%q{gcc -c -o build/program.o -MMD -MF build/program.mf -DSTRING="Hello" -O2 src/program.c},
%q{gcc -o program build/program.o},
%q{gcc -o program2 build/program.o},
]
end
it 'builds a C++ program with one source file' do it 'builds a C++ program with one source file' do
test_dir('simple_cc') test_dir('simple_cc')
Rscons::Environment.new do |env| Rscons::Environment.new do |env|