Compare commits

..
18 Commits
Author SHA1 Message Date
josh 7ff2ec1f1a v0.3.0 2013-12-31 14:10:47 -05:00
josh 5362f761e6 move default builder classes into Rscons::Builders namespace module 2013-12-31 13:31:17 -05:00
josh cdb3352b4e remove module monkey-patching; add Builder#name() 2013-12-31 13:10:00 -05:00
josh 08bc390d40 Rscons::BuildError inherits from RuntimeError instead of Exception 2013-12-31 13:08:38 -05:00
josh f8af0630b0 pass env to Cache#up_to_date?() and #register_build() instead of user_deps 2013-12-31 12:46:30 -05:00
josh 1868193b54 do not prepend build_root to source files that are already inside build_root 2013-12-31 10:15:19 -05:00
josh 8976d7dea2 update README custom builder examples 2013-12-27 15:01:02 -05:00
josh 295324eafd add Environment#depends() to specify user dependencies for a build target 2013-12-27 14:31:46 -05:00
josh 7330e74ab8 modify Cache to store user-specified extra dependencies 2013-12-27 14:20:32 -05:00
josh a28cef9eb7 change "vars" parameters to be required 2013-12-27 13:53:40 -05:00
josh e667046da8 add Environment#clear_targets() 2013-12-27 13:35:16 -05:00
josh 539a49fe90 do not allow construction variables as arguments to Environment#initialize() or Environment#clone() 2013-12-27 12:10:54 -05:00
josh 7681c7584f add String#absolute_path? 2013-12-27 11:40:24 -05:00
josh 8fcbd39057 v0.2.2 2013-11-13 23:52:33 -05:00
josh d0bbc78751 Program: do not continue executing builder if building a source fails 2013-11-13 23:52:15 -05:00
josh 7249e57951 add info to error message when expanding unknown variable types 2013-11-13 23:50:46 -05:00
josh d01913641f v0.2.1 2013-11-11 11:36:10 -05:00
josh d5be569cd5 fix Environment#clone() to allow subclassing 2013-11-11 11:35:59 -05:00
16 changed files with 443 additions and 227 deletions

No files matched your search

+36 -1
View File
@@ -66,7 +66,8 @@ end
```ruby
class GenerateFoo < Rscons::Builder
def run(target, sources, cache, env, vars)
def run(target, sources, user_deps, cache, env, vars)
cache.mkdir_p(File.dirname(target))
File.open(target, "w") do |fh|
fh.puts <<EOF
#define GENERATED 42
@@ -81,6 +82,40 @@ Rscons::Environment.new do |env|
end
```
### Example: Custom Builder That Only Regenerates When Necessary
```ruby
class CmdBuilder < Rscons::Builder
def run(target, sources, user_deps, cache, env, vars)
cmd = ["cmd", "-i", sources.first, "-o", target]
unless cache.up_to_date?(target, cmd, sources, user_deps)
cache.mkdir_p(File.dirname(target))
system(cmd)
cache.register_build(target, cmd, sources, user_deps)
end
end
end
Rscons::Environment.new do |env|
env.CmdBuilder("foo.gen", "foo_gen.cfg")
end
```
### Example: Custom Builder Using Builder#standard_build()
```ruby
class CmdBuilder < Rscons::Builder
def run(target, sources, user_deps, cache, env, vars)
cmd = ["cmd", "-i", sources.first, "-o", target]
standard_build("CmdBld #{target}", target, cmd, sources, user_deps, env, cache)
end
end
Rscons::Environment.new do |env|
env.CmdBuilder("foo.gen", "foo_gen.cfg")
end
```
### Example: Using different compilation flags for some sources
```ruby
+4 -6
View File
@@ -4,7 +4,6 @@ require "rscons/environment"
require "rscons/varset"
require "rscons/version"
require "rscons/monkey/module"
require "rscons/monkey/string"
# default builders
@@ -15,13 +14,12 @@ require "rscons/builders/program"
# Namespace module for rscons classes
module Rscons
DEFAULT_BUILDERS = [
Library,
Object,
Program,
:Library,
:Object,
:Program,
]
class BuildError < Exception
end
class BuildError < RuntimeError; end
# Remove all generated files
def self.clean
+11 -2
View File
@@ -1,8 +1,17 @@
require "fileutils"
module Rscons
# Namespace module in which to store builders for convenient grouping
module Builders; end
# Class to hold an object that knows how to build a certain type of file.
class Builder
# Return the name of the builder.
# If not overridden this defaults to the last component of the class name.
def name
self.class.name.split(":").last
end
# Return a set of default variable values for the Environment to use
# unless the user overrides any.
# @param env [Environment] The Environment.
@@ -23,11 +32,11 @@ module Rscons
# build command.
# Return the name of the target or false on failure.
def standard_build(short_cmd_string, target, command, sources, env, cache)
unless cache.up_to_date?(target, command, sources)
unless cache.up_to_date?(target, command, sources, env)
cache.mkdir_p(File.dirname(target))
FileUtils.rm_f(target)
return false unless env.execute(short_cmd_string, command)
cache.register_build(target, command, sources)
cache.register_build(target, command, sources, env)
end
target
end
+22 -22
View File
@@ -1,27 +1,27 @@
require 'fileutils'
module Rscons
# A default RScons builder that produces a static library archive.
class Library < Builder
def default_variables(env)
{
'AR' => 'ar',
'LIBSUFFIX' => '.a',
'ARFLAGS' => [],
'ARCMD' => ['${AR}', 'rcs', '${ARFLAGS}', '${_TARGET}', '${_SOURCES}']
}
end
module Builders
# A default RScons builder that produces a static library archive.
class Rscons::Builders::Library < Rscons::Builder
def default_variables(env)
{
'AR' => 'ar',
'LIBSUFFIX' => '.a',
'ARFLAGS' => [],
'ARCMD' => ['${AR}', 'rcs', '${ARFLAGS}', '${_TARGET}', '${_SOURCES}']
}
end
def run(target, sources, cache, env, vars = {})
# build sources to linkable objects
objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars)
if objects
vars = vars.merge({
'_TARGET' => target,
'_SOURCES' => objects,
})
command = env.build_command(env['ARCMD'], vars)
standard_build("AR #{target}", target, command, objects, env, cache)
def run(target, sources, cache, env, vars)
# build sources to linkable objects
objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars)
if objects
vars = vars.merge({
'_TARGET' => target,
'_SOURCES' => objects,
})
command = env.build_command(env['ARCMD'], vars)
standard_build("AR #{target}", target, command, objects, env, cache)
end
end
end
end
+71 -69
View File
@@ -1,79 +1,81 @@
module Rscons
# A default RScons builder which knows how to produce an object file from
# various types of source files.
class Object < Builder
KNOWN_SUFFIXES = {
"AS" => "ASSUFFIX",
"CC" => "CSUFFIX",
"CXX" => "CXXSUFFIX",
"DC" => "DSUFFIX",
}
def default_variables(env)
{
'OBJSUFFIX' => '.o',
'AS' => '${CC}',
'ASFLAGS' => [],
'ASSUFFIX' => '.S',
'ASPPPATH' => '${CPPPATH}',
'ASPPFLAGS' => '${CPPFLAGS}',
'ASDEPGEN' => ['-MMD', '-MF', '${_DEPFILE}'],
'ASCMD' => ['${AS}', '-c', '-o', '${_TARGET}', '${ASDEPGEN}', '-I${ASPPPATH}', '${ASPPFLAGS}', '${ASFLAGS}', '${_SOURCES}'],
'CPPFLAGS' => [],
'CPPPATH' => [],
'CC' => 'gcc',
'CFLAGS' => [],
'CSUFFIX' => '.c',
'CCDEPGEN' => ['-MMD', '-MF', '${_DEPFILE}'],
'CCCMD' => ['${CC}', '-c', '-o', '${_TARGET}', '${CCDEPGEN}', '-I${CPPPATH}', '${CPPFLAGS}', '${CFLAGS}', '${_SOURCES}'],
'CXX' => 'g++',
'CXXFLAGS' => [],
'CXXSUFFIX' => '.cc',
'CXXDEPGEN' => ['-MMD', '-MF', '${_DEPFILE}'],
'CXXCMD' =>['${CXX}', '-c', '-o', '${_TARGET}', '${CXXDEPGEN}', '-I${CPPPATH}', '${CPPFLAGS}', '${CXXFLAGS}', '${_SOURCES}'],
'DC' => 'gdc',
'DFLAGS' => [],
'DSUFFIX' => '.d',
'D_IMPORT_PATH' => [],
'DCCMD' => ['${DC}', '-c', '-o', '${_TARGET}', '-I${D_IMPORT_PATH}', '${DFLAGS}', '${_SOURCES}'],
module Builders
# A default RScons builder which knows how to produce an object file from
# various types of source files.
class Object < Builder
KNOWN_SUFFIXES = {
"AS" => "ASSUFFIX",
"CC" => "CSUFFIX",
"CXX" => "CXXSUFFIX",
"DC" => "DSUFFIX",
}
end
def produces?(target, source, env)
target.has_suffix?(env['OBJSUFFIX']) and KNOWN_SUFFIXES.find do |compiler, suffix_var|
source.has_suffix?(env[suffix_var])
def default_variables(env)
{
'OBJSUFFIX' => '.o',
'AS' => '${CC}',
'ASFLAGS' => [],
'ASSUFFIX' => '.S',
'ASPPPATH' => '${CPPPATH}',
'ASPPFLAGS' => '${CPPFLAGS}',
'ASDEPGEN' => ['-MMD', '-MF', '${_DEPFILE}'],
'ASCMD' => ['${AS}', '-c', '-o', '${_TARGET}', '${ASDEPGEN}', '-I${ASPPPATH}', '${ASPPFLAGS}', '${ASFLAGS}', '${_SOURCES}'],
'CPPFLAGS' => [],
'CPPPATH' => [],
'CC' => 'gcc',
'CFLAGS' => [],
'CSUFFIX' => '.c',
'CCDEPGEN' => ['-MMD', '-MF', '${_DEPFILE}'],
'CCCMD' => ['${CC}', '-c', '-o', '${_TARGET}', '${CCDEPGEN}', '-I${CPPPATH}', '${CPPFLAGS}', '${CFLAGS}', '${_SOURCES}'],
'CXX' => 'g++',
'CXXFLAGS' => [],
'CXXSUFFIX' => '.cc',
'CXXDEPGEN' => ['-MMD', '-MF', '${_DEPFILE}'],
'CXXCMD' =>['${CXX}', '-c', '-o', '${_TARGET}', '${CXXDEPGEN}', '-I${CPPPATH}', '${CPPFLAGS}', '${CXXFLAGS}', '${_SOURCES}'],
'DC' => 'gdc',
'DFLAGS' => [],
'DSUFFIX' => '.d',
'D_IMPORT_PATH' => [],
'DCCMD' => ['${DC}', '-c', '-o', '${_TARGET}', '-I${D_IMPORT_PATH}', '${DFLAGS}', '${_SOURCES}'],
}
end
end
def run(target, sources, cache, env, vars = {})
vars = vars.merge({
'_TARGET' => target,
'_SOURCES' => sources,
'_DEPFILE' => target.set_suffix('.mf'),
})
com_prefix = KNOWN_SUFFIXES.find do |compiler, suffix_var|
sources.first.has_suffix?(env[suffix_var])
end.tap do |v|
v.nil? and raise "Error: unknown input file type: #{sources.first.inspect}"
end.first
command = env.build_command(env["#{com_prefix}CMD"], vars)
unless cache.up_to_date?(target, command, sources)
cache.mkdir_p(File.dirname(target))
FileUtils.rm_f(target)
return false unless env.execute("#{com_prefix} #{target}", command)
deps = sources
if File.exists?(vars['_DEPFILE'])
deps += Environment.parse_makefile_deps(vars['_DEPFILE'], target)
FileUtils.rm_f(vars['_DEPFILE'])
def produces?(target, source, env)
target.has_suffix?(env['OBJSUFFIX']) and KNOWN_SUFFIXES.find do |compiler, suffix_var|
source.has_suffix?(env[suffix_var])
end
cache.register_build(target, command, deps.uniq)
end
target
def run(target, sources, cache, env, vars)
vars = vars.merge({
'_TARGET' => target,
'_SOURCES' => sources,
'_DEPFILE' => target.set_suffix('.mf'),
})
com_prefix = KNOWN_SUFFIXES.find do |compiler, suffix_var|
sources.first.has_suffix?(env[suffix_var])
end.tap do |v|
v.nil? and raise "Error: unknown input file type: #{sources.first.inspect}"
end.first
command = env.build_command(env["#{com_prefix}CMD"], vars)
unless cache.up_to_date?(target, command, sources, env)
cache.mkdir_p(File.dirname(target))
FileUtils.rm_f(target)
return false unless env.execute("#{com_prefix} #{target}", command)
deps = sources
if File.exists?(vars['_DEPFILE'])
deps += Environment.parse_makefile_deps(vars['_DEPFILE'], target)
FileUtils.rm_f(vars['_DEPFILE'])
end
cache.register_build(target, command, deps.uniq, env)
end
target
end
end
end
end
+36 -33
View File
@@ -1,38 +1,41 @@
module Rscons
# A default RScons builder that knows how to link object files into an
# executable program.
class Program < Builder
def default_variables(env)
{
'LD' => nil,
'OBJSUFFIX' => '.o',
'LIBSUFFIX' => '.a',
'LDFLAGS' => [],
'LIBPATH' => [],
'LIBS' => [],
'LDCMD' => ['${LD}', '-o', '${_TARGET}', '${LDFLAGS}', '${_SOURCES}', '-L${LIBPATH}', '-l${LIBS}']
}
end
module Builders
# A default RScons builder that knows how to link object files into an
# executable program.
class Rscons::Builders::Program < Rscons::Builder
def default_variables(env)
{
'LD' => nil,
'OBJSUFFIX' => '.o',
'LIBSUFFIX' => '.a',
'LDFLAGS' => [],
'LIBPATH' => [],
'LIBS' => [],
'LDCMD' => ['${LD}', '-o', '${_TARGET}', '${LDFLAGS}', '${_SOURCES}', '-L${LIBPATH}', '-l${LIBS}']
}
end
def run(target, sources, cache, env, vars = {})
# build sources to linkable objects
objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars)
ld = if env["LD"]
env["LD"]
elsif sources.find {|s| s.has_suffix?(env["DSUFFIX"])}
env["DC"]
elsif sources.find {|s| s.has_suffix?(env["CXXSUFFIX"])}
env["CXX"]
else
env["CC"]
end
vars = vars.merge({
'_TARGET' => target,
'_SOURCES' => objects,
'LD' => ld,
})
command = env.build_command(env['LDCMD'], vars)
standard_build("LD #{target}", target, command, objects, env, cache)
def run(target, sources, cache, env, vars)
# build sources to linkable objects
objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars)
return false unless objects
ld = if env["LD"]
env["LD"]
elsif sources.find {|s| s.has_suffix?(env["DSUFFIX"])}
env["DC"]
elsif sources.find {|s| s.has_suffix?(env["CXXSUFFIX"])}
env["CXX"]
else
env["CC"]
end
vars = vars.merge({
'_TARGET' => target,
'_SOURCES' => objects,
'LD' => ld,
})
command = env.build_command(env['LDCMD'], vars)
standard_build("LD #{target}", target, command, objects, env, cache)
end
end
end
end
+35 -13
View File
@@ -18,9 +18,15 @@ module Rscons
# {
# 'fname' => 'program.o',
# 'checksum' => '87654321',
# }
# },
# ],
# }
# 'user_deps' => [
# {
# 'fname' => 'lscript.ld',
# 'checksum' => '77551133',
# },
# ],
# },
# 'program.o' => {
# 'checksum' => '87654321',
# 'command' => ['gcc', '-c', '-o', 'program.o', 'program.c'],
@@ -32,15 +38,16 @@ module Rscons
# {
# 'fname' => 'program.h',
# 'checksum' => '7979764643',
# }
# ]
# },
# ],
# 'user_deps' => [],
# }
# }
# },
# directories: {
# 'build' => true,
# 'build/one' => true,
# 'build/two' => true,
# }
# },
# }
class Cache
#### Constants
@@ -82,6 +89,7 @@ module Rscons
# @param target [String] The name of the target file.
# @param command [Array] The command used to build the target.
# @param deps [Array] List of the target's dependency files.
# @param env [Environment] The Rscons::Environment.
# @param options [Hash] Optional options. Can contain the following keys:
# :strict_deps::
# Only consider a target up to date if its list of dependencies is
@@ -96,7 +104,7 @@ module Rscons
# exactly equal to those cached
# - each cached dependency file's current checksum matches the checksum
# stored in the cache file
def up_to_date?(target, command, deps, options = {})
def up_to_date?(target, command, deps, env, options = {})
# target file must exist on disk
return false unless File.exists?(target)
@@ -109,17 +117,24 @@ module Rscons
# command used to build target must be identical
return false unless @cache[:targets][target][:command] == command
cached_deps = @cache[:targets][target][:deps].map { |dc| dc[:fname] }
cached_deps = @cache[:targets][target][:deps] or []
cached_deps_fnames = cached_deps.map { |dc| dc[:fname] }
if options[:strict_deps]
# depedencies passed in must exactly equal those in the cache
return false unless deps == cached_deps
return false unless deps == cached_deps_fnames
else
# all dependencies passed in must exist in cache (but cache may have more)
return false unless (Set.new(deps) - Set.new(cached_deps)).empty?
return false unless (Set.new(deps) - Set.new(cached_deps_fnames)).empty?
end
# set of user dependencies must match
user_deps = env.get_user_deps(target) || []
cached_user_deps = @cache[:targets][target][:user_deps] or []
cached_user_deps_fnames = cached_user_deps.map { |dc| dc[:fname] }
return false unless user_deps == cached_user_deps_fnames
# all cached dependencies must have their checksums match
@cache[:targets][target][: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])
end
@@ -130,7 +145,8 @@ module Rscons
# @param target [String] The name of the target.
# @param command [Array] The command used to build the target.
# @param deps [Array] List of dependencies for the target.
def register_build(target, command, deps)
# @param env [Environment] The Rscons::Environment.
def register_build(target, command, deps, env)
@cache[:targets][target.encode(__ENCODING__)] = {
command: command,
checksum: calculate_checksum(target),
@@ -139,7 +155,13 @@ module Rscons
fname: dep.encode(__ENCODING__),
checksum: lookup_checksum(dep),
}
end
end,
user_deps: (env.get_user_deps(target) || []).map do |dep|
{
fname: dep.encode(__ENCODING__),
checksum: lookup_checksum(dep),
}
end,
}
end
+62 -32
View File
@@ -9,33 +9,39 @@ module Rscons
# Hash of +{"builder_name" => builder_object}+ pairs.
attr_reader :builders
# :command, :short, or :off
attr_accessor :echo
# String or +nil+
attr_accessor :build_root
attr_reader :build_root
def build_root=(build_root)
@build_root = build_root.gsub('\\', '/')
end
# Create an Environment object.
# @param variables [Hash]
# The variables hash can contain construction variables, which are
# uppercase strings (such as "CC" or "LDFLAGS"), user variables, which
# are lowercase strings (such as "sources"), and RScons options, which
# are lowercase symbols (such as :echo).
# @param options [Hash]
# Possible options keys:
# :echo => :command, :short, or :off (default :short)
# :build_root => String specifying build root directory (default nil)
# :exclude_builders => true to omit adding default builders (default false)
# If a block is given, the Environment object is yielded to the block and
# when the block returns, the {#process} method is automatically called.
def initialize(variables = {})
@varset = VarSet.new(variables)
def initialize(options = {})
@varset = VarSet.new
@targets = {}
@user_deps = {}
@builders = {}
@build_dirs = []
@build_hooks = []
@varset[:exclude_builders] ||= []
unless @varset[:exclude_builders] == :all
exclude_builders = Set.new(@varset[:exclude_builders] || [])
DEFAULT_BUILDERS.each do |builder_class|
unless exclude_builders.include?(builder_class.short_name)
add_builder(builder_class.new)
end
unless options[:exclude_builders]
DEFAULT_BUILDERS.each do |builder_class_name|
builder_class = Builders.const_get(builder_class_name)
builder_class or raise "Could not find builder class #{builder_class_name}"
add_builder(builder_class.new)
end
end
@varset[:echo] ||= :short
@echo = options[:echo] || :short
@build_root = options[:build_root]
if block_given?
yield self
@@ -45,17 +51,23 @@ module Rscons
# Make a copy of the Environment object.
# The cloned environment will contain a copy of all environment options,
# construction variables, and builders. It will not contain a copy of the
# targets, build hooks, build directories, or the build root. If a block
# is given, the Environment object is yielded to the block and when the
# block returns, the {#process} method is automatically called.
def clone(variables = {})
env = Environment.new()
@builders.each do |builder_name, builder|
env.add_builder(builder)
# construction variables, and builders (unless :exclude_builders => true is
# passed as an option). It will not contain a copy of the targets, build
# hooks, build directories, or the build root. If a block is given, the
# Environment object is yielded to the block and when the block returns,
# the {#process} method is automatically called. The possible options keys
# match those documented in the #initialize method.
def clone(options = {})
env = self.class.new(
echo: options[:echo] || @echo,
build_root: options[:build_root],
exclude_builders: true)
unless options[:exclude_builders]
@builders.each do |builder_name, builder|
env.add_builder(builder)
end
end
env.append(@varset.clone)
env.append(variables)
if block_given?
yield env
@@ -66,7 +78,7 @@ module Rscons
# Add a {Builder} object to the Environment.
def add_builder(builder)
@builders[builder.class.short_name] = builder
@builders[builder.name] = builder
var_defs = builder.default_variables(self)
if var_defs
var_defs.each_pair do |var, val|
@@ -99,7 +111,7 @@ module Rscons
end
end
if @build_root and not found_match
unless source_fname.start_with?('/') or source_fname =~ %r{^\w:[\\/]}
unless source_fname.absolute_path? or build_fname.start_with?("#{@build_root}/")
build_fname = "#{@build_root}/#{build_fname}"
end
end
@@ -156,6 +168,11 @@ module Rscons
cache.write
end
# Clear all targets registered for the Environment.
def clear_targets
@targets = {}
end
# Build a command line from the given template, resolving references to
# variables using the Environment's construction variables and any extra
# variables specified.
@@ -169,7 +186,7 @@ module Rscons
end
# Execute a builder command
# @param short_desc [String] Message to print if the Environment's :echo
# @param short_desc [String] Message to print if the Environment's echo
# mode is set to :short
# @param command [Array] The command to execute.
# @param options [Hash] Optional options to pass to Kernel#system.
@@ -177,13 +194,13 @@ module Rscons
print_command = proc do
puts command.map { |c| c =~ /\s/ ? "'#{c}'" : c }.join(' ')
end
if @varset[:echo] == :command
if @echo == :command
print_command.call
elsif @varset[:echo] == :short
elsif @echo == :short
puts short_desc
end
system(*command, options).tap do |result|
unless result or @varset[:echo] == :command
unless result or @echo == :command
$stdout.write "Failed command was: "
print_command.call
end
@@ -209,6 +226,19 @@ module Rscons
end
end
# Manually record a given target as depending on the specified
# dependency files.
def depends(target, *user_deps)
@user_deps[target] ||= []
@user_deps[target] = (@user_deps[target] + user_deps).uniq
end
# Return the list of user dependencies for a given target, or +nil+ for
# none.
def get_user_deps(target)
@user_deps[target]
end
# Build a list of source files into files containing one of the suffixes
# given by suffixes.
# This method is used internally by RScons builders.
@@ -217,7 +247,7 @@ module Rscons
# @param cache [Cache] The Cache.
# @param vars [Hash] Extra variables to pass to the builder.
# Return a list of the converted file names.
def build_sources(sources, suffixes, cache, vars = {})
def build_sources(sources, suffixes, cache, vars)
sources.map do |source|
if source.has_suffix?(suffixes)
source
-7
View File
@@ -1,7 +0,0 @@
# Standard Ruby Module class.
class Module
# @return the base module name (not the fully qualified name)
def short_name
name.split(':').last
end
end
+5
View File
@@ -17,4 +17,9 @@ class String
def set_suffix(suffix = '')
sub(/\.[^.]*$/, suffix)
end
# Return whether the string represents an absolute filesystem path
def absolute_path?
self =~ %r{^(/|\w:[\\/])}
end
end
+1 -1
View File
@@ -72,7 +72,7 @@ module Rscons
elsif varval.is_a?(Array)
varval.map {|vv| expand_varref("#{prefix}#{vv}#{suffix}")}.flatten
else
raise "I do not know how to expand a variable reference to a #{varval.class.name}"
raise "I do not know how to expand a variable reference to a #{varval.class.name} (from #{varname.inspect} => #{@vars[varname].inspect})"
end
else
varref
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons
# gem version
VERSION = "0.2.0"
VERSION = "0.3.0"
end
+34 -2
View File
@@ -220,7 +220,7 @@ describe Rscons do
it 'allows Ruby classes as custom builders to be used to construct files' do
test_dir('custom_builder')
class MySource < Rscons::Builder
def run(target, sources, cache, env, vars = {})
def run(target, sources, user_deps, cache, env, vars = {})
File.open(target, 'w') do |fh|
fh.puts <<EOF
#define THE_VALUE 5678
@@ -251,7 +251,8 @@ EOF
env.Program('program-debug', Dir['src/*.c'])
end
release = debug.clone('CPPFLAGS' => '-DSTRING="Release Version"') do |env|
release = debug.clone do |env|
env["CPPFLAGS"] = '-DSTRING="Release Version"'
env.build_dir('src', 'release')
env.Program('program-release', Dir['src/*.c'])
end
@@ -327,6 +328,37 @@ EOF
]
end
it 'rebuilds when user-specified dependencies change' do
test_dir('simple')
Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c'])
File.open("file.ld", "w") do |fh|
fh.puts("foo")
end
env.depends('simple', 'file.ld')
end
lines.should == ["CC simple.o", "LD simple"]
File.exists?('simple.o').should be_true
`./simple`.should == "This is a simple C program\n"
Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c'])
File.open("file.ld", "w") do |fh|
fh.puts("bar")
end
env.depends('simple', 'file.ld')
end
lines.should == ["LD simple"]
Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c'])
File.unlink("file.ld")
end
lines.should == ["LD simple"]
Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c'])
end
lines.should == []
end
unless ENV["omit_gdc_tests"]
it "supports building D sources" do
test_dir("d")
+62 -12
View File
@@ -38,14 +38,19 @@ module Rscons
end
describe "#up_to_date?" do
empty_env = "env"
before do
empty_env.stub(:get_user_deps) { nil }
end
it "returns false when target file does not exist" do
File.should_receive(:exists?).with("target").and_return(false)
build_from({}).up_to_date?("target", "command", []).should be_false
build_from({}).up_to_date?("target", "command", [], empty_env).should be_false
end
it "returns false when target is not registered in the cache" do
File.should_receive(:exists?).with("target").and_return(true)
build_from({}).up_to_date?("target", "command", []).should be_false
build_from({}).up_to_date?("target", "command", [], empty_env).should be_false
end
it "returns false when the target's checksum does not match" do
@@ -53,7 +58,7 @@ module Rscons
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("def")
cache.up_to_date?("target", "command", []).should be_false
cache.up_to_date?("target", "command", [], empty_env).should be_false
end
it "returns false when the build command has changed" do
@@ -61,7 +66,7 @@ module Rscons
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", []).should be_false
cache.up_to_date?("target", "command", [], empty_env).should be_false
end
it "returns false when there is a new dependency" do
@@ -71,7 +76,7 @@ module Rscons
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"]).should be_false
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env).should be_false
end
it "returns false when a dependency's checksum has changed" do
@@ -82,13 +87,14 @@ module Rscons
{fname: "dep.2",
checksum: "dep.2.chk"},
{fname: "extra.dep",
checksum: "extra.dep.chk"}]}}}
checksum: "extra.dep.chk"}],
user_deps: []}}}
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.changed")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"]).should be_false
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env).should be_false
end
it "returns false with strict_deps=true when cache has an extra dependency" do
@@ -99,11 +105,48 @@ module Rscons
{fname: "dep.2",
checksum: "dep.2.chk"},
{fname: "extra.dep",
checksum: "extra.dep.chk"}]}}}
checksum: "extra.dep.chk"}],
user_deps: []}}}
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], strict_deps: true).should be_false
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env, strict_deps: true).should be_false
end
it "returns false when there is a new user dependency" do
_cache = {targets: {"target" => {checksum: "abc",
command: "command",
deps: [{fname: "dep.1"}],
user_deps: []}}}
cache = build_from(_cache)
env = "env"
env.should_receive(:get_user_deps).with("target").and_return(["file.ld"])
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", ["dep.1"], env).should be_false
end
it "returns false when a user dependency checksum has changed" do
_cache = {targets: {"target" => {checksum: "abc",
command: "command",
deps: [{fname: "dep.1",
checksum: "dep.1.chk"},
{fname: "dep.2",
checksum: "dep.2.chk"},
{fname: "extra.dep",
checksum: "extra.dep.chk"}],
user_deps: [{fname: "user.dep",
checksum: "user.dep.chk"}]}}}
cache = build_from(_cache)
env = "env"
env.should_receive(:get_user_deps).with("target").and_return(["user.dep"])
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk")
cache.should_receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk")
cache.should_receive(:calculate_checksum).with("user.dep").and_return("INCORRECT")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], env).should be_false
end
it "returns true when no condition for false is met" do
@@ -114,14 +157,15 @@ module Rscons
{fname: "dep.2",
checksum: "dep.2.chk"},
{fname: "extra.dep",
checksum: "extra.dep.chk"}]}}}
checksum: "extra.dep.chk"}],
user_deps: []}}}
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk")
cache.should_receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"]).should be_true
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env).should be_true
end
end
@@ -129,10 +173,13 @@ module Rscons
it "stores the given information in the cache" do
_cache = {}
cache = build_from(_cache)
env = "env"
env.should_receive(:get_user_deps).with("the target").and_return(["user.dep"])
cache.should_receive(:calculate_checksum).with("the target").and_return("the checksum")
cache.should_receive(:calculate_checksum).with("dep 1").and_return("dep 1 checksum")
cache.should_receive(:calculate_checksum).with("dep 2").and_return("dep 2 checksum")
cache.register_build("the target", "the command", ["dep 1", "dep 2"])
cache.should_receive(:calculate_checksum).with("user.dep").and_return("user.dep checksum")
cache.register_build("the target", "the command", ["dep 1", "dep 2"], env)
cached_target = cache.instance_variable_get(:@cache)[:targets]["the target"]
cached_target.should_not be_nil
cached_target[:command].should == "the command"
@@ -141,6 +188,9 @@ module Rscons
{fname: "dep 1", checksum: "dep 1 checksum"},
{fname: "dep 2", checksum: "dep 2 checksum"},
]
cached_target[:user_deps].should == [
{fname: "user.dep", checksum: "user.dep checksum"},
]
end
end
+63 -20
View File
@@ -1,12 +1,6 @@
module Rscons
describe Environment do
describe "#initialize" do
it "stores the construction variables passed in" do
env = Environment.new("CFLAGS" => ["-g"], "CPPPATH" => ["dir"])
env["CFLAGS"].should == ["-g"]
env["CPPPATH"].should == ["dir"]
end
it "adds the default builders when they are not excluded" do
env = Environment.new
env.builders.size.should be > 0
@@ -17,18 +11,10 @@ module Rscons
end
it "excludes the default builders with exclude_builders: :all" do
env = Environment.new(exclude_builders: :all)
env = Environment.new(exclude_builders: true)
env.builders.size.should == 0
end
it "excludes the named builders" do
env = Environment.new(exclude_builders: ["Library"])
env.builders.size.should be > 0
env.builders.find {|name, builder| name == "Object"}.should_not be_nil
env.builders.find {|name, builder| name == "Program"}.should_not be_nil
env.builders.find {|name, builder| name == "Library"}.should be_nil
end
context "when a block is given" do
it "yields self and invokes #process()" do
env = Environment.new do |env|
@@ -60,9 +46,9 @@ module Rscons
describe "#add_builder" do
it "adds the builder to the list of builders" do
env = Environment.new(exclude_builders: :all)
env = Environment.new(exclude_builders: true)
env.builders.keys.should == []
env.add_builder(Rscons::Object.new)
env.add_builder(Rscons::Builders::Object.new)
env.builders.keys.should == ["Object"]
end
end
@@ -75,6 +61,18 @@ module Rscons
env.get_build_fname("src\\dir\\other.d", ".a").should == "src/dir/other.a"
env.get_build_fname("source.cc", ".o").should == "source.o"
end
context "with a build_root" do
it "uses the build_root unless the path is absolute" do
env = Environment.new
env.build_root = "build/proj"
env.get_build_fname("src/dir/file.c", ".o").should == "build/proj/src/dir/file.o"
env.get_build_fname("/some/lib.c", ".a").should == "/some/lib.a"
env.get_build_fname("C:\\abspath\\mod.cc", ".o").should == "C:/abspath/mod.o"
env.get_build_fname("build\\proj\\generated.c", ".o").should == "build/proj/generated.o"
env.get_build_fname("build/proj.XX", ".yy").should == "build/proj/build/proj.yy"
end
end
end
context "with build directories" do
@@ -87,19 +85,36 @@ module Rscons
env.get_build_fname("libs/otherlib/otherlib.cc", ".o").should == "build/libs/otherlib/otherlib.o"
env.get_build_fname("other_directory/o.d", ".a").should == "other_directory/o.a"
end
context "with a build_root" do
it "uses the build_root unless a build directory matches or the path is absolute" do
env = Environment.new
env.build_dir("src", "bld")
env.build_dir(%r{^libs/([^/]+)}, 'build/libs/\1')
env.build_root = "bldit"
env.get_build_fname("src/input.cc", ".o").should == "bld/input.o"
env.get_build_fname("libs/lib1/some/file.c", ".o").should == "build/libs/lib1/some/file.o"
env.get_build_fname("libs/otherlib/otherlib.cc", ".o").should == "build/libs/otherlib/otherlib.o"
env.get_build_fname("other_directory/o.d", ".a").should == "bldit/other_directory/o.a"
env.get_build_fname("bldit/some/mod.d", ".a").should == "bldit/some/mod.a"
end
end
end
end
describe "#[]" do
it "allows reading construction variables" do
env = Environment.new("CFLAGS" => ["-g", "-Wall"])
env = Environment.new
env["CFLAGS"] = ["-g", "-Wall"]
env["CFLAGS"].should == ["-g", "-Wall"]
end
end
describe "#[]=" do
it "allows writing construction variables" do
env = Environment.new("CFLAGS" => ["-g", "-Wall"])
env = Environment.new
env["CFLAGS"] = ["-g", "-Wall"]
env["CFLAGS"] -= ["-g"]
env["CFLAGS"] += ["-O3"]
env["CFLAGS"].should == ["-Wall", "-O3"]
@@ -110,7 +125,9 @@ module Rscons
describe "#append" do
it "allows adding many construction variables at once" do
env = Environment.new("CFLAGS" => ["-g"], "CPPPATH" => ["inc"])
env = Environment.new
env["CFLAGS"] = ["-g"]
env["CPPPATH"] = ["inc"]
env.append("CFLAGS" => ["-Wall"], "CPPPATH" => ["include"])
env["CFLAGS"].should == ["-Wall"]
env["CPPPATH"].should == ["include"]
@@ -158,6 +175,18 @@ module Rscons
end
end
describe "#clear_targets" do
it "resets @targets to an empty hash" do
env = Environment.new
env.Program("a.out", "main.o")
expect(env.instance_variable_get(:@targets).keys).to eq(["a.out"])
env.clear_targets
expect(env.instance_variable_get(:@targets).keys).to eq([])
end
end
describe "#build_command" do
it "returns a command based on the variables in the Environment" do
env = Environment.new
@@ -228,6 +257,20 @@ module Rscons
end
end
describe "#depends" do
it "records the given dependencies in @user_deps" do
env = Environment.new
env.depends("foo", "bar", "baz")
env.instance_variable_get(:@user_deps).should == {"foo" => ["bar", "baz"]}
end
it "records user dependencies only once" do
env = Environment.new
env.instance_variable_set(:@user_deps, {"foo" => ["bar"]})
env.depends("foo", "bar", "baz")
env.instance_variable_get(:@user_deps).should == {"foo" => ["bar", "baz"]}
end
end
describe "#build_sources" do
class ABuilder < Builder
def produces?(target, source, env)
-6
View File
@@ -1,6 +0,0 @@
describe "Module#short_name" do
it "returns the inner name of the module" do
Rscons::Environment.short_name.should == "Environment"
Rscons::Object.short_name.should == "Object"
end
end