Compare commits

...
26 Commits
Author SHA1 Message Date
josh 31abeed3d5 v0.0.13 2013-10-15 16:29:54 -04:00
josh 446fef931f add "tweakers" functionality
tweakers allow the user to modify construction variables before any build is performed
2013-10-15 16:15:37 -04:00
josh 4a2997feec VarSet: make a deep copy of values passed to VarSet#append() 2013-10-15 16:10:56 -04:00
josh 013995bbc0 refactor into Environment#run_builder() 2013-10-15 13:36:25 -04:00
josh cd266c511e v0.0.12 2013-10-08 11:54:38 -04:00
josh 0f5fee359d cloning an Environment should make a deep copy of its construction variables 2013-10-08 11:54:24 -04:00
josh dc6bac2895 change build_dir build test to test build_dir specified as a Regexp 2013-10-04 21:40:11 -04:00
josh e50aac31c9 tweak custom_builder build test for additional coverage 2013-10-04 21:26:51 -04:00
josh e788f6ec25 claim simplecov coverage from build_tests 2013-10-04 21:05:44 -04:00
josh 8d893d7b72 rspec Module monkey-patching 2013-10-04 20:22:37 -04:00
josh c258dc379f rspec VarSet 2013-10-04 20:18:40 -04:00
josh 0facfb274d turn on simplecov 2013-10-04 19:37:56 -04:00
josh 3339cf5280 Cache: re-encode file names in default encoding 2013-09-20 15:58:55 -04:00
josh e041ea6612 v0.0.11 2013-09-19 17:06:37 -04:00
josh ab51ddcc1b do not try to clean directories that have already been removed 2013-09-19 17:06:20 -04:00
josh 3e4e9cce1c v0.0.10 2013-09-19 16:48:15 -04:00
josh 32d19489cb Cache: encode directory names in __ENCODING__ - close #3 2013-09-19 16:45:35 -04:00
josh 45a0d56c54 recover from, and warn when, .rsconscache loads something other than a Hash 2013-09-19 16:18:07 -04:00
josh 9b954d9d98 v0.0.9 2013-09-19 09:43:17 -04:00
josh e63b4c932f Cache: create missing hash entries from previous versions of the cache file 2013-09-19 09:43:00 -04:00
josh 97e11f45e5 v0.0.8 2013-09-18 21:18:00 -04:00
josh 5c6239ea6d Rscons.clean() removes any directories it created if they are empty 2013-09-18 21:17:42 -04:00
josh 770a22250a add Rscons.clear() class method 2013-09-17 21:48:50 -04:00
josh 983cb7cf21 v0.0.7 2013-09-16 23:03:14 -04:00
josh 986b8f08cc prepend builder-temporary construction variables with "_"; reserve lowercase string variables for user 2013-09-16 22:30:16 -04:00
josh f13664331f create requisite output directories for build targets; refactor common buile functionality into Builder#standard_build() 2013-09-15 15:27:16 -04:00
17 changed files with 352 additions and 69 deletions

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
Rscons::Environment.new do |env| Rscons::Environment.new do |env|
env.append('CPPPATH' => Dir['src/**/*/']) env.append('CPPPATH' => Dir['src/**/*/'])
env.build_dir('src', 'build') env.build_dir(%r{^src/([^/]+)/}, 'build_\\1/')
env.Program('build_dir', Dir['src/**/*.c']) env.Program('build_dir', Dir['src/**/*.c'])
end end
+12
View File
@@ -0,0 +1,12 @@
Rscons::Environment.new do |env|
env.append('CPPPATH' => Dir['src/**/*/'])
env.build_dir(%r{^src/([^/]+)/}, 'build_\\1/')
env.add_tweaker do |build_op|
if build_op[:target] =~ %r{build_one/.*\.o}
build_op[:vars]["CFLAGS"] << "-O1"
elsif build_op[:target] =~ %r{build_two/.*\.o}
build_op[:vars]["CFLAGS"] << "-O2"
end
end
env.Program('tweaker', Dir['src/**/*.c'])
end
+1 -2
View File
@@ -9,8 +9,7 @@ EOF
end end
end end
Rscons::Environment.new(echo: :short) do |env| Rscons::Environment.new(echo: :short, builders: [MySource.new]) do |env|
env.add_builder(MySource.new)
env.MySource('inc.h', []) env.MySource('inc.h', [])
env.Program('program', Dir['*.c']) env.Program('program', Dir['*.c'])
end end
+17
View File
@@ -22,4 +22,21 @@ module Rscons
class BuildError < Exception class BuildError < Exception
end end
# Remove all generated files
def self.clean
cache = Cache.new
# remove all built files
cache.targets.each do |target|
FileUtils.rm_f(target)
end
# remove all created directories if they are empty
cache.directories.sort {|a, b| b.size <=> a.size}.each do |directory|
next unless File.directory?(directory)
if (Dir.entries(directory) - ['.', '..']).empty?
Dir.rmdir(directory) rescue nil
end
end
Cache.clear
end
end end
+15
View File
@@ -1,3 +1,5 @@
require "fileutils"
module Rscons 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
@@ -16,5 +18,18 @@ module Rscons
def produces?(target, source, env) def produces?(target, source, env)
false false
end end
# Check if the cache is up to date for the target and if not execute the
# 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)
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)
end
target
end
end end
end end
+4 -9
View File
@@ -8,7 +8,7 @@ module Rscons
'AR' => 'ar', 'AR' => 'ar',
'LIBSUFFIX' => '.a', 'LIBSUFFIX' => '.a',
'ARFLAGS' => [], 'ARFLAGS' => [],
'ARCOM' => ['$AR', 'rcs', '$ARFLAGS', '$TARGET', '$SOURCES'] 'ARCOM' => ['$AR', 'rcs', '$ARFLAGS', '$_TARGET', '$_SOURCES']
} }
end end
@@ -17,16 +17,11 @@ module Rscons
objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars) objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars)
if objects if objects
vars = vars.merge({ vars = vars.merge({
'TARGET' => target, '_TARGET' => target,
'SOURCES' => objects, '_SOURCES' => objects,
}) })
command = env.build_command(env['ARCOM'], vars) command = env.build_command(env['ARCOM'], vars)
unless cache.up_to_date?(target, command, objects) standard_build("AR #{target}", target, command, objects, env, cache)
FileUtils.rm_f(target)
return false unless env.execute("AR #{target}", command)
cache.register_build(target, command, objects)
end
target
end end
end end
end end
+14 -12
View File
@@ -11,8 +11,8 @@ module Rscons
'ASSUFFIX' => '.S', 'ASSUFFIX' => '.S',
'ASPPPATH' => '$CPPPATH', 'ASPPPATH' => '$CPPPATH',
'ASPPFLAGS' => '$CPPFLAGS', 'ASPPFLAGS' => '$CPPFLAGS',
'ASDEPGEN' => ['-MMD', '-MF', '$DEPFILE'], 'ASDEPGEN' => ['-MMD', '-MF', '$_DEPFILE'],
'ASCOM' => ['$AS', '-c', '-o', '$TARGET', '$ASDEPGEN', '-I$[ASPPPATH]', '$ASPPFLAGS', '$ASFLAGS', '$SOURCES'], 'ASCOM' => ['$AS', '-c', '-o', '$_TARGET', '$ASDEPGEN', '-I$[ASPPPATH]', '$ASPPFLAGS', '$ASFLAGS', '$_SOURCES'],
'CPPFLAGS' => [], 'CPPFLAGS' => [],
'CPPPATH' => [], 'CPPPATH' => [],
@@ -20,14 +20,14 @@ module Rscons
'CC' => 'gcc', 'CC' => 'gcc',
'CFLAGS' => [], 'CFLAGS' => [],
'CSUFFIX' => '.c', 'CSUFFIX' => '.c',
'CCDEPGEN' => ['-MMD', '-MF', '$DEPFILE'], 'CCDEPGEN' => ['-MMD', '-MF', '$_DEPFILE'],
'CCCOM' => ['$CC', '-c', '-o', '$TARGET', '$CCDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CFLAGS', '$SOURCES'], 'CCCOM' => ['$CC', '-c', '-o', '$_TARGET', '$CCDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CFLAGS', '$_SOURCES'],
'CXX' => 'g++', 'CXX' => 'g++',
'CXXFLAGS' => [], 'CXXFLAGS' => [],
'CXXSUFFIX' => '.cc', 'CXXSUFFIX' => '.cc',
'CXXDEPGEN' => ['-MMD', '-MF', '$DEPFILE'], 'CXXDEPGEN' => ['-MMD', '-MF', '$_DEPFILE'],
'CXXCOM' =>['$CXX', '-c', '-o', '$TARGET', '$CXXDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CXXFLAGS', '$SOURCES'], 'CXXCOM' =>['$CXX', '-c', '-o', '$_TARGET', '$CXXDEPGEN', '-I$[CPPPATH]', '$CPPFLAGS', '$CXXFLAGS', '$_SOURCES'],
} }
end end
@@ -40,9 +40,9 @@ module Rscons
def run(target, sources, cache, env, vars = {}) def run(target, sources, cache, env, vars = {})
vars = vars.merge({ vars = vars.merge({
'TARGET' => target, '_TARGET' => target,
'SOURCES' => sources, '_SOURCES' => sources,
'DEPFILE' => target.set_suffix('.mf'), '_DEPFILE' => target.set_suffix('.mf'),
}) })
com_prefix = if sources.first.has_suffix?(env['ASSUFFIX']) com_prefix = if sources.first.has_suffix?(env['ASSUFFIX'])
'AS' 'AS'
@@ -55,11 +55,13 @@ module Rscons
end end
command = env.build_command(env["#{com_prefix}COM"], vars) command = env.build_command(env["#{com_prefix}COM"], vars)
unless cache.up_to_date?(target, command, sources) 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) return false unless env.execute("#{com_prefix} #{target}", command)
deps = sources deps = sources
if File.exists?(vars['DEPFILE']) if File.exists?(vars['_DEPFILE'])
deps += env.parse_makefile_deps(vars['DEPFILE'], target) deps += env.parse_makefile_deps(vars['_DEPFILE'], target)
FileUtils.rm_f(vars['DEPFILE']) FileUtils.rm_f(vars['_DEPFILE'])
end end
cache.register_build(target, command, deps.uniq) cache.register_build(target, command, deps.uniq)
end end
+4 -8
View File
@@ -10,7 +10,7 @@ module Rscons
'LDFLAGS' => [], 'LDFLAGS' => [],
'LIBPATH' => [], 'LIBPATH' => [],
'LIBS' => [], 'LIBS' => [],
'LDCOM' => ['$LD', '-o', '$TARGET', '$LDFLAGS', '$SOURCES', '-L$[LIBPATH]', '-l$[LIBS]'] 'LDCOM' => ['$LD', '-o', '$_TARGET', '$LDFLAGS', '$_SOURCES', '-L$[LIBPATH]', '-l$[LIBS]']
} }
end end
@@ -23,16 +23,12 @@ module Rscons
end.any? end.any?
ld_alt = use_cxx ? env['CXX'] : env['CC'] ld_alt = use_cxx ? env['CXX'] : env['CC']
vars = vars.merge({ vars = vars.merge({
'TARGET' => target, '_TARGET' => target,
'SOURCES' => objects, '_SOURCES' => objects,
'LD' => env['LD'] || ld_alt, 'LD' => env['LD'] || ld_alt,
}) })
command = env.build_command(env['LDCOM'], vars) command = env.build_command(env['LDCOM'], vars)
unless cache.up_to_date?(target, command, objects) standard_build("LD #{target}", target, command, objects, env, cache)
return false unless env.execute("LD #{target}", command)
cache.register_build(target, command, objects)
end
target
end end
end end
end end
+40 -6
View File
@@ -36,6 +36,11 @@ module Rscons
# ] # ]
# } # }
# } # }
# directories: {
# 'build' => true,
# 'build/one' => true,
# 'build/two' => true,
# }
# } # }
class Cache class Cache
#### Constants #### Constants
@@ -55,10 +60,14 @@ module Rscons
# Create a Cache object and load in the previous contents from the cache # Create a Cache object and load in the previous contents from the cache
# file. # file.
def initialize def initialize
@cache = YAML.load(File.read(CACHE_FILE)) rescue { @cache = YAML.load(File.read(CACHE_FILE)) rescue {}
targets: {}, unless @cache.is_a?(Hash)
version: VERSION, $stderr.puts "Warning: #{CACHE_FILE} was corrupt. Contents:\n#{@cache.inspect}"
} @cache = {}
end
@cache[:targets] ||= {}
@cache[:directories] ||= {}
@cache[:version] ||= VERSION
@lookup_checksums = {} @lookup_checksums = {}
end end
@@ -120,18 +129,43 @@ module Rscons
# @param command [Array] The command used to build the target. # @param command [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.
def register_build(target, command, deps) def register_build(target, command, deps)
@cache[:targets][target] = { @cache[:targets][target.encode(__ENCODING__)] = {
command: command, command: command,
checksum: calculate_checksum(target), checksum: calculate_checksum(target),
deps: deps.map do |dep| deps: deps.map do |dep|
{ {
fname: dep, fname: dep.encode(__ENCODING__),
checksum: lookup_checksum(dep), checksum: lookup_checksum(dep),
} }
end end
} }
end end
# Return a list of targets that have been built
def targets
@cache[:targets].keys
end
# Make any needed directories and record the ones that are created for
# removal upon a "clean" operation.
def mkdir_p(path)
parts = path.split(/[\\\/]/)
(0..parts.size).each do |i|
subpath = File.join(*parts[0, i + 1]).encode(__ENCODING__)
unless File.exists?(subpath)
FileUtils.mkdir(subpath)
unless @cache[:directories].include?(subpath)
@cache[:directories][subpath] = true
end
end
end
end
# Return a list of directories which were created as a part of the build
def directories
@cache[:directories].keys
end
# Private Instance Methods # Private Instance Methods
private private
+42 -15
View File
@@ -6,14 +6,15 @@ module Rscons
# 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
# [Array] of {Builder} objects. # [Hash] of {"builder_name" => builder_object} pairs.
attr_reader :builders attr_reader :builders
# Create an Environment object. # Create an Environment object.
# @param variables [Hash] # @param variables [Hash]
# The variables hash can contain both construction variables, which are # The variables hash can contain construction variables, which are
# uppercase strings (such as "CC" or "LDFLAGS"), and RScons options, # uppercase strings (such as "CC" or "LDFLAGS"), user variables, which
# which are lowercase symbols (such as :echo). # are lowercase strings (such as "sources"), and RScons options, which
# are lowercase symbols (such as :echo).
# If a block is given, the Environment object is yielded to the block and # If a block is given, the Environment object is yielded to the block and
# when the block returns, the {#process} method is automatically called. # when the block returns, the {#process} method is automatically called.
def initialize(variables = {}) def initialize(variables = {})
@@ -21,6 +22,7 @@ module Rscons
@targets = {} @targets = {}
@builders = {} @builders = {}
@build_dirs = [] @build_dirs = []
@tweakers = []
@varset[:exclude_builders] ||= [] @varset[:exclude_builders] ||= []
unless @varset[:exclude_builders] == :all unless @varset[:exclude_builders] == :all
exclude_builders = Set.new(@varset[:exclude_builders] || []) exclude_builders = Set.new(@varset[:exclude_builders] || [])
@@ -44,7 +46,7 @@ module Rscons
# 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, # The cloned environment will contain a copy of all environment options,
# construction variables, builders, and build directories. It will not # construction variables, builders, and build directories. It will not
# contain a copy of the targets. # contain a copy of the targets or tweakers.
# If a block is given, the Environment object is yielded to the block and # If a block is given, the Environment object is yielded to the block and
# when the block returns, the {#process} method is automatically called. # when the block returns, the {#process} method is automatically called.
def clone(variables = {}) def clone(variables = {})
@@ -52,7 +54,7 @@ module Rscons
@builders.each do |builder_name, builder| @builders.each do |builder_name, builder|
env.add_builder(builder) env.add_builder(builder)
end end
env.append(@varset) env.append(@varset.clone)
env.append(variables) env.append(variables)
if block_given? if block_given?
@@ -73,11 +75,15 @@ module Rscons
end end
end end
# Add a tweaker block to the Environment.
def add_tweaker(&block)
@tweakers << block
end
# Specify a build directory for this Environment. # Specify a build directory for this Environment.
# Source files from src_dir will produce object files under obj_dir. # Source files from src_dir will produce object files under obj_dir.
def build_dir(src_dir, obj_dir) def build_dir(src_dir, obj_dir)
src_dir = src_dir.gsub('\\', '/') if src_dir.is_a?(String) src_dir = src_dir.gsub('\\', '/') if src_dir.is_a?(String)
obj_dir = obj_dir.gsub('\\', '/')
@build_dirs << [src_dir, obj_dir] @build_dirs << [src_dir, obj_dir]
end end
@@ -91,11 +97,10 @@ module Rscons
if src_dir.is_a?(Regexp) if src_dir.is_a?(Regexp)
build_fname.sub!(src_dir, obj_dir) build_fname.sub!(src_dir, obj_dir)
else else
build_fname.sub!(/^#{src_dir}\//, "#{obj_dir}/") build_fname.sub!(%r{^#{src_dir}/}, "#{obj_dir}/")
end end
build_fname.gsub!('\\', '/') build_fname.gsub!('\\', '/')
end end
FileUtils.mkdir_p(File.dirname(build_fname))
build_fname build_fname
end end
@@ -137,11 +142,11 @@ module Rscons
if @targets[target][:source].map do |src| if @targets[target][:source].map do |src|
targets_processed.include?(src) or not @targets.include?(src) or process_target.call(src) targets_processed.include?(src) or not @targets.include?(src) or process_target.call(src)
end.all? end.all?
@targets[target][:builder].run(target, run_builder(@targets[target][:builder],
@targets[target][:source], target,
cache, @targets[target][:source],
self, cache,
@targets[target][:vars] || {}) @targets[target][:vars] || {})
else else
false false
end end
@@ -239,6 +244,7 @@ module Rscons
# @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.
# @param vars [Hash] Extra variables to pass to the builder.
# Return a list of the converted file names. # 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| sources.map do |source|
@@ -250,7 +256,7 @@ module Rscons
converted_fname = get_build_fname(source, suffix) converted_fname = get_build_fname(source, suffix)
builder = @builders.values.find { |b| b.produces?(converted_fname, source, self) } builder = @builders.values.find { |b| b.produces?(converted_fname, source, self) }
if builder if builder
converted = builder.run(converted_fname, [source], cache, self, vars) converted = run_builder(builder, converted_fname, [source], cache, vars)
return nil unless converted return nil unless converted
break break
end end
@@ -259,5 +265,26 @@ module Rscons
end end
end end
end end
# Invoke a builder to build the given target based on the given sources.
# @param builder [Builder] The Builder to use.
# @param target [String] The target output file.
# @param sources [Array] List of source files.
# @param cache [Cache] The Cache.
# @param vars [Hash] Extra variables to pass to the builder.
# Return the result of the builder's run() method.
def run_builder(builder, target, sources, cache, vars)
vars = @varset.merge(vars)
@tweakers.each do |tweaker_block|
build_operation = {
builder: builder,
target: target,
sources: sources,
vars: vars,
}
tweaker_block.call(build_operation)
end
builder.run(target, sources, cache, self, vars)
end
end end
end end
+3 -3
View File
@@ -24,9 +24,9 @@ module Rscons
# returned. # returned.
def [](key, type = nil) def [](key, type = nil)
val = @vars[key] val = @vars[key]
if type == :array and val.is_a?(String) if type == :array and not val.is_a?(Array)
[val] [val]
elsif type == :string and val.is_a?(Array) elsif type == :scalar and val.is_a?(Array)
val.first val.first
else else
val val
@@ -44,7 +44,7 @@ module Rscons
# @param values [VarSet, Hash] New set of variables. # @param values [VarSet, Hash] New set of variables.
def append(values) def append(values)
values = values.vars if values.is_a?(VarSet) values = values.vars if values.is_a?(VarSet)
@vars.merge!(values) @vars.merge!(Marshal.load(Marshal.dump(values)))
self self
end end
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons module Rscons
# gem version # gem version
VERSION = "0.0.6" VERSION = "0.0.13"
end end
+63 -12
View File
@@ -11,17 +11,35 @@ describe Rscons do
FileUtils.rm_rf('build_tests_run') FileUtils.rm_rf('build_tests_run')
end end
def build_testdir def build_testdir(build_script = "build.rb")
if File.exists?("build.rb") if File.exists?(build_script)
system("ruby -I #{@owd}/lib -r rscons build.rb > build.out") build_rb = File.read(build_script)
File.open(build_script, "w") do |fh|
fh.puts(<<EOF + build_rb)
require "simplecov"
SimpleCov.start do
root("#{@owd}")
command_name("build_test_#{@build_test_name}")
add_filter("spec")
end
require "rscons"
EOF
end
IO.popen(%{ruby -I #{@owd}/lib #{build_script}}) do |io|
io.readlines.reject do |line|
line =~ /^Coverage report/
end
end.map(&:strip)
end end
get_build_output
end end
def test_dir(build_test_directory) def test_dir(build_test_directory, build_script = "build.rb")
@build_test_name = build_test_directory
FileUtils.cp_r("build_tests/#{build_test_directory}", 'build_tests_run') FileUtils.cp_r("build_tests/#{build_test_directory}", 'build_tests_run')
Dir.chdir("build_tests_run") Dir.chdir("build_tests_run")
build_testdir build_testdir(build_script)
end end
def file_sub(fname) def file_sub(fname)
@@ -35,10 +53,6 @@ describe Rscons do
end end
end end
def get_build_output
File.read('build.out').lines.map(&:strip)
end
########################################################################### ###########################################################################
# Tests # Tests
########################################################################### ###########################################################################
@@ -116,8 +130,35 @@ describe Rscons do
it 'builds object files in a different build directory' do it 'builds object files in a different build directory' do
lines = test_dir('build_dir') lines = test_dir('build_dir')
`./build_dir`.should == "Hello from two()\n" `./build_dir`.should == "Hello from two()\n"
File.exists?('build/one/one.o').should be_true File.exists?('build_one/one.o').should be_true
File.exists?('build/two/two.o').should be_true File.exists?('build_two/two.o').should be_true
end
it 'cleans built files' do
lines = test_dir('build_dir')
`./build_dir`.should == "Hello from two()\n"
File.exists?('build_one/one.o').should be_true
File.exists?('build_two/two.o').should be_true
Rscons.clean
File.exists?('build_one/one.o').should be_false
File.exists?('build_two/two.o').should be_false
File.exists?('build_one').should be_false
File.exists?('build_two').should be_false
File.exists?('src/one/one.c').should be_true
end
it 'does not clean created directories if other non-rscons-generated files reside there' do
lines = test_dir('build_dir')
`./build_dir`.should == "Hello from two()\n"
File.exists?('build_one/one.o').should be_true
File.exists?('build_two/two.o').should be_true
File.open('build_two/tmp', 'w') { |fh| fh.puts "dum" }
Rscons.clean
File.exists?('build_one/one.o').should be_false
File.exists?('build_two/two.o').should be_false
File.exists?('build_one').should be_false
File.exists?('build_two').should be_true
File.exists?('src/one/one.c').should be_true
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
@@ -166,4 +207,14 @@ describe Rscons do
File.exists?('library').should be_true File.exists?('library').should be_true
`ar t lib.a`.should == "one.o\ntwo.o\n" `ar t lib.a`.should == "one.o\ntwo.o\n"
end end
it 'supports tweakers to override construction variables' do
lines = test_dir("build_dir", "tweaker_build.rb")
`./tweaker`.should == "Hello from two()\n"
lines.should =~ [
'gcc -c -o build_one/one.o -MMD -MF build_one/one.mf -Isrc/one/ -Isrc/two/ -O1 src/one/one.c',
'gcc -c -o build_two/two.o -MMD -MF build_two/two.mf -Isrc/one/ -Isrc/two/ -O2 src/two/two.c',
'gcc -o tweaker build_one/one.o build_two/two.o',
]
end
end end
+11
View File
@@ -1,5 +1,16 @@
module Rscons module Rscons
describe Environment do describe Environment do
describe '.clone' do
it 'should create unique copies of each construction variable' do
env = Environment.new
env["CPPPATH"] << "path1"
env2 = env.clone
env2["CPPPATH"] << "path2"
env["CPPPATH"].should == ["path1"]
env2["CPPPATH"].should == ["path1", "path2"]
end
end
describe '.parse_makefile_deps' do describe '.parse_makefile_deps' do
it 'handles dependencies on one line' do it 'handles dependencies on one line' do
File.should_receive(:read).with('makefile').and_return(<<EOS) File.should_receive(:read).with('makefile').and_return(<<EOS)
+6
View File
@@ -0,0 +1,6 @@
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
+114
View File
@@ -0,0 +1,114 @@
module Rscons
describe VarSet do
describe '.initialize' do
it "initializes variables from a Hash" do
v = VarSet.new({"one" => 1, "two" => :two})
v["one"].should == 1
v["two"].should == :two
end
it "initializes variables from another VarSet" do
v = VarSet.new({"one" => 1})
v2 = VarSet.new(v)
v2["one"].should == 1
end
it "makes a deep copy of the given VarSet" do
v = VarSet.new({"array" => [1, 2, 3]})
v2 = VarSet.new(v)
v["array"] << 4
v["array"].should == [1, 2, 3, 4]
v2["array"].should == [1, 2, 3]
end
end
describe :[] do
v = VarSet.new({"fuz" => "a string", "foo" => 42, "bar" => :baz,
"qax" => [3, 6], "qux" => {a: :b}})
it "allows accessing a variable with its verbatim value if type is not specified" do
v["fuz"].should == "a string"
v["foo"].should == 42
v["bar"].should == :baz
v["qax"].should == [3, 6]
v["qux"].should == {a: :b}
end
it "allows accessing a non-array converted to an array" do
v["fuz", :array].should == ["a string"]
end
it "allows accessing an array as a single value" do
v["qax", :scalar].should == 3
end
end
describe :[]= do
it "allows assigning to variables" do
v = VarSet.new("CFLAGS" => ["-Wall", "-O3"])
v["CPPPATH"] = ["one", "two"]
v["CFLAGS"].should == ["-Wall", "-O3"]
v["CPPPATH"].should == ["one", "two"]
end
end
describe '.append' do
it "adds values from a Hash to the VarSet" do
v = VarSet.new("LDFLAGS" => "-lgcc")
v.append("LIBS" => "gcc", "LIBPATH" => ["mylibs"])
v.vars.keys.should =~ ["LDFLAGS", "LIBS", "LIBPATH"]
end
it "adds values from another VarSet to the VarSet" do
v = VarSet.new("CPPPATH" => ["mydir"])
v2 = VarSet.new("CFLAGS" => ["-O0"], "CPPPATH" => ["different_dir"])
v.append(v2)
v.vars.keys.should =~ ["CPPPATH", "CFLAGS"]
v["CPPPATH"].should == ["different_dir"]
end
end
describe '.merge' do
it "returns a new VarSet merged with the given Hash" do
v = VarSet.new("foo" => "yoda")
v2 = v.merge("baz" => "qux")
v.vars.keys.should == ["foo"]
v2.vars.keys.should =~ ["foo", "baz"]
end
it "returns a new VarSet merged with the given VarSet" do
v = VarSet.new("foo" => ["a", "b"], "bar" => 42)
v2 = v.merge(VarSet.new("bar" => 33, "baz" => :baz))
v2["foo"] << "c"
v["foo"].should == ["a", "b"]
v["bar"].should == 42
v2["foo"].should == ["a", "b", "c"]
v2["bar"].should == 33
end
end
describe '.expand_varref' do
v = VarSet.new("CFLAGS" => ["-Wall", "-O2"],
"CC" => "gcc",
"CPPPATH" => ["dir1", "dir2"],
"compiler" => "$CC",
"cmd" => ["$CC", "-c", "$CFLAGS", "-I$[CPPPATH]"])
it "expands to the string itself if the string is not a variable reference" do
v.expand_varref("CC").should == "CC"
v.expand_varref("CPPPATH").should == "CPPPATH"
v.expand_varref("str").should == "str"
end
it "expands a single variable reference beginning with a '$'" do
v.expand_varref("$CC").should == "gcc"
v.expand_varref("$CPPPATH").should == ["dir1", "dir2"]
end
it "expands a single variable reference in $[arr] notation" do
v.expand_varref("prefix$[CFLAGS]suffix").should == ["prefix-Wallsuffix", "prefix-O2suffix"]
v.expand_varref(v["cmd"]).should == ["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"]
end
it "expands a variable reference recursively" do
v.expand_varref("$compiler").should == "gcc"
v.expand_varref("$cmd").should == ["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"]
end
it "raises an error when array notation is applied to a non-array variable" do
expect { v.expand_varref("$[CC]") }.to raise_error /Array.expected/
end
it "raises an error when a variable reference refers to a non-existent variable" do
expect { v.expand_varref("$not_here") }.to raise_error /Could.not.find.variable..not_here/
end
end
end
end
+4
View File
@@ -1 +1,5 @@
require "simplecov"
SimpleCov.start
require "rscons" require "rscons"