Compare commits

...
48 Commits
Author SHA1 Message Date
josh 6acb209ef4 v1.6.1 2014-08-19 09:12:35 -04:00
josh 43e12819d7 update .gitignore, version Gemfile.lock 2014-08-19 09:12:21 -04:00
josh 1bfb7dd100 add v1.6.1 release notes 2014-08-19 09:09:37 -04:00
josh df1e3be829 fix #12 - allow overriding dependency file suffix with DEPFILESUFFIX construction variable 2014-08-18 15:53:02 -04:00
josh 851adae66f fix #13 - Environment#depends should expand its arguments for construction variables 2014-08-18 15:39:33 -04:00
josh eb28e09d8c add v1.6.0 release notes 2014-07-10 14:34:35 -04:00
josh 01a84e8819 v1.6.0 2014-06-18 10:45:05 -04:00
josh 28bedfce11 Support lambdas as construction variable values 2014-06-18 10:43:35 -04:00
josh e792fd473b add v1.5.0 release notes 2014-06-18 09:15:49 -04:00
josh 9f738ee175 v1.5.0 2014-06-16 22:20:06 -04:00
josh 912615535a added more documentation to get to 100% YARD coverage; fix YARD warnings 2014-06-16 15:33:22 -04:00
josh d87c4990b1 add redcarpet to bundle 2014-06-16 14:40:43 -04:00
josh 6a17cbcd54 remove unnecessary rspec dependencies 2014-06-16 14:37:24 -04:00
josh 8fc95b5d69 change Builder#create_build_target and BuildTarget#initialize to accept a Hash of options 2014-06-16 12:17:33 -04:00
josh d9eea10363 update D build test for Linux 2014-06-12 16:00:41 -04:00
josh 5045e2e336 fix build test for Linux 2014-06-12 15:59:49 -04:00
josh 31968e7e32 complete spec updates for RSpec 3 using only :expect syntax 2014-06-12 15:52:15 -04:00
josh 5783ed993a update more rspec expectations 2014-06-12 15:43:50 -04:00
josh 0778f043ee update many specs to RSpec 3 format 2014-06-12 15:39:20 -04:00
josh 42597b2dac use new rspec expect syntax 2014-06-12 13:54:35 -04:00
josh 7b13d51d23 environment spec updates for Windows 2014-06-12 13:41:46 -04:00
josh c2d4ece957 build test spec fix for running on Windows 2014-06-12 13:38:15 -04:00
josh 3ab9c8723b fix a build test for MinGW 2014-06-12 13:35:11 -04:00
josh 1b50a9dd3c add Rscons::BuildTarget
Return a BuildTarget object from Environment#method_missing when a build target is registered.
2014-06-12 13:31:17 -04:00
josh 87b25d7bfd add PROGSUFFIX construction variable and default it to ".exe" for mingw/cygwin platforms 2014-06-12 13:02:53 -04:00
josh da457b9138 fix spec typo 2014-06-12 11:31:56 -04:00
josh 0cbf05da49 retry directory removal to work around Windows' inability to remove directories when you ask 2014-06-12 11:31:17 -04:00
josh 08b8a2e6be update gemspec, do not use git to determine files 2014-06-12 11:13:56 -04:00
josh 9f75ea9d21 add Rscons.command_executer to determine command executer
Use "env" to execute commands when in MSYS on Windows
2014-06-12 11:07:09 -04:00
josh ef18c9da35 Environment: add #parse_flags, #parse_flags!, and #merge_flags 2014-06-12 10:40:01 -04:00
josh 7c9e7e833a Unbuffer $stdout 2014-06-12 09:36:24 -04:00
josh eb9aa476b7 update default construction variables to match SCons a little more closely 2014-06-11 16:54:32 -04:00
josh 7f2b685bfb shorten Environment#shell code a bit 2014-06-11 15:31:13 -04:00
josh ae9021a505 add Environment#shell - close #11 2014-06-11 14:59:31 -04:00
josh a1a4cc18cd change json to a runtime gem dependency 2014-06-05 09:19:59 -04:00
josh f5a3b448d3 test supported construction variable types when cloning 2014-05-15 15:54:02 -04:00
josh 9abb47ffe7 v1.4.3 2014-05-14 15:53:52 -04:00
josh f78d13b648 add v1.4.3 release notes 2014-05-14 15:53:33 -04:00
josh ab1ea34941 add specs testing construction variable overrides for built-in builders 2014-05-14 15:48:00 -04:00
josh 08f2248ed7 fix up builders to properly use variable overrides 2014-05-14 15:15:08 -04:00
josh 9d61fa0276 merge Environment#build_command and #expand_varref 2014-05-14 14:33:09 -04:00
josh c0adb64c5d v1.4.2 2014-05-09 10:32:22 -04:00
josh 95da14e029 add release notes in README.md 2014-05-09 10:31:54 -04:00
josh 58b41df426 expand target and sources for construction variable references before invoking builder 2014-05-09 09:45:29 -04:00
josh bd525e2742 rename Environment#clean_target_paths! -> #expand_paths! 2014-05-09 09:19:40 -04:00
josh c08a2243b9 add Environment#expand_path 2014-05-05 16:22:07 -04:00
josh 0de1e8e970 v1.4.1 2014-04-17 12:03:29 -04:00
josh 6f20a1da3f bugfix: support invoking builders with no sources and a build root defined 2014-04-17 12:03:09 -04:00
28 changed files with 1537 additions and 493 deletions

No files matched your search

+6 -18
View File
@@ -1,18 +1,6 @@
*.gem /.yardoc/
*.rbc /_yardoc/
.bundle /coverage/
.config /doc/
.yardoc /pkg/
Gemfile.lock /build_test_run/
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
build_test_run
+45
View File
@@ -0,0 +1,45 @@
PATH
remote: .
specs:
rscons (1.6.1)
json (~> 1.0)
GEM
remote: https://rubygems.org/
specs:
diff-lcs (1.2.5)
docile (1.1.5)
json (1.8.1)
multi_json (1.10.1)
rake (10.3.2)
redcarpet (3.1.2)
rspec (3.0.0)
rspec-core (~> 3.0.0)
rspec-expectations (~> 3.0.0)
rspec-mocks (~> 3.0.0)
rspec-core (3.0.1)
rspec-support (~> 3.0.0)
rspec-expectations (3.0.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.0.0)
rspec-mocks (3.0.1)
rspec-support (~> 3.0.0)
rspec-support (3.0.0)
simplecov (0.8.2)
docile (~> 1.1.0)
multi_json
simplecov-html (~> 0.8.0)
simplecov-html (0.8.0)
yard (0.8.7.4)
PLATFORMS
ruby
x86-mingw32
DEPENDENCIES
rake
redcarpet
rscons!
rspec
simplecov
yard
+38 -2
View File
@@ -268,8 +268,8 @@ env.Preprocess(target, source)
env.Preprocess("module-preprocessed.cc", "module.cc") env.Preprocess("module-preprocessed.cc", "module.cc")
``` ```
The Preprocess builder invokes either ${CC} or ${CXX} (depending on if the The Preprocess builder invokes either `${CC}` or `${CXX}` (depending on if the
source contains an extension in ${CXXSUFFIX} or not) and writes the source contains an extension in `${CXXSUFFIX}` or not) and writes the
preprocessed output to the target file. preprocessed output to the target file.
#### Program #### Program
@@ -329,6 +329,42 @@ http://rubydoc.info/github/holtrop/rscons/frames.
## Release Notes ## Release Notes
### v1.6.1
- add DEPFILESUFFIX construction variable to override dependency file suffix
- fix Environment#depends to expand its arguments for construction variables
### v1.6.0
- support lambdas as construction variable values
### v1.5.0
- add "json" as a runtime dependency
- update construction variables to match SCons more closely
- add CPPDEFPREFIX, INCPREFIX, CPPDEFINES, CCFLAGS, LIBDIRPREFIX, and LIBLINKPREFIX
- add Environment#shell
- add Environment#parse_flags, #parse_flags!, #merge_flags
- unbuffer $stdout by default
- add PROGSUFFIX construction variable (defaults to .exe on MinGW/Cygwin)
- add Rscons::BuildTarget and Builder#create_build_target
- update specs to RSpec 3.x and fix to run on MinGW/Cygwin/Linux
- add YARD documentation to get to 100% coverage
### v1.4.3
- fix builders properly using construction variable overrides
- expand nil construction variables to empty strings
### v1.4.2
- add Environment#expand_path
- expand construction variable references in builder targets and sources before invoking builder
### v1.4.1
- fix invoking a builder with no sources while a build root defined
### v1.4.0 ### v1.4.0
- add CFile builder - add CFile builder
+75 -5
View File
@@ -1,3 +1,4 @@
require_relative "rscons/build_target"
require_relative "rscons/builder" require_relative "rscons/builder"
require_relative "rscons/cache" require_relative "rscons/cache"
require_relative "rscons/environment" require_relative "rscons/environment"
@@ -14,6 +15,8 @@ require_relative "rscons/builders/program"
# Namespace module for rscons classes # Namespace module for rscons classes
module Rscons module Rscons
# Names of the default builders which will be added to all newly created
# {Environment} objects.
DEFAULT_BUILDERS = [ DEFAULT_BUILDERS = [
:CFile, :CFile,
:Disassemble, :Disassemble,
@@ -23,9 +26,10 @@ module Rscons
:Program, :Program,
] ]
# Class to represent a fatal error while building a target.
class BuildError < RuntimeError; end class BuildError < RuntimeError; end
# Remove all generated files # Remove all generated files.
def self.clean def self.clean
cache = Cache.instance cache = Cache.instance
# remove all built files # remove all built files
@@ -42,16 +46,82 @@ module Rscons
cache.clear cache.clear
end end
# Return whether the given path is an absolute filesystem path or not # Return whether the given path is an absolute filesystem path.
# @param path [String] the path to examine #
# @param path [String] the path to examine.
#
# @return [Boolean] Whether the given path is an absolute filesystem path.
def self.absolute_path?(path) def self.absolute_path?(path)
path =~ %r{^(/|\w:[\\/])} path =~ %r{^(/|\w:[\\/])}
end end
# Return a new path by changing the suffix in path to suffix. # Return a new path by changing the suffix in path to suffix.
# @param path [String] the path to alter #
# @param suffix [String] the new filename suffix # @param path [String] The path to alter.
# @param suffix [String] The new filename suffix, e.g. ".exe".
#
# @return [String] New path.
def self.set_suffix(path, suffix) def self.set_suffix(path, suffix)
path.sub(/\.[^.]*$/, suffix) path.sub(/\.[^.]*$/, suffix)
end end
# Return the system shell and arguments for executing a shell command.
#
# @return [Array<String>] The shell and flag.
def self.get_system_shell
@@shell ||=
begin
test_shell = lambda do |*args|
begin
"success" == IO.popen([*args, "echo success"]) do |io|
io.read.strip
end end
rescue
false
end
end
if ENV["SHELL"] and ENV["SHELL"] != "" and test_shell[ENV["SHELL"], "-c"]
[ENV["SHELL"], "-c"]
elsif Object.const_get("RUBY_PLATFORM") =~ /mingw/
if test_shell["sh", "-c"]
# Using Rscons from MSYS should use MSYS's shell.
["sh", "-c"]
else
["cmd", "/c"]
end
else
["sh", "-c"]
end
end
end
# Return an Array containing a command used to execute commands.
#
# This will normally be an empty Array, but on Windows if Rscons detects
# that it is running in MSYS then ["env"] will be returned.
#
# @return [Array<String>] Command used to execute commands.
def self.command_executer
@@command_executer ||=
if Object.const_get("RUBY_PLATFORM") =~ /mingw/
if ENV.keys.find {|key| key =~ /MSYS/}
begin
if IO.popen(["env", "echo", "success"]) {|io| io.read.strip} == "success"
["env"]
end
rescue
end
end
end || []
end
# Set the command executer array.
#
# @param val [Array<String>] Command used to execute commands.
def self.command_executer=(val)
@@command_executer = val
end
end
# Unbuffer $stdout
$stdout.sync = true
+36
View File
@@ -0,0 +1,36 @@
module Rscons
# The BuildTarget class represents a single build target.
class BuildTarget
# Create a BuildTarget object.
#
# @param options [Hash] Options to create the BuildTarget with.
# @option options [Environment] :env
# The Environment.
# @option options [String] :target
# The user-supplied target name.
# @option options [Array<String>] :sources
# The user-supplied source file name(s).
def initialize(options)
@env = options[:env]
@target = options[:target]
end
# Manually record a given target as depending on the specified files.
#
# @param user_deps [Array<String>] Dependency files.
#
# @return [void]
def depends(*user_deps)
@env.depends(@target, *user_deps)
end
# Convert the BuildTarget to a String.
#
# This method always returns the target file name.
#
# @return [String] Target file name.
def to_s
@target
end
end
end
+41 -3
View File
@@ -7,30 +7,68 @@ 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
# Return the name of the builder. # Return the name of the builder.
#
# If not overridden this defaults to the last component of the class name. # If not overridden this defaults to the last component of the class name.
def name def name
self.class.name.split(":").last self.class.name.split(":").last
end end
# Return a set of default variable values for the Environment to use # Return a set of default construction variables for the builder.
# unless the user overrides any. #
# @param env [Environment] The Environment. # @param env [Environment] The Environment.
#
# @return [Hash] Default construction variables.
def default_variables(env) def default_variables(env)
{} {}
end end
# Create a BuildTarget object for this build target.
#
# Builder sub-classes can override this method to manipulate parameters
# (for example, add a suffix to the user-given target file name).
#
# @param options [Hash] Options to create the BuildTarget with.
# @option options [Environment] :env
# The Environment.
# @option options [String] :target
# The user-supplied target name.
# @option options [Array<String>] :sources
# The user-supplied source file name(s).
#
# @return [BuildTarget]
def create_build_target(options)
BuildTarget.new(options)
end
# Return whether this builder object is capable of producing a given target # Return whether this builder object is capable of producing a given target
# file name from a given source file name. # file name from a given source file name.
#
# @param target [String] The target file name. # @param target [String] The target file name.
# @param source [String, Array] The source file name(s). # @param source [String, Array] The source file name(s).
# @param env [Environment] The Environment. # @param env [Environment] The Environment.
#
# @return [Boolean]
# Whether this builder object is capable of producing a given target
# file name from a given source file name.
def produces?(target, source, env) 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 # Check if the cache is up to date for the target and if not execute the
# build command. # build command.
# Return the name of the target or false on failure. #
# @param short_cmd_string [String]
# Short description of build action to be printed when env.echo ==
# :short.
# @param target [String] Name of the target file.
# @param command [Array<String>]
# The command to execute to build the target.
# @param sources [Array<String>] Source file name(s).
# @param env [Environment] The Environment executing the builder.
# @param cache [Cache] The Cache object.
#
# @return [String,false]
# The name of the target on success or false on failure.
def standard_build(short_cmd_string, target, command, sources, env, cache) def standard_build(short_cmd_string, target, command, sources, env, cache)
unless cache.up_to_date?(target, command, sources, env) unless cache.up_to_date?(target, command, sources, env)
cache.mkdir_p(File.dirname(target)) cache.mkdir_p(File.dirname(target))
+16 -1
View File
@@ -7,6 +7,11 @@ module Rscons
# env.CFile("parser.tab.cc", "parser.yy") # env.CFile("parser.tab.cc", "parser.yy")
# env.CFile("lex.yy.cc", "parser.ll") # env.CFile("lex.yy.cc", "parser.ll")
class CFile < Builder class CFile < Builder
# Return default construction variables for the builder.
#
# @param env [Environment] The Environment using the builder.
#
# @return [Hash] Default construction variables for the builder.
def default_variables(env) def default_variables(env)
{ {
"YACC" => "bison", "YACC" => "bison",
@@ -18,6 +23,16 @@ module Rscons
} }
end end
# Run the builder to produce a build target.
#
# @param target [String] Target file name.
# @param sources [Array<String>] Source file name(s).
# @param cache [Cache] The Cache object.
# @param env [Environment] The Environment executing the builder.
# @param vars [Hash,VarSet] Extra construction variables.
#
# @return [String,false]
# Name of the target file on success or false on failure.
def run(target, sources, cache, env, vars) def run(target, sources, cache, env, vars)
vars = vars.merge({ vars = vars.merge({
"_TARGET" => target, "_TARGET" => target,
@@ -32,7 +47,7 @@ module Rscons
else else
raise "Unknown source file #{sources.first.inspect} for CFile builder" raise "Unknown source file #{sources.first.inspect} for CFile builder"
end end
command = env.build_command(env["#{cmd}_CMD"], vars) command = env.build_command("${#{cmd}_CMD}", vars)
standard_build("#{cmd} #{target}", target, command, sources, env, cache) standard_build("#{cmd} #{target}", target, command, sources, env, cache)
end end
end end
+16 -1
View File
@@ -2,6 +2,11 @@ module Rscons
module Builders module Builders
# The Disassemble builder produces a disassembly listing of a source file. # The Disassemble builder produces a disassembly listing of a source file.
class Disassemble < Builder class Disassemble < Builder
# Return default construction variables for the builder.
#
# @param env [Environment] The Environment using the builder.
#
# @return [Hash] Default construction variables for the builder.
def default_variables(env) def default_variables(env)
{ {
"OBJDUMP" => "objdump", "OBJDUMP" => "objdump",
@@ -10,9 +15,19 @@ module Rscons
} }
end end
# Run the builder to produce a build target.
#
# @param target [String] Target file name.
# @param sources [Array<String>] Source file name(s).
# @param cache [Cache] The Cache object.
# @param env [Environment] The Environment executing the builder.
# @param vars [Hash,VarSet] Extra construction variables.
#
# @return [String,false]
# Name of the target file on success or false on failure.
def run(target, sources, cache, env, vars) def run(target, sources, cache, env, vars)
vars = vars.merge("_SOURCES" => sources) vars = vars.merge("_SOURCES" => sources)
command = env.build_command(env["DISASM_CMD"], vars) command = env.build_command("${DISASM_CMD}", vars)
unless cache.up_to_date?(target, command, sources, env) unless cache.up_to_date?(target, command, sources, env)
cache.mkdir_p(File.dirname(target)) cache.mkdir_p(File.dirname(target))
return false unless env.execute("Disassemble #{target}", command, options: {out: target}) return false unless env.execute("Disassemble #{target}", command, options: {out: target})
+19 -4
View File
@@ -2,24 +2,39 @@ 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 Library < Builder class Library < Builder
# Return default construction variables for the builder.
#
# @param env [Environment] The Environment using the builder.
#
# @return [Hash] Default construction variables for the builder.
def default_variables(env) def default_variables(env)
{ {
'AR' => 'ar', 'AR' => 'ar',
'LIBSUFFIX' => '.a', 'LIBSUFFIX' => '.a',
'ARFLAGS' => [], 'ARFLAGS' => ['rcs'],
'ARCMD' => ['${AR}', 'rcs', '${ARFLAGS}', '${_TARGET}', '${_SOURCES}'] 'ARCMD' => ['${AR}', '${ARFLAGS}', '${_TARGET}', '${_SOURCES}']
} }
end end
# Run the builder to produce a build target.
#
# @param target [String] Target file name.
# @param sources [Array<String>] Source file name(s).
# @param cache [Cache] The Cache object.
# @param env [Environment] The Environment executing the builder.
# @param vars [Hash,VarSet] Extra construction variables.
#
# @return [String,false]
# Name of the target file on success or false on failure.
def run(target, sources, cache, env, vars) def run(target, sources, cache, env, vars)
# build sources to linkable objects # build sources to linkable objects
objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars) objects = env.build_sources(sources, env.expand_varref(["${OBJSUFFIX}", "${LIBSUFFIX}"], vars).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['ARCMD'], vars) command = env.build_command("${ARCMD}", vars)
standard_build("AR #{target}", target, command, objects, env, cache) standard_build("AR #{target}", target, command, objects, env, cache)
end end
end end
+41 -8
View File
@@ -3,6 +3,7 @@ module Rscons
# 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
# Mapping of known sources from which to build object files.
KNOWN_SUFFIXES = { KNOWN_SUFFIXES = {
"AS" => "ASSUFFIX", "AS" => "ASSUFFIX",
"CC" => "CSUFFIX", "CC" => "CSUFFIX",
@@ -10,9 +11,18 @@ module Rscons
"DC" => "DSUFFIX", "DC" => "DSUFFIX",
} }
# Return default construction variables for the builder.
#
# @param env [Environment] The Environment using the builder.
#
# @return [Hash] Default construction variables for the builder.
def default_variables(env) def default_variables(env)
{ {
'OBJSUFFIX' => '.o', 'OBJSUFFIX' => '.o',
'DEPFILESUFFIX' => '.mf',
'CPPDEFPREFIX' => '-D',
'INCPREFIX' => '-I',
'AS' => '${CC}', 'AS' => '${CC}',
'ASFLAGS' => [], 'ASFLAGS' => [],
@@ -20,49 +30,72 @@ module Rscons
'ASPPPATH' => '${CPPPATH}', 'ASPPPATH' => '${CPPPATH}',
'ASPPFLAGS' => '${CPPFLAGS}', 'ASPPFLAGS' => '${CPPFLAGS}',
'ASDEPGEN' => ['-MMD', '-MF', '${_DEPFILE}'], 'ASDEPGEN' => ['-MMD', '-MF', '${_DEPFILE}'],
'ASCMD' => ['${AS}', '-c', '-o', '${_TARGET}', '${ASDEPGEN}', '-I${ASPPPATH}', '${ASPPFLAGS}', '${ASFLAGS}', '${_SOURCES}'], 'ASCMD' => ['${AS}', '-c', '-o', '${_TARGET}', '${ASDEPGEN}', '${INCPREFIX}${ASPPPATH}', '${ASPPFLAGS}', '${ASFLAGS}', '${_SOURCES}'],
'CPPFLAGS' => [], 'CPPFLAGS' => ['${CPPDEFPREFIX}${CPPDEFINES}'],
'CPPDEFINES' => [],
'CPPPATH' => [], 'CPPPATH' => [],
'CCFLAGS' => [],
'CC' => 'gcc', 'CC' => 'gcc',
'CFLAGS' => [], 'CFLAGS' => [],
'CSUFFIX' => '.c', 'CSUFFIX' => '.c',
'CCDEPGEN' => ['-MMD', '-MF', '${_DEPFILE}'], 'CCDEPGEN' => ['-MMD', '-MF', '${_DEPFILE}'],
'CCCMD' => ['${CC}', '-c', '-o', '${_TARGET}', '${CCDEPGEN}', '-I${CPPPATH}', '${CPPFLAGS}', '${CFLAGS}', '${_SOURCES}'], 'CCCMD' => ['${CC}', '-c', '-o', '${_TARGET}', '${CCDEPGEN}', '${INCPREFIX}${CPPPATH}', '${CPPFLAGS}', '${CFLAGS}', '${CCFLAGS}', '${_SOURCES}'],
'CXX' => 'g++', 'CXX' => 'g++',
'CXXFLAGS' => [], 'CXXFLAGS' => [],
'CXXSUFFIX' => '.cc', 'CXXSUFFIX' => '.cc',
'CXXDEPGEN' => ['-MMD', '-MF', '${_DEPFILE}'], 'CXXDEPGEN' => ['-MMD', '-MF', '${_DEPFILE}'],
'CXXCMD' =>['${CXX}', '-c', '-o', '${_TARGET}', '${CXXDEPGEN}', '-I${CPPPATH}', '${CPPFLAGS}', '${CXXFLAGS}', '${_SOURCES}'], 'CXXCMD' =>['${CXX}', '-c', '-o', '${_TARGET}', '${CXXDEPGEN}', '${INCPREFIX}${CPPPATH}', '${CPPFLAGS}', '${CXXFLAGS}', '${CCFLAGS}', '${_SOURCES}'],
'DC' => 'gdc', 'DC' => 'gdc',
'DFLAGS' => [], 'DFLAGS' => [],
'DSUFFIX' => '.d', 'DSUFFIX' => '.d',
'D_IMPORT_PATH' => [], 'D_IMPORT_PATH' => [],
'DCCMD' => ['${DC}', '-c', '-o', '${_TARGET}', '-I${D_IMPORT_PATH}', '${DFLAGS}', '${_SOURCES}'], 'DCCMD' => ['${DC}', '-c', '-o', '${_TARGET}', '${INCPREFIX}${D_IMPORT_PATH}', '${DFLAGS}', '${_SOURCES}'],
} }
end end
# Return whether this builder object is capable of producing a given target
# file name from a given source file name.
#
# @param target [String] The target file name.
# @param source [String, Array] The source file name(s).
# @param env [Environment] The Environment.
#
# @return [Boolean]
# Whether this builder object is capable of producing a given target
# file name from a given source file name.
def produces?(target, source, env) def produces?(target, source, env)
target.end_with?(*env['OBJSUFFIX']) and KNOWN_SUFFIXES.find do |compiler, suffix_var| target.end_with?(*env['OBJSUFFIX']) and KNOWN_SUFFIXES.find do |compiler, suffix_var|
source.end_with?(*env[suffix_var]) source.end_with?(*env[suffix_var])
end end
end end
# Run the builder to produce a build target.
#
# @param target [String] Target file name.
# @param sources [Array<String>] Source file name(s).
# @param cache [Cache] The Cache object.
# @param env [Environment] The Environment executing the builder.
# @param vars [Hash,VarSet] Extra construction variables.
#
# @return [String,false]
# Name of the target file on success or false on failure.
def run(target, sources, cache, env, vars) def run(target, sources, cache, env, vars)
vars = vars.merge({ vars = vars.merge({
'_TARGET' => target, '_TARGET' => target,
'_SOURCES' => sources, '_SOURCES' => sources,
'_DEPFILE' => Rscons.set_suffix(target, '.mf'), '_DEPFILE' => Rscons.set_suffix(target, env.expand_varref("${DEPFILESUFFIX}", vars)),
}) })
com_prefix = KNOWN_SUFFIXES.find do |compiler, suffix_var| com_prefix = KNOWN_SUFFIXES.find do |compiler, suffix_var|
sources.first.end_with?(*env[suffix_var]) sources.first.end_with?(*env.expand_varref("${#{suffix_var}}"))
end.tap do |v| end.tap do |v|
v.nil? and raise "Error: unknown input file type: #{sources.first.inspect}" v.nil? and raise "Error: unknown input file type: #{sources.first.inspect}"
end.first end.first
command = env.build_command(env["#{com_prefix}CMD"], vars) command = env.build_command("${#{com_prefix}CMD}", vars)
unless cache.up_to_date?(target, command, sources, env) unless cache.up_to_date?(target, command, sources, env)
cache.mkdir_p(File.dirname(target)) cache.mkdir_p(File.dirname(target))
FileUtils.rm_f(target) FileUtils.rm_f(target)
+19 -4
View File
@@ -2,22 +2,37 @@ module Rscons
module Builders module Builders
# The Preprocess builder invokes the C preprocessor # The Preprocess builder invokes the C preprocessor
class Preprocess < Builder class Preprocess < Builder
# Return default construction variables for the builder.
#
# @param env [Environment] The Environment using the builder.
#
# @return [Hash] Default construction variables for the builder.
def default_variables(env) def default_variables(env)
{ {
"CPP_CMD" => ["${_PREPROCESS_CC}", "-E", "-o", "${_TARGET}", "-I${CPPPATH}", "${CPPFLAGS}", "${CFLAGS}", "${_SOURCES}"], "CPP_CMD" => ["${_PREPROCESS_CC}", "-E", "-o", "${_TARGET}", "-I${CPPPATH}", "${CPPFLAGS}", "${CFLAGS}", "${_SOURCES}"],
} }
end end
# Run the builder to produce a build target.
#
# @param target [String] Target file name.
# @param sources [Array<String>] Source file name(s).
# @param cache [Cache] The Cache object.
# @param env [Environment] The Environment executing the builder.
# @param vars [Hash,VarSet] Extra construction variables.
#
# @return [String,false]
# Name of the target file on success or false on failure.
def run(target, sources, cache, env, vars) def run(target, sources, cache, env, vars)
pp_cc = if sources.find {|s| s.end_with?(*env["CXXSUFFIX"])} pp_cc = if sources.find {|s| s.end_with?(*env.expand_varref("${CXXSUFFIX}", vars))}
env["CXX"] "${CXX}"
else else
env["CC"] "${CC}"
end end
vars = vars.merge("_PREPROCESS_CC" => pp_cc, vars = vars.merge("_PREPROCESS_CC" => pp_cc,
"_TARGET" => target, "_TARGET" => target,
"_SOURCES" => sources) "_SOURCES" => sources)
command = env.build_command(env["CPP_CMD"], vars) command = env.build_command("${CPP_CMD}", vars)
standard_build("Preprocess #{target}", target, command, sources, env, cache) standard_build("Preprocess #{target}", target, command, sources, env, cache)
end end
end end
+52 -11
View File
@@ -3,37 +3,78 @@ module Rscons
# 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 Program < Builder class Program < Builder
# Return default construction variables for the builder.
#
# @param env [Environment] The Environment using the builder.
#
# @return [Hash] Default construction variables for the builder.
def default_variables(env) def default_variables(env)
{ {
'LD' => nil,
'OBJSUFFIX' => '.o', 'OBJSUFFIX' => '.o',
'PROGSUFFIX' => (Object.const_get("RUBY_PLATFORM") =~ /mingw|cygwin/ ? ".exe" : ""),
'LD' => nil,
'LIBSUFFIX' => '.a', 'LIBSUFFIX' => '.a',
'LDFLAGS' => [], 'LDFLAGS' => [],
'LIBPATH' => [], 'LIBPATH' => [],
'LIBDIRPREFIX' => '-L',
'LIBLINKPREFIX' => '-l',
'LIBS' => [], 'LIBS' => [],
'LDCMD' => ['${LD}', '-o', '${_TARGET}', '${LDFLAGS}', '${_SOURCES}', '-L${LIBPATH}', '-l${LIBS}'] 'LDCMD' => ['${LD}', '-o', '${_TARGET}', '${LDFLAGS}', '${_SOURCES}', '${LIBDIRPREFIX}${LIBPATH}', '${LIBLINKPREFIX}${LIBS}']
} }
end end
# Create a BuildTarget object for this build target.
#
# The build target filename is given a ".exe" suffix if Rscons is
# executing on a Windows platform and no other suffix is given.
#
# @param options [Hash] Options to create the BuildTarget with.
# @option options [Environment] :env
# The Environment.
# @option options [String] :target
# The user-supplied target name.
# @option options [Array<String>] :sources
# The user-supplied source file name(s).
#
# @return [BuildTarget]
def create_build_target(options)
my_options = options.dup
unless my_options[:target] =~ /\./
my_options[:target] += options[:env].expand_varref("${PROGSUFFIX}")
end
super(my_options)
end
# Run the builder to produce a build target.
#
# @param target [String] Target file name.
# @param sources [Array<String>] Source file name(s).
# @param cache [Cache] The Cache object.
# @param env [Environment] The Environment executing the builder.
# @param vars [Hash,VarSet] Extra construction variables.
#
# @return [String,false]
# Name of the target file on success or false on failure.
def run(target, sources, cache, env, vars) def run(target, sources, cache, env, vars)
# build sources to linkable objects # build sources to linkable objects
objects = env.build_sources(sources, [env['OBJSUFFIX'], env['LIBSUFFIX']].flatten, cache, vars) objects = env.build_sources(sources, env.expand_varref(["${OBJSUFFIX}", "${LIBSUFFIX}"], vars).flatten, cache, vars)
return false unless objects return false unless objects
ld = if env["LD"] ld = env.expand_varref("${LD}", vars)
env["LD"] ld = if ld != ""
elsif sources.find {|s| s.end_with?(*env["DSUFFIX"])} ld
env["DC"] elsif sources.find {|s| s.end_with?(*env.expand_varref("${DSUFFIX}", vars))}
elsif sources.find {|s| s.end_with?(*env["CXXSUFFIX"])} "${DC}"
env["CXX"] elsif sources.find {|s| s.end_with?(*env.expand_varref("${CXXSUFFIX}", vars))}
"${CXX}"
else else
env["CC"] "${CC}"
end end
vars = vars.merge({ vars = vars.merge({
'_TARGET' => target, '_TARGET' => target,
'_SOURCES' => objects, '_SOURCES' => objects,
'LD' => ld, 'LD' => ld,
}) })
command = env.build_command(env['LDCMD'], vars) command = env.build_command("${LDCMD}", vars)
standard_build("LD #{target}", target, command, objects, env, cache) standard_build("LD #{target}", target, command, objects, env, cache)
end end
end end
+26 -6
View File
@@ -84,7 +84,8 @@ module Rscons
@dirty = false @dirty = false
end end
# Check if target(s) are up to date # Check if target(s) are up to date.
#
# @param targets [String, Array] The name(s) of the target file(s). # @param targets [String, Array] The name(s) of the target file(s).
# @param command [String, 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.
@@ -93,6 +94,7 @@ module Rscons
# @option options [Boolean] :strict_deps # @option options [Boolean] :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 [Boolean] # @return [Boolean]
# True value if the targets are all up to date, meaning that, # True value if the targets are all up to date, meaning that,
# for each target: # for each target:
@@ -144,11 +146,14 @@ module Rscons
true true
end end
# Store cache information about target(s) built by a builder # Store cache information about target(s) built by a builder.
#
# @param targets [String, Array] The name of the target(s) built. # @param targets [String, Array] The name of the target(s) built.
# @param command [String, 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}.
#
# @return [void]
def register_build(targets, command, deps, env) def register_build(targets, command, deps, env)
Array(targets).each do |target| Array(targets).each do |target|
@cache["targets"][target] = { @cache["targets"][target] = {
@@ -171,13 +176,19 @@ module Rscons
end end
end end
# Return a list of targets that have been built # Return a list of targets that have been built.
#
# @return [Array<String>] List of targets that have been built.
def targets def targets
@cache["targets"].keys @cache["targets"].keys
end end
# Make any needed directories and record the ones that are created for # Make any needed directories and record the ones that are created for
# removal upon a "clean" operation. # removal upon a "clean" operation.
#
# @param path [String] Directory to create.
#
# @return [void]
def mkdir_p(path) def mkdir_p(path)
parts = path.split(/[\\\/]/) parts = path.split(/[\\\/]/)
parts.each_index do |i| parts.each_index do |i|
@@ -191,7 +202,10 @@ module Rscons
end end
end end
# Return a list of directories which were created as a part of the build # Return a list of directories which were created as a part of the build.
#
# @return [Array<String>]
# List of directories which were created as a part of the build.
def directories def directories
@cache["directories"].keys @cache["directories"].keys
end end
@@ -213,14 +227,20 @@ module Rscons
end end
# Return a file's checksum, or the previously calculated checksum for # Return a file's checksum, or the previously calculated checksum for
# the same file # the same file.
#
# @param file [String] The file name. # @param file [String] The file name.
#
# @return [String] The file's checksum.
def lookup_checksum(file) def lookup_checksum(file)
@lookup_checksums[file] || calculate_checksum(file) @lookup_checksums[file] || calculate_checksum(file)
end end
# Calculate and return a file's checksum # Calculate and return a file's checksum.
#
# @param file [String] The file name. # @param file [String] The file name.
#
# @return [String] The file's checksum.
def calculate_checksum(file) def calculate_checksum(file)
@lookup_checksums[file] = Digest::MD5.hexdigest(File.read(file, mode: "rb")) rescue "" @lookup_checksums[file] = Digest::MD5.hexdigest(File.read(file, mode: "rb")) rescue ""
end end
+302 -48
View File
@@ -1,30 +1,39 @@
require 'set' require "fileutils"
require 'fileutils' require "set"
require "shellwords"
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
# Hash of +{"builder_name" => builder_object}+ pairs. # @return [Hash] Set of \{"builder_name" => builder_object} pairs.
attr_reader :builders attr_reader :builders
# :command, :short, or :off # :command, :short, or :off
attr_accessor :echo attr_accessor :echo
# String or +nil+ # @return [String, nil] The build root.
attr_reader :build_root attr_reader :build_root
# Set the build root.
#
# @param build_root [String] The build root.
def build_root=(build_root) def build_root=(build_root)
@build_root = build_root @build_root = build_root
@build_root.gsub!('\\', '/') if @build_root @build_root.gsub!('\\', '/') if @build_root
end end
# Create an Environment object. # Create an Environment object.
#
# @param options [Hash] # @param options [Hash]
# Possible options keys: # @option options [Symbol] :echo
# :echo => :command, :short, or :off (default :short) # :command, :short, or :off (default :short)
# :build_root => String specifying build root directory (default nil) # @option options [String] :build_root
# :exclude_builders => true to omit adding default builders (default false) # Build root directory (default nil)
# @option options [Boolean] :exclude_builders
# Whether to omit adding default builders (default false)
#
# 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(options = {}) def initialize(options = {})
@@ -107,6 +116,10 @@ module Rscons
end end
# Add a {Builder} object to the Environment. # Add a {Builder} object to the Environment.
#
# @param builder [Builder] The {Builder} object to add.
#
# @return [void]
def add_builder(builder) def add_builder(builder)
@builders[builder.name] = builder @builders[builder.name] = builder
var_defs = builder.default_variables(self) var_defs = builder.default_variables(self)
@@ -118,19 +131,45 @@ module Rscons
end end
# Add a build hook to the Environment. # Add a build hook to the Environment.
#
# @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_build_hook(&block) def add_build_hook(&block)
@build_hooks << block @build_hooks << block
end 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.
#
# @param src_dir [String] Path to the source directory.
# @param obj_dir [String] Path to the object directory.
#
# @return [void]
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)
@build_dirs << [src_dir, obj_dir] @build_dirs << [src_dir, obj_dir]
end end
# Return the file name to be built from source_fname with suffix suffix. # Return the file name to be built from +source_fname+ with suffix
# +suffix+.
#
# This method takes into account the Environment's build directories. # This method takes into account the Environment's build directories.
#
# @param source_fname [String] Source file name.
# @param suffix [String] Suffix, including "." if desired.
#
# @return [String]
# The file name to be built from +source_fname+ with suffix +suffix+.
def get_build_fname(source_fname, suffix) def get_build_fname(source_fname, suffix)
build_fname = Rscons.set_suffix(source_fname, suffix).gsub('\\', '/') build_fname = Rscons.set_suffix(source_fname, suffix).gsub('\\', '/')
found_match = @build_dirs.find do |src_dir, obj_dir| found_match = @build_dirs.find do |src_dir, obj_dir|
@@ -150,29 +189,35 @@ module Rscons
end end
# Access a construction variable or environment option. # Access a construction variable or environment option.
#
# @see VarSet#[] # @see VarSet#[]
def [](*args) def [](*args)
@varset.send(:[], *args) @varset.send(:[], *args)
end end
# Set a construction variable or environment option. # Set a construction variable or environment option.
#
# @see VarSet#[]= # @see VarSet#[]=
def []=(*args) def []=(*args)
@varset.send(:[]=, *args) @varset.send(:[]=, *args)
end end
# Add a set of construction variables or environment options. # Add a set of construction variables or environment options.
#
# @see VarSet#append # @see VarSet#append
def append(*args) def append(*args)
@varset.append(*args) @varset.append(*args)
end end
# Build all target specified in the Environment. # Build all build targets specified in the Environment.
#
# When a block is passed to Environment.new, this method is automatically # When a block is passed to Environment.new, this method is automatically
# called after the block returns. # called after the block returns.
#
# @return [void]
def process def process
unless @targets.empty? unless @targets.empty?
clean_target_paths! expand_paths!
cache = Cache.instance cache = Cache.instance
cache.clear_checksum_cache! cache.clear_checksum_cache!
targets_processed = {} targets_processed = {}
@@ -209,30 +254,35 @@ module Rscons
@targets = {} @targets = {}
end end
# Build a command line from the given template, resolving references to # Expand a construction variable reference.
# variables using the Environment's construction variables and any extra #
# variables specified. # @param varref [Array, String] Variable reference to expand.
# @param command_template [Array] template for the command with variable # @param extra_vars [Hash, VarSet]
# references # Extra variables to use in addition to (or replace) the Environment's
# @param extra_vars [Hash, VarSet] extra variables to use in addition to # construction variables when expanding the variable reference.
# (or replace) the Environment's construction variables when building #
# the command # @return [Array, String] Expansion of the variable reference.
def build_command(command_template, extra_vars) def expand_varref(varref, extra_vars = nil)
@varset.merge(extra_vars).expand_varref(command_template) vars = if extra_vars.nil?
@varset
else
@varset.merge(extra_vars)
end end
lambda_args = [env: self, vars: vars]
# Expand a construction variable reference (String or Array) vars.expand_varref(varref, lambda_args)
def expand_varref(varref)
@varset.expand_varref(varref)
end end
alias_method :build_command, :expand_varref
# Execute a builder command # 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 # mode is set to :short
# @param command [Array] The command to execute. # @param command [Array] The command to execute.
# @param options [Hash] Optional options, possible keys: # @param options [Hash] Optional options, possible keys:
# - :env - environment Hash to pass to Kernel#system. # - :env - environment Hash to pass to Kernel#system.
# - :options - options Hash to pass to Kernel#system. # - :options - options Hash to pass to Kernel#system.
#
# @return [true,false,nil] Return value from Kernel.system().
def execute(short_desc, command, options = {}) def execute(short_desc, command, options = {})
print_command = proc do print_command = proc do
puts command.map { |c| c =~ /\s/ ? "'#{c}'" : c }.join(' ') puts command.map { |c| c =~ /\s/ ? "'#{c}'" : c }.join(' ')
@@ -244,7 +294,7 @@ module Rscons
end end
env_args = options[:env] ? [options[:env]] : [] env_args = options[:env] ? [options[:env]] : []
options_args = options[:options] ? [options[:options]] : [] options_args = options[:options] ? [options[:options]] : []
system(*env_args, *command, *options_args).tap do |result| system(*env_args, *Rscons.command_executer, *command, *options_args).tap do |result|
unless result or @echo == :command unless result or @echo == :command
$stdout.write "Failed command was: " $stdout.write "Failed command was: "
print_command.call print_command.call
@@ -252,19 +302,39 @@ module Rscons
end end
end end
# Define a build target.
#
# @param method [Symbol] Method name.
# @param args [Array] Method arguments.
#
# @return [BuildTarget]
# The {BuildTarget} object registered, if the method called is a
# {Builder}.
def method_missing(method, *args) def method_missing(method, *args)
if @builders.has_key?(method.to_s) if @builders.has_key?(method.to_s)
target, sources, vars, *rest = args target, sources, vars, *rest = args
unless vars.nil? or vars.is_a?(Hash) or vars.is_a?(VarSet) unless vars.nil? or vars.is_a?(Hash) or vars.is_a?(VarSet)
raise "Unexpected construction variable set: #{vars.inspect}" raise "Unexpected construction variable set: #{vars.inspect}"
end end
sources = [sources] unless sources.is_a?(Array) sources = Array(sources)
add_target(target, @builders[method.to_s], sources, vars, rest) builder = @builders[method.to_s]
build_target = builder.create_build_target(env: self, target: target, sources: sources)
add_target(build_target.to_s, builder, sources, vars, rest)
build_target
else else
super super
end end
end end
# Add a build target.
#
# @param target [String] Build target file name.
# @param builder [Builder] The {Builder} to use to build the target.
# @param sources [Array<String>] Source file name(s).
# @param vars [Hash] Construction variable overrides.
# @param args [Object] Any extra arguments passed to the {Builder}.
#
# @return [void]
def add_target(target, builder, sources, vars, args) def add_target(target, builder, sources, vars, args)
@targets[target] = { @targets[target] = {
builder: builder, builder: builder,
@@ -274,27 +344,41 @@ module Rscons
} }
end end
# Manually record a given target as depending on the specified # Manually record a given target as depending on the specified files.
# dependency files. #
# @param target [String,BuildTarget] Target file.
# @param user_deps [Array<String>] Dependency files.
#
# @return [void]
def depends(target, *user_deps) def depends(target, *user_deps)
target = expand_varref(target.to_s)
user_deps = user_deps.map {|ud| expand_varref(ud)}
@user_deps[target] ||= [] @user_deps[target] ||= []
@user_deps[target] = (@user_deps[target] + user_deps).uniq @user_deps[target] = (@user_deps[target] + user_deps).uniq
end end
# Return the list of user dependencies for a given target, or +nil+ for # Return the list of user dependencies for a given target.
# none. #
# @param target [String] Target file name.
#
# @return [Array<String>,nil]
# List of user-specified dependencies for the target, or nil if none were
# specified.
def get_user_deps(target) def get_user_deps(target)
@user_deps[target] @user_deps[target]
end end
# 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.
# @param vars [Hash] Extra variables to pass to the builder. # @param vars [Hash] Extra variables to pass to the builder.
# Return a list of the converted file names. #
# @return [Array<String>] List of the converted file name(s).
def build_sources(sources, suffixes, cache, vars) def build_sources(sources, suffixes, cache, vars)
sources.map do |source| sources.map do |source|
if source.end_with?(*suffixes) if source.end_with?(*suffixes)
@@ -316,12 +400,14 @@ module Rscons
end end
# Invoke a builder to build the given target based on the given sources. # Invoke a builder to build the given target based on the given sources.
#
# @param builder [Builder] The Builder to use. # @param builder [Builder] The Builder to use.
# @param target [String] The target output file. # @param target [String] The target output file.
# @param sources [Array] List of source files. # @param sources [Array] List of source files.
# @param cache [Cache] The Cache. # @param cache [Cache] The Cache.
# @param vars [Hash] Extra variables to pass to the builder. # @param vars [Hash] Extra variables to pass to the builder.
# Return the result of the builder's run() method. #
# @return [String,false] Return value from the {Builder}'s +run+ method.
def run_builder(builder, target, sources, cache, vars) def run_builder(builder, target, sources, cache, vars)
vars = @varset.merge(vars) vars = @varset.merge(vars)
@build_hooks.each do |build_hook_block| @build_hooks.each do |build_hook_block|
@@ -337,31 +423,199 @@ module Rscons
builder.run(target, sources, cache, self, vars) builder.run(target, sources, cache, self, vars)
end end
private # Expand a path to be relative to the Environment's build root.
#
# Expand all target paths that begin with ^/ to be relative to the # Paths beginning with "^/" are expanded by replacing "^" with the
# Environment's build root, if present # Environment's build root.
def clean_target_paths! #
if @build_root # @param path [String] The path to expand.
expand = lambda do |path| #
# @return [String] The expanded path.
def expand_path(path)
path.sub(%r{^\^(?=[\\/])}, @build_root) path.sub(%r{^\^(?=[\\/])}, @build_root)
end end
new_targets = {} # Execute a command using the system shell.
@targets.each_pair do |target, target_params| #
target_params[:sources].map! do |source| # The shell is automatically determined but can be overridden by the SHELL
expand[source] # construction variable. If the SHELL construction variable is specified,
# the flag to pass to the shell is automatically dtermined but can be
# overridden by the SHELLFLAG construction variable.
#
# @param command [String] Command to execute.
#
# @return [String] The command's standard output.
def shell(command)
shell_cmd =
if self["SHELL"]
flag = self["SHELLFLAG"] || (self["SHELL"] == "cmd" ? "/c" : "-c")
[self["SHELL"], flag]
else
Rscons.get_system_shell
end end
new_targets[expand[target]] = target_params IO.popen([*shell_cmd, command]) do |io|
io.read
end end
@targets = new_targets end
# @!method parse_flags(flags)
# @!method parse_flags!(flags)
#
# Parse command-line flags for compilation/linking options into separate
# construction variables.
#
# The parsed construction variables are returned in a Hash instead of
# merging them directly to the Environment. They can be merged with
# {#merge_flags}. The {#parse_flags!} version immediately merges the parsed
# flags as well.
#
# Example:
# # Import FreeType build options
# env.parse_flags!("!freetype-config --cflags --libs")
#
# @param flags [String]
# String containing the flags to parse, or if the flags string begins
# with "!", a shell command to execute using {#shell} to obtain the
# flags to parse.
#
# @return [Hash] Set of construction variables to append.
def parse_flags(flags)
if flags =~ /^!(.*)$/
flags = shell($1)
end
rv = {}
words = Shellwords.split(flags)
skip = false
words.each_with_index do |word, i|
if skip
skip = false
next
end
append = lambda do |var, val|
rv[var] ||= []
rv[var] += val
end
handle = lambda do |var, val|
if val.nil? or val.empty?
val = words[i + 1]
skip = true
end
if val and not val.empty?
append[var, [val]]
end
end
if word == "-arch"
if val = words[i + 1]
append["CCFLAGS", ["-arch", val]]
append["LDFLAGS", ["-arch", val]]
end
skip = true
elsif word =~ /^#{self["CPPDEFPREFIX"]}(.*)$/
handle["CPPDEFINES", $1]
elsif word == "-include"
if val = words[i + 1]
append["CCFLAGS", ["-include", val]]
end
skip = true
elsif word == "-isysroot"
if val = words[i + 1]
append["CCFLAGS", ["-isysroot", val]]
append["LDFLAGS", ["-isysroot", val]]
end
skip = true
elsif word =~ /^#{self["INCPREFIX"]}(.*)$/
handle["CPPPATH", $1]
elsif word =~ /^#{self["LIBLINKPREFIX"]}(.*)$/
handle["LIBS", $1]
elsif word =~ /^#{self["LIBDIRPREFIX"]}(.*)$/
handle["LIBPATH", $1]
elsif word == "-mno-cygwin"
append["CCFLAGS", [word]]
append["LDFLAGS", [word]]
elsif word == "-mwindows"
append["LDFLAGS", [word]]
elsif word == "-pthread"
append["CCFLAGS", [word]]
append["LDFLAGS", [word]]
elsif word =~ /^-std=/
append["CFLAGS", [word]]
elsif word =~ /^-Wa,(.*)$/
append["ASFLAGS", $1.split(",")]
elsif word =~ /^-Wl,(.*)$/
append["LDFLAGS", $1.split(",")]
elsif word =~ /^-Wp,(.*)$/
append["CPPFLAGS", $1.split(",")]
elsif word.start_with?("-")
append["CCFLAGS", [word]]
elsif word.start_with?("+")
append["CCFLAGS", [word]]
append["LDFLAGS", [word]]
else
append["LIBS", [word]]
end
end
rv
end
def parse_flags!(flags)
flags = parse_flags(flags)
merge_flags(flags)
flags
end
# Merge construction variable flags into this Environment's construction
# variables.
#
# This method does the same thing as {#append}, except that Array values in
# +flags+ are appended to the end of Array construction variables instead
# of replacing their contents.
#
# @param flags [Hash]
# Set of construction variables to merge into the current Environment.
# This can be the value (or a modified version) returned by
# {#parse_flags}.
#
# @return [void]
def merge_flags(flags)
flags.each_pair do |key, val|
if self[key].is_a?(Array) and val.is_a?(Array)
self[key] += val
else
self[key] = val
end
end
end
private
# Expand target and source paths before invoking builders.
#
# This method expand construction variable references in the target and
# source file names before passing them to the builder. It also expands
# "^/" prefixes to the Environment's build root if a build root is defined.
#
# @return [void]
def expand_paths!
@targets = @targets.reduce({}) do |result, (target, target_params)|
sources = target_params[:sources].map do |source|
source = expand_path(source) if @build_root
expand_varref(source)
end
target = expand_path(target) if @build_root
target = expand_varref(target)
result[target] = target_params.merge(sources: sources)
result
end end
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.
#
# @return [Array<String>] Paths of dependency files.
def self.parse_makefile_deps(mf_fname, target) def self.parse_makefile_deps(mf_fname, target)
deps = [] deps = []
buildup = '' buildup = ''
+53 -21
View File
@@ -1,9 +1,9 @@
module Rscons module Rscons
# This class represents a collection of variables which can be accessed # This class represents a collection of variables which supports efficient
# as certain types. # deep cloning.
# Only nil, strings, arrays, and hashes should be stored in a VarSet.
class VarSet class VarSet
# Create a VarSet. # Create a VarSet.
#
# @param vars [Hash] Optional initial variables. # @param vars [Hash] Optional initial variables.
def initialize(vars = {}) def initialize(vars = {})
@my_vars = {} @my_vars = {}
@@ -11,8 +11,10 @@ module Rscons
append(vars) append(vars)
end end
# Access the value of variable as a particular type # Access the value of variable.
#
# @param key [String, Symbol] The variable name. # @param key [String, Symbol] The variable name.
#
# @return [Object] The variable's value. # @return [Object] The variable's value.
def [](key) def [](key)
if @my_vars.include?(key) if @my_vars.include?(key)
@@ -29,15 +31,19 @@ module Rscons
end end
# Assign a value to a variable. # Assign a value to a variable.
#
# @param key [String, Symbol] The variable name. # @param key [String, Symbol] The variable name.
#
# @param val [Object] The value to set. # @param val [Object] The value to set.
def []=(key, val) def []=(key, val)
@my_vars[key] = val @my_vars[key] = val
end end
# Check if the VarSet contains a variable. # Check if the VarSet contains a variable.
#
# @param key [String, Symbol] The variable name. # @param key [String, Symbol] The variable name.
# @return [true, false] Whether the VarSet contains a variable. #
# @return [Boolean] Whether the VarSet contains the variable.
def include?(key) def include?(key)
if @my_vars.include?(key) if @my_vars.include?(key)
true true
@@ -48,8 +54,11 @@ module Rscons
end end
end end
# Add or overwrite a set of variables # Add or overwrite a set of variables.
#
# @param values [VarSet, Hash] New set of variables. # @param values [VarSet, Hash] New set of variables.
#
# @return [self]
def append(values) def append(values)
coa! coa!
if values.is_a?(VarSet) if values.is_a?(VarSet)
@@ -62,7 +71,10 @@ module Rscons
end end
# Create a new VarSet object based on the first merged with other. # Create a new VarSet object based on the first merged with other.
#
# @param other [VarSet, Hash] Other variables to add or overwrite. # @param other [VarSet, Hash] Other variables to add or overwrite.
#
# @return [VarSet] The newly created VarSet.
def merge(other = {}) def merge(other = {})
coa! coa!
varset = self.class.new varset = self.class.new
@@ -71,34 +83,49 @@ module Rscons
end end
alias_method :clone, :merge alias_method :clone, :merge
# Replace "$" variable references in varref with the variables values, # Replace "$\{var}" variable references in varref with the expanded
# recursively. # variables' values, recursively.
# @param varref [String, Array] Value containing references to variables. #
def expand_varref(varref) # @param varref [nil, String, Array, Proc]
if varref.is_a?(Array) # Value containing references to variables.
varref.map do |ent| # @param lambda_args [Array]
expand_varref(ent) # Arguments to pass to any lambda variable values to be expanded.
end.flatten #
else # @return [nil, String, Array]
# Expanded value with "$\{var}" variable references replaced.
def expand_varref(varref, lambda_args)
if varref.is_a?(String)
if varref =~ /^(.*)\$\{([^}]+)\}(.*)$/ if varref =~ /^(.*)\$\{([^}]+)\}(.*)$/
prefix, varname, suffix = $1, $2, $3 prefix, varname, suffix = $1, $2, $3
varval = expand_varref(self[varname]) varval = expand_varref(self[varname], lambda_args)
if varval.is_a?(String) if varval.is_a?(String) or varval.nil?
expand_varref("#{prefix}#{varval}#{suffix}") expand_varref("#{prefix}#{varval}#{suffix}", lambda_args)
elsif varval.is_a?(Array) elsif varval.is_a?(Array)
varval.map {|vv| expand_varref("#{prefix}#{vv}#{suffix}")}.flatten varval.map {|vv| expand_varref("#{prefix}#{vv}#{suffix}", lambda_args)}.flatten
else else
raise "I do not know how to expand a variable reference to a #{varval.class.name} (from #{varname.inspect} => #{self[varname].inspect})" raise "I do not know how to expand a variable reference to a #{varval.class.name} (from #{varname.inspect} => #{self[varname].inspect})"
end end
else else
varref varref
end end
elsif varref.is_a?(Array)
varref.map do |ent|
expand_varref(ent, lambda_args)
end.flatten
elsif varref.is_a?(Proc)
expand_varref(varref[*lambda_args], lambda_args)
elsif varref.nil?
nil
else
raise "Unknown varref type: #{varref.class} (#{varref.inspect})"
end end
end end
private private
# Move all VarSet variables into the copy-on-access list. # Move all VarSet variables into the copy-on-access list.
#
# @return [void]
def coa! def coa!
unless @my_vars.empty? unless @my_vars.empty?
@coa_vars.unshift(@my_vars) @coa_vars.unshift(@my_vars)
@@ -107,8 +134,13 @@ module Rscons
end end
# Create a deep copy of an object. # Create a deep copy of an object.
# @param obj [nil, String, Array, Hash] Object to deep copy. #
# @return [nil, String, Array, Hash] Deep copied value. # Only objects which are of type String, Array, or Hash are deep copied.
# Any other object just has its referenced copied.
#
# @param obj [Object] Object to deep copy.
#
# @return [Object] Deep copied value.
def deep_dup(obj) def deep_dup(obj)
obj_class = obj.class obj_class = obj.class
if obj_class == Hash if obj_class == Hash
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons module Rscons
# gem version # gem version
VERSION = "1.4.0" VERSION = "1.6.1"
end end
+8 -10
View File
@@ -1,30 +1,28 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
lib = File.expand_path('../lib', __FILE__) lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'rscons/version' require "rscons/version"
Gem::Specification.new do |gem| Gem::Specification.new do |gem|
gem.name = "rscons" gem.name = "rscons"
gem.version = Rscons::VERSION gem.version = Rscons::VERSION
gem.authors = ["Josh Holtrop"] gem.authors = ["Josh Holtrop"]
gem.email = ["jholtrop@gmail.com"] gem.email = ["jholtrop@gmail.com"]
gem.description = %q{Software construction library inspired by SCons and implemented in Ruby} gem.description = %q{Software construction library inspired by SCons and implemented in Ruby.}
gem.summary = %q{Software construction library inspired by SCons and implemented in Ruby} gem.summary = %q{Software construction library inspired by SCons and implemented in Ruby}
gem.homepage = "https://github.com/holtrop/rscons" gem.homepage = "https://github.com/holtrop/rscons"
gem.license = 'MIT' gem.license = "MIT"
gem.files = `git ls-files`.split($/) gem.files = Dir["{bin,assets,lib}/**/*", "*.gemspec", "LICENSE.txt"]
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.require_paths = ["lib"] gem.require_paths = ["lib"]
gem.add_development_dependency "rspec-core" gem.add_dependency "json", "~> 1.0"
gem.add_development_dependency "rspec-mocks"
gem.add_development_dependency "rspec-expectations"
gem.add_development_dependency "rspec" gem.add_development_dependency "rspec"
gem.add_development_dependency "rake" gem.add_development_dependency "rake"
gem.add_development_dependency "simplecov" gem.add_development_dependency "simplecov"
gem.add_development_dependency "json"
gem.add_development_dependency 'rdoc'
gem.add_development_dependency "yard" gem.add_development_dependency "yard"
gem.add_development_dependency "redcarpet"
end end
+202 -124
View File
@@ -12,24 +12,42 @@ end
describe Rscons do describe Rscons do
BUILD_TEST_RUN_DIR = "build_test_run" BUILD_TEST_RUN_DIR = "build_test_run"
def rm_rf(dir)
FileUtils.rm_rf(dir)
if File.exists?(dir)
sleep 0.2
FileUtils.rm_rf(dir)
if File.exists?(dir)
sleep 0.5
FileUtils.rm_rf(dir)
if File.exists?(dir)
sleep 1.0
FileUtils.rm_rf(dir)
if File.exists?(dir)
raise "Could not remove #{dir}"
end
end
end
end
end
before(:all) do before(:all) do
FileUtils.rm_rf(BUILD_TEST_RUN_DIR) rm_rf(BUILD_TEST_RUN_DIR)
@owd = Dir.pwd @owd = Dir.pwd
end end
before(:each) do before(:each) do
@output = "" @saved_stdout = $stdout
$stdout.stub(:write) do |content| $stdout = StringIO.new
@output += content @saved_stderr = $stderr
end $stderr = StringIO.new
$stderr.stub(:write) do |content|
@output += content
end
end end
after(:each) do after(:each) do
$stdout = @saved_stdout
$stderr = @saved_stderr
Dir.chdir(@owd) Dir.chdir(@owd)
FileUtils.rm_rf(BUILD_TEST_RUN_DIR) rm_rf(BUILD_TEST_RUN_DIR)
end end
def test_dir(build_test_directory) def test_dir(build_test_directory)
@@ -43,15 +61,16 @@ describe Rscons do
contents.each_line do |line| contents.each_line do |line|
replaced += yield(line) replaced += yield(line)
end end
File.open(fname, 'w') do |fh| File.open(fname, 'wb') do |fh|
fh.write(replaced) fh.write(replaced)
end end
end end
def lines def lines
@output.lines.map(&:rstrip).tap do |v| rv = ($stdout.string + $stderr.string).lines.map(&:rstrip)
@output = "" $stdout.string = ""
end $stderr.string = ""
rv
end end
########################################################################### ###########################################################################
@@ -63,30 +82,30 @@ describe Rscons do
Rscons::Environment.new do |env| Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c']) env.Program('simple', Dir['*.c'])
end end
File.exists?('simple.o').should be_true expect(File.exists?('simple.o')).to be_truthy
`./simple`.should == "This is a simple C program\n" expect(`./simple`).to eq "This is a simple C program\n"
end end
it 'prints commands as they are executed' do it 'prints commands as they are executed' do
test_dir('simple') test_dir('simple')
Rscons::Environment.new(echo: :command) do |env| env = Rscons::Environment.new(echo: :command) do |env|
env["LD"] = "gcc" env["LD"] = "gcc"
env.Program('simple', Dir['*.c']) env.Program('simple', Dir['*.c'])
end end
lines.should == [ expect(lines).to eq [
'gcc -c -o simple.o -MMD -MF simple.mf simple.c', 'gcc -c -o simple.o -MMD -MF simple.mf simple.c',
'gcc -o simple simple.o', "gcc -o simple#{env["PROGSUFFIX"]} simple.o",
] ]
end end
it 'prints short representations of the commands being executed' do it 'prints short representations of the commands being executed' do
test_dir('header') test_dir('header')
Rscons::Environment.new do |env| env = Rscons::Environment.new do |env|
env.Program('header', Dir['*.c']) env.Program('header', Dir['*.c'])
end end
lines.should == [ expect(lines).to eq [
'CC header.o', 'CC header.o',
'LD header', "LD header#{env["PROGSUFFIX"]}",
] ]
end end
@@ -95,8 +114,8 @@ describe Rscons do
Rscons::Environment.new do |env| Rscons::Environment.new do |env|
env.Program('header', Dir['*.c']) env.Program('header', Dir['*.c'])
end end
File.exists?('header.o').should be_true expect(File.exists?('header.o')).to be_truthy
`./header`.should == "The value is 2\n" expect(`./header`).to eq "The value is 2\n"
end end
it 'rebuilds a C module when a header it depends on changes' do it 'rebuilds a C module when a header it depends on changes' do
@@ -104,10 +123,10 @@ describe Rscons do
env = Rscons::Environment.new do |env| env = Rscons::Environment.new do |env|
env.Program('header', Dir['*.c']) env.Program('header', Dir['*.c'])
end end
`./header`.should == "The value is 2\n" expect(`./header`).to eq "The value is 2\n"
file_sub('header.h') {|line| line.sub(/2/, '5')} file_sub('header.h') {|line| line.sub(/2/, '5')}
env.process env.process
`./header`.should == "The value is 5\n" expect(`./header`).to eq "The value is 5\n"
end end
it 'does not rebuild a C module when its dependencies have not changed' do it 'does not rebuild a C module when its dependencies have not changed' do
@@ -115,44 +134,45 @@ describe Rscons do
env = Rscons::Environment.new do |env| env = Rscons::Environment.new do |env|
env.Program('header', Dir['*.c']) env.Program('header', Dir['*.c'])
end end
`./header`.should == "The value is 2\n" expect(`./header`).to eq "The value is 2\n"
lines.should == [ expect(lines).to eq [
'CC header.o', 'CC header.o',
'LD header', "LD header#{env["PROGSUFFIX"]}",
] ]
env.process env.process
lines.should == [] expect(lines).to eq []
end end
it "does not rebuild a C module when only the file's timestampe has changed" do it "does not rebuild a C module when only the file's timestamp has changed" do
test_dir('header') test_dir('header')
env = Rscons::Environment.new do |env| env = Rscons::Environment.new do |env|
env.Program('header', Dir['*.c']) env.Program('header', Dir['*.c'])
end end
`./header`.should == "The value is 2\n" expect(`./header`).to eq "The value is 2\n"
lines.should == [ expect(lines).to eq [
'CC header.o', 'CC header.o',
'LD header', "LD header#{env["PROGSUFFIX"]}",
] ]
sleep 0.05
file_sub('header.c') {|line| line} file_sub('header.c') {|line| line}
env.process env.process
lines.should == [] expect(lines).to eq []
end end
it 're-links a program when the link flags have changed' do it 're-links a program when the link flags have changed' do
test_dir('simple') test_dir('simple')
Rscons::Environment.new(echo: :command) do |env| env = Rscons::Environment.new(echo: :command) do |env|
env.Program('simple', Dir['*.c']) env.Program('simple', Dir['*.c'])
end end
lines.should == [ expect(lines).to eq [
'gcc -c -o simple.o -MMD -MF simple.mf simple.c', 'gcc -c -o simple.o -MMD -MF simple.mf simple.c',
'gcc -o simple simple.o', "gcc -o simple#{env["PROGSUFFIX"]} simple.o",
] ]
Rscons::Environment.new(echo: :command) do |env| e2 = Rscons::Environment.new(echo: :command) do |env|
env["LIBS"] += ["c"] env["LIBPATH"] += ["libdir"]
env.Program('simple', Dir['*.c']) env.Program('simple', Dir['*.c'])
end end
lines.should == ['gcc -o simple simple.o -lc'] expect(lines).to eq ["gcc -o simple#{env["PROGSUFFIX"]} simple.o -Llibdir"]
end end
it 'builds object files in a different build directory' do it 'builds object files in a different build directory' do
@@ -162,20 +182,20 @@ describe Rscons do
env.build_dir(%r{^src/([^/]+)/}, 'build_\\1/') env.build_dir(%r{^src/([^/]+)/}, 'build_\\1/')
env.Program('build_dir', Dir['src/**/*.c']) env.Program('build_dir', Dir['src/**/*.c'])
end end
`./build_dir`.should == "Hello from two()\n" expect(`./build_dir`).to eq "Hello from two()\n"
File.exists?('build_one/one.o').should be_true expect(File.exists?('build_one/one.o')).to be_truthy
File.exists?('build_two/two.o').should be_true expect(File.exists?('build_two/two.o')).to be_truthy
end end
it 'uses build directories before build root' do it 'uses build directories before build root' do
test_dir('build_dir') test_dir('build_dir')
Rscons::Environment.new do |env| env = Rscons::Environment.new do |env|
env.append('CPPPATH' => Dir['src/**/*/']) env.append('CPPPATH' => Dir['src/**/*/'])
env.build_dir("src", "build") env.build_dir("src", "build")
env.build_root = "build_root" env.build_root = "build_root"
env.Program('build_dir', Dir['src/**/*.c']) env.Program('build_dir', Dir['src/**/*.c'])
end end
lines.should == ["CC build/one/one.o", "CC build/two/two.o", "LD build_dir"] expect(lines).to eq ["CC build/one/one.o", "CC build/two/two.o", "LD build_dir#{env["PROGSUFFIX"]}"]
end end
it 'uses build_root if no build directories match' do it 'uses build_root if no build directories match' do
@@ -184,14 +204,14 @@ describe Rscons do
env.append('CPPPATH' => Dir['src/**/*/']) env.append('CPPPATH' => Dir['src/**/*/'])
env.build_dir("src2", "build") env.build_dir("src2", "build")
env.build_root = "build_root" env.build_root = "build_root"
env.Program('build_dir', Dir['src/**/*.c']) env.Program('build_dir.exe', Dir['src/**/*.c'])
end end
lines.should == ["CC build_root/src/one/one.o", "CC build_root/src/two/two.o", "LD build_dir"] expect(lines).to eq ["CC build_root/src/one/one.o", "CC build_root/src/two/two.o", "LD build_dir.exe"]
end end
it "expands target and source paths starting with ^/ to be relative to the build root" do it "expands target and source paths starting with ^/ to be relative to the build root" do
test_dir('build_dir') test_dir('build_dir')
Rscons::Environment.new(echo: :command) do |env| env = Rscons::Environment.new(echo: :command) do |env|
env.append('CPPPATH' => Dir['src/**/*/']) env.append('CPPPATH' => Dir['src/**/*/'])
env.build_root = "build_root" env.build_root = "build_root"
FileUtils.mkdir_p(env.build_root) FileUtils.mkdir_p(env.build_root)
@@ -199,10 +219,10 @@ describe Rscons do
env.Object("^/one.o", "^/one.c") env.Object("^/one.o", "^/one.c")
env.Program('build_dir', Dir['src/**/*.c'] + ["^/one.o"]) env.Program('build_dir', Dir['src/**/*.c'] + ["^/one.o"])
end end
lines.should == [ expect(lines).to eq [
%q{gcc -c -o build_root/one.o -MMD -MF build_root/one.mf -Isrc/one/ -Isrc/two/ build_root/one.c}, %q{gcc -c -o build_root/one.o -MMD -MF build_root/one.mf -Isrc/one/ -Isrc/two/ build_root/one.c},
%q{gcc -c -o build_root/src/two/two.o -MMD -MF build_root/src/two/two.mf -Isrc/one/ -Isrc/two/ src/two/two.c}, %q{gcc -c -o build_root/src/two/two.o -MMD -MF build_root/src/two/two.mf -Isrc/one/ -Isrc/two/ src/two/two.c},
%q{gcc -o build_dir build_root/src/two/two.o build_root/one.o}, %Q{gcc -o build_dir#{env["PROGSUFFIX"]} build_root/src/two/two.o build_root/one.o},
] ]
end end
@@ -213,15 +233,15 @@ describe Rscons do
env.build_dir(%r{^src/([^/]+)/}, 'build_\\1/') env.build_dir(%r{^src/([^/]+)/}, 'build_\\1/')
env.Program('build_dir', Dir['src/**/*.c']) env.Program('build_dir', Dir['src/**/*.c'])
end end
`./build_dir`.should == "Hello from two()\n" expect(`./build_dir`).to eq "Hello from two()\n"
File.exists?('build_one/one.o').should be_true expect(File.exists?('build_one/one.o')).to be_truthy
File.exists?('build_two/two.o').should be_true expect(File.exists?('build_two/two.o')).to be_truthy
Rscons.clean Rscons.clean
File.exists?('build_one/one.o').should be_false expect(File.exists?('build_one/one.o')).to be_falsey
File.exists?('build_two/two.o').should be_false expect(File.exists?('build_two/two.o')).to be_falsey
File.exists?('build_one').should be_false expect(File.exists?('build_one')).to be_falsey
File.exists?('build_two').should be_false expect(File.exists?('build_two')).to be_falsey
File.exists?('src/one/one.c').should be_true expect(File.exists?('src/one/one.c')).to be_truthy
end end
it 'does not clean created directories if other non-rscons-generated files reside there' do it 'does not clean created directories if other non-rscons-generated files reside there' do
@@ -231,16 +251,16 @@ describe Rscons do
env.build_dir(%r{^src/([^/]+)/}, 'build_\\1/') env.build_dir(%r{^src/([^/]+)/}, 'build_\\1/')
env.Program('build_dir', Dir['src/**/*.c']) env.Program('build_dir', Dir['src/**/*.c'])
end end
`./build_dir`.should == "Hello from two()\n" expect(`./build_dir`).to eq "Hello from two()\n"
File.exists?('build_one/one.o').should be_true expect(File.exists?('build_one/one.o')).to be_truthy
File.exists?('build_two/two.o').should be_true expect(File.exists?('build_two/two.o')).to be_truthy
File.open('build_two/tmp', 'w') { |fh| fh.puts "dum" } File.open('build_two/tmp', 'w') { |fh| fh.puts "dum" }
Rscons.clean Rscons.clean
File.exists?('build_one/one.o').should be_false expect(File.exists?('build_one/one.o')).to be_falsey
File.exists?('build_two/two.o').should be_false expect(File.exists?('build_two/two.o')).to be_falsey
File.exists?('build_one').should be_false expect(File.exists?('build_one')).to be_falsey
File.exists?('build_two').should be_true expect(File.exists?('build_two')).to be_truthy
File.exists?('src/one/one.c').should be_true expect(File.exists?('src/one/one.c')).to be_truthy
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
@@ -256,15 +276,15 @@ EOF
end end
end end
Rscons::Environment.new do |env| env = Rscons::Environment.new do |env|
env.add_builder(MySource.new) 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
lines.should == ['CC program.o', 'LD program'] expect(lines).to eq ["CC program.o", "LD program#{env["PROGSUFFIX"]}"]
File.exists?('inc.h').should be_true expect(File.exists?('inc.h')).to be_truthy
`./program`.should == "The value is 5678\n" expect(`./program`).to eq "The value is 5678\n"
end end
it 'supports custom builders with multiple targets' do it 'supports custom builders with multiple targets' do
@@ -289,15 +309,15 @@ EOF
env.Program("program", Dir["*.c"] + ["inc.c"]) env.Program("program", Dir["*.c"] + ["inc.c"])
end end
lines.should == ["CHGen inc.c", "CC program.o", "CC inc.o", "LD program"] expect(lines).to eq ["CHGen inc.c", "CC program.o", "CC inc.o", "LD program#{env["PROGSUFFIX"]}"]
File.exists?("inc.c").should be_true expect(File.exists?("inc.c")).to be_truthy
File.exists?("inc.h").should be_true expect(File.exists?("inc.h")).to be_truthy
`./program`.should == "The value is 42\n" expect(`./program`).to eq "The value is 42\n"
File.open("inc.c", "w") {|fh| fh.puts "int THE_VALUE = 33;"} File.open("inc.c", "w") {|fh| fh.puts "int THE_VALUE = 33;"}
env.process env.process
lines.should == ["CHGen inc.c"] expect(lines).to eq ["CHGen inc.c"]
`./program`.should == "The value is 42\n" expect(`./program`).to eq "The value is 42\n"
end end
it 'allows cloning Environment objects' do it 'allows cloning Environment objects' do
@@ -316,11 +336,11 @@ EOF
env.Program('program-release', Dir['src/*.c']) env.Program('program-release', Dir['src/*.c'])
end end
lines.should == [ expect(lines).to eq [
%q{gcc -c -o debug/program.o -MMD -MF debug/program.mf '-DSTRING="Debug Version"' -O2 src/program.c}, %q{gcc -c -o debug/program.o -MMD -MF debug/program.mf '-DSTRING="Debug Version"' -O2 src/program.c},
%q{gcc -o program-debug debug/program.o}, %Q{gcc -o program-debug#{debug["PROGSUFFIX"]} debug/program.o},
%q{gcc -c -o release/program.o -MMD -MF release/program.mf '-DSTRING="Release Version"' -O2 src/program.c}, %q{gcc -c -o release/program.o -MMD -MF release/program.mf '-DSTRING="Release Version"' -O2 src/program.c},
%q{gcc -o program-release release/program.o}, %Q{gcc -o program-release#{debug["PROGSUFFIX"]} release/program.o},
] ]
end end
@@ -340,10 +360,10 @@ EOF
env.Program('program2', Dir['src/*.c']) env.Program('program2', Dir['src/*.c'])
end end
lines.should == [ expect(lines).to eq [
%q{gcc -c -o build/program.o -MMD -MF build/program.mf -DSTRING="Hello" -O2 src/program.c}, %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 program#{env1["PROGSUFFIX"]} build/program.o},
%q{gcc -o program2 build/program.o}, %Q{gcc -o program2#{env2["PROGSUFFIX"]} build/program.o},
] ]
end end
@@ -352,45 +372,45 @@ EOF
Rscons::Environment.new do |env| Rscons::Environment.new do |env|
env.Program('simple', Dir['*.cc']) env.Program('simple', Dir['*.cc'])
end end
File.exists?('simple.o').should be_true expect(File.exists?('simple.o')).to be_truthy
`./simple`.should == "This is a simple C++ program\n" expect(`./simple`).to eq "This is a simple C++ program\n"
end end
it 'allows overriding construction variables for individual builder calls' do it 'allows overriding construction variables for individual builder calls' do
test_dir('two_sources') test_dir('two_sources')
Rscons::Environment.new(echo: :command) do |env| env = Rscons::Environment.new(echo: :command) do |env|
env.Object("one.o", "one.c", 'CPPFLAGS' => ['-DONE']) env.Object("one.o", "one.c", 'CPPFLAGS' => ['-DONE'])
env.Program('two_sources', ['one.o', 'two.c']) env.Program('two_sources', ['one.o', 'two.c'])
end end
lines.should == [ expect(lines).to eq [
'gcc -c -o one.o -MMD -MF one.mf -DONE one.c', 'gcc -c -o one.o -MMD -MF one.mf -DONE one.c',
'gcc -c -o two.o -MMD -MF two.mf two.c', 'gcc -c -o two.o -MMD -MF two.mf two.c',
'gcc -o two_sources one.o two.o', "gcc -o two_sources#{env["PROGSUFFIX"]} one.o two.o",
] ]
File.exists?('two_sources').should be_true expect(File.exists?("two_sources#{env["PROGSUFFIX"]}")).to be_truthy
`./two_sources`.should == "This is a C program with two sources.\n" expect(`./two_sources`).to eq "This is a C program with two sources.\n"
end end
it 'builds a static library archive' do it 'builds a static library archive' do
test_dir('library') test_dir('library')
Rscons::Environment.new(echo: :command) do |env| env = Rscons::Environment.new(echo: :command) do |env|
env.Program('library', ['lib.a', 'three.c']) env.Program('library', ['lib.a', 'three.c'])
env.Library("lib.a", ['one.c', 'two.c'], 'CPPFLAGS' => ['-Dmake_lib']) env.Library("lib.a", ['one.c', 'two.c'], 'CPPFLAGS' => ['-Dmake_lib'])
end end
lines.should == [ expect(lines).to eq [
'gcc -c -o one.o -MMD -MF one.mf -Dmake_lib one.c', 'gcc -c -o one.o -MMD -MF one.mf -Dmake_lib one.c',
'gcc -c -o two.o -MMD -MF two.mf -Dmake_lib two.c', 'gcc -c -o two.o -MMD -MF two.mf -Dmake_lib two.c',
'ar rcs lib.a one.o two.o', 'ar rcs lib.a one.o two.o',
'gcc -c -o three.o -MMD -MF three.mf three.c', 'gcc -c -o three.o -MMD -MF three.mf three.c',
'gcc -o library lib.a three.o', "gcc -o library#{env["PROGSUFFIX"]} lib.a three.o",
] ]
File.exists?('library').should be_true expect(File.exists?("library#{env["PROGSUFFIX"]}")).to be_truthy
`ar t lib.a`.should == "one.o\ntwo.o\n" expect(`ar t lib.a`).to eq "one.o\ntwo.o\n"
end end
it 'supports build hooks to override construction variables' do it 'supports build hooks to override construction variables' do
test_dir("build_dir") test_dir("build_dir")
Rscons::Environment.new(echo: :command) do |env| env = Rscons::Environment.new(echo: :command) do |env|
env.append('CPPPATH' => Dir['src/**/*/']) env.append('CPPPATH' => Dir['src/**/*/'])
env.build_dir(%r{^src/([^/]+)/}, 'build_\\1/') env.build_dir(%r{^src/([^/]+)/}, 'build_\\1/')
env.add_build_hook do |build_op| env.add_build_hook do |build_op|
@@ -400,58 +420,57 @@ EOF
build_op[:vars]["CFLAGS"] << "-O2" build_op[:vars]["CFLAGS"] << "-O2"
end end
end end
env.Program('build_hook', Dir['src/**/*.c']) env.Program('build_hook.exe', Dir['src/**/*.c'])
end end
`./build_hook`.should == "Hello from two()\n" expect(`./build_hook.exe`).to eq "Hello from two()\n"
lines.should =~ [ expect(lines).to match_array [
'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_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 -c -o build_two/two.o -MMD -MF build_two/two.mf -Isrc/one/ -Isrc/two/ -O2 src/two/two.c',
'gcc -o build_hook build_one/one.o build_two/two.o', 'gcc -o build_hook.exe build_one/one.o build_two/two.o',
] ]
end end
it 'rebuilds when user-specified dependencies change' do it 'rebuilds when user-specified dependencies change' do
test_dir('simple') test_dir('simple')
Rscons::Environment.new do |env| env = Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c']) env.Program('simple.exe', Dir['*.c']).depends("file.ld")
File.open("file.ld", "w") do |fh| File.open("file.ld", "w") do |fh|
fh.puts("foo") fh.puts("foo")
end end
env.depends('simple', 'file.ld')
end end
lines.should == ["CC simple.o", "LD simple"] expect(lines).to eq ["CC simple.o", "LD simple.exe"]
File.exists?('simple.o').should be_true expect(File.exists?('simple.o')).to be_truthy
`./simple`.should == "This is a simple C program\n" expect(`./simple.exe`).to eq "This is a simple C program\n"
Rscons::Environment.new do |env| e2 = Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c']) program = env.Program('simple.exe', Dir['*.c'])
env.depends(program, "file.ld")
File.open("file.ld", "w") do |fh| File.open("file.ld", "w") do |fh|
fh.puts("bar") fh.puts("bar")
end end
env.depends('simple', 'file.ld')
end end
lines.should == ["LD simple"] expect(lines).to eq ["LD simple.exe"]
Rscons::Environment.new do |env| e3 = Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c']) env.Program('simple.exe', Dir['*.c'])
File.unlink("file.ld") File.unlink("file.ld")
end end
lines.should == ["LD simple"] expect(lines).to eq ["LD simple.exe"]
Rscons::Environment.new do |env| Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c']) env.Program('simple.exe', Dir['*.c'])
end end
lines.should == [] expect(lines).to eq []
end end
unless ENV["omit_gdc_tests"] unless ENV["omit_gdc_tests"]
it "supports building D sources" do it "supports building D sources" do
test_dir("d") test_dir("d")
Rscons::Environment.new(echo: :command) do |env| Rscons::Environment.new(echo: :command) do |env|
env.Program("hello-d", Dir["*.d"]) env.Program("hello-d.exe", Dir["*.d"])
end end
lines.should == [ expect(lines).to eq [
"gdc -c -o main.o main.d", "gdc -c -o main.o main.d",
"gdc -o hello-d main.o", "gdc -o hello-d.exe main.o",
] ]
`./hello-d`.rstrip.should == "Hello from D!" expect(`./hello-d.exe`.rstrip).to eq "Hello from D!"
end end
end end
@@ -461,8 +480,8 @@ EOF
env.Object("simple.o", "simple.c") env.Object("simple.o", "simple.c")
env.Disassemble("simple.txt", "simple.o") env.Disassemble("simple.txt", "simple.o")
end end
File.exists?("simple.txt").should be_true expect(File.exists?("simple.txt")).to be_truthy
File.read("simple.txt").should =~ /Disassembly of section .text:/ expect(File.read("simple.txt")).to match /Disassembly of section .text:/
end end
it "supports preprocessing C sources" do it "supports preprocessing C sources" do
@@ -471,8 +490,8 @@ EOF
env.Preprocess("simplepp.c", "simple.c") env.Preprocess("simplepp.c", "simple.c")
env.Program("simple", "simplepp.c") env.Program("simple", "simplepp.c")
end end
File.read("simplepp.c").should =~ /# \d+ "simple.c"/ expect(File.read("simplepp.c")).to match /# \d+ "simple.c"/
`./simple`.should == "This is a simple C program\n" expect(`./simple`).to eq "This is a simple C program\n"
end end
it "supports preprocessing C++ sources" do it "supports preprocessing C++ sources" do
@@ -481,7 +500,66 @@ EOF
env.Preprocess("simplepp.cc", "simple.cc") env.Preprocess("simplepp.cc", "simple.cc")
env.Program("simple", "simplepp.cc") env.Program("simple", "simplepp.cc")
end end
File.read("simplepp.cc").should =~ /# \d+ "simple.cc"/ expect(File.read("simplepp.cc")).to match /# \d+ "simple.cc"/
`./simple`.should == "This is a simple C++ program\n" expect(`./simple`).to eq "This is a simple C++ program\n"
end
it "supports invoking builders with no sources and a build_root defined" do
class TestBuilder < Rscons::Builder
def run(target, sources, cache, env, vars)
target
end
end
test_dir("simple")
Rscons::Environment.new do |env|
env.build_root = "build"
env.add_builder(TestBuilder.new)
env.TestBuilder("file")
end
end
it "expands construction variables in builder target and sources before invoking the builder" do
test_dir('custom_builder')
class MySource < Rscons::Builder
def run(target, sources, cache, env, vars)
File.open(target, 'w') do |fh|
fh.puts <<EOF
#define THE_VALUE 678
EOF
end
target
end
end
env = Rscons::Environment.new do |env|
env["hdr"] = "inc.h"
env["src"] = "program.c"
env.add_builder(MySource.new)
env.MySource('${hdr}')
env.Program('program', "${src}")
end
expect(lines).to eq ["CC program.o", "LD program#{env["PROGSUFFIX"]}"]
expect(File.exists?('inc.h')).to be_truthy
expect(`./program`).to eq "The value is 678\n"
end
it "supports lambdas as construction variable values" do
env = Rscons::Environment.new do |env|
env["prefix"] = "-H"
env["suffix"] = "xyz"
env[:cfg] = {val: 44}
env["computed"] = lambda do |args|
"#{args[:env]['prefix']}#{args[:env][:cfg][:val]}#{args[:env]['suffix']}"
end
env["lambda_recurse"] = lambda do |args|
"${prefix}ello"
end
end
e2 = env.clone
e2[:cfg][:val] = 38
expect(env.expand_varref("${computed}")).to eq("-H44xyz")
expect(e2.expand_varref("${computed}")).to eq("-H38xyz")
expect(env.expand_varref("${lambda_recurse}")).to eq("-Hello")
end end
end end
+7 -2
View File
@@ -5,16 +5,21 @@ module Rscons
subject {CFile.new} subject {CFile.new}
it "invokes bison to create a .c file from a .y file" do it "invokes bison to create a .c file from a .y file" do
subject.should_receive(:standard_build).with("YACC parser.c", "parser.c", ["bison", "-d", "-o", "parser.c", "parser.y"], ["parser.y"], env, :cache) expect(subject).to receive(:standard_build).with("YACC parser.c", "parser.c", ["bison", "-d", "-o", "parser.c", "parser.y"], ["parser.y"], env, :cache)
subject.run("parser.c", ["parser.y"], :cache, env, {}) subject.run("parser.c", ["parser.y"], :cache, env, {})
end end
it "invokes a custom lexer to create a .cc file from a .ll file" do it "invokes a custom lexer to create a .cc file from a .ll file" do
env["LEX"] = "custom_lex" env["LEX"] = "custom_lex"
subject.should_receive(:standard_build).with("LEX lexer.cc", "lexer.cc", ["custom_lex", "-o", "lexer.cc", "parser.ll"], ["parser.ll"], env, :cache) expect(subject).to receive(:standard_build).with("LEX lexer.cc", "lexer.cc", ["custom_lex", "-o", "lexer.cc", "parser.ll"], ["parser.ll"], env, :cache)
subject.run("lexer.cc", ["parser.ll"], :cache, env, {}) subject.run("lexer.cc", ["parser.ll"], :cache, env, {})
end end
it "supports overriding construction variables" do
expect(subject).to receive(:standard_build).with("LEX lexer.c", "lexer.c", ["hi", "parser.l"], ["parser.l"], env, :cache)
subject.run("lexer.c", ["parser.l"], :cache, env, "LEX_CMD" => ["hi", "${_SOURCES}"])
end
it "raises an error when an unknown source file is specified" do it "raises an error when an unknown source file is specified" do
expect {subject.run("file.c", ["foo.bar"], :cache, env, {})}.to raise_error /Unknown source file .foo.bar. for CFile builder/ expect {subject.run("file.c", ["foo.bar"], :cache, env, {})}.to raise_error /Unknown source file .foo.bar. for CFile builder/
end end
+17
View File
@@ -0,0 +1,17 @@
module Rscons
module Builders
describe Disassemble do
let(:env) {Environment.new}
subject {Disassemble.new}
it "supports overriding DISASM_CMD construction variable" do
cache = "cache"
allow(cache).to receive(:up_to_date?) { false }
allow(cache).to receive(:mkdir_p) { }
allow(cache).to receive(:register_build) { }
expect(env).to receive(:execute).with("Disassemble a_file.txt", ["my_disasm", "a_file.exe"], anything).and_return(true)
subject.run("a_file.txt", ["a_file.exe"], cache, env, "DISASM_CMD" => ["my_disasm", "${_SOURCES}"])
end
end
end
end
+18
View File
@@ -0,0 +1,18 @@
module Rscons
module Builders
describe Library do
let(:env) {Environment.new}
subject {Library.new}
it "supports overriding AR construction variable" do
expect(subject).to receive(:standard_build).with("AR prog.a", "prog.a", ["sp-ar", "rcs", "prog.a", "prog.o"], ["prog.o"], env, :cache)
subject.run("prog.a", ["prog.o"], :cache, env, "AR" => "sp-ar")
end
it "supports overriding ARCMD construction variable" do
expect(subject).to receive(:standard_build).with("AR prog.a", "prog.a", ["special", "AR!", "prog.o"], ["prog.o"], env, :cache)
subject.run("prog.a", ["prog.o"], :cache, env, "ARCMD" => ["special", "AR!", "${_SOURCES}"])
end
end
end
end
+35
View File
@@ -0,0 +1,35 @@
module Rscons
module Builders
describe Object do
let(:env) {Environment.new}
let(:cache) {double(Cache)}
subject {Object.new}
it "supports overriding CCCMD construction variable" do
expect(cache).to receive(:up_to_date?).and_return(false)
expect(cache).to receive(:mkdir_p)
expect(FileUtils).to receive(:rm_f)
expect(env).to receive(:execute).with("CC mod.o", ["llc", "mod.c"]).and_return(true)
expect(File).to receive(:exists?).and_return(false)
expect(cache).to receive(:register_build)
subject.run("mod.o", ["mod.c"], cache, env, "CCCMD" => ["llc", "${_SOURCES}"])
end
it "supports overriding DEPFILESUFFIX construction variable" do
expect(cache).to receive(:up_to_date?).and_return(false)
expect(cache).to receive(:mkdir_p)
expect(FileUtils).to receive(:rm_f)
expect(env).to receive(:execute).with(anything, %w[gcc -c -o f.o -MMD -MF f.d in.c]).and_return(true)
expect(File).to receive(:exists?).with("f.d").and_return(false)
expect(cache).to receive(:register_build)
subject.run("f.o", ["in.c"], cache, env, "DEPFILESUFFIX" => ".d")
end
it "raises an error when given a source file with an unknown suffix" do
expect { subject.run("mod.o", ["mod.xyz"], :cache, env, {}) }.to raise_error /unknown input file type: "mod.xyz"/
end
end
end
end
+18
View File
@@ -0,0 +1,18 @@
module Rscons
module Builders
describe Preprocess do
let(:env) {Environment.new}
subject {Preprocess.new}
it "supports overriding CC construction variable" do
expect(subject).to receive(:standard_build).with("Preprocess module.pp", "module.pp", ["my_cpp", "-E", "-o", "module.pp", "module.c"], ["module.c"], env, :cache)
subject.run("module.pp", ["module.c"], :cache, env, "CC" => "my_cpp")
end
it "supports overriding CPP_CMD construction variable" do
expect(subject).to receive(:standard_build).with("Preprocess module.pp", "module.pp", ["my_cpp", "module.c"], ["module.c"], env, :cache)
subject.run("module.pp", ["module.c"], :cache, env, "CPP_CMD" => ["my_cpp", "${_SOURCES}"])
end
end
end
end
+18
View File
@@ -0,0 +1,18 @@
module Rscons
module Builders
describe Program do
let(:env) {Environment.new}
subject {Program.new}
it "supports overriding CC construction variable" do
expect(subject).to receive(:standard_build).with("LD prog", "prog", ["sp-c++", "-o", "prog", "prog.o"], ["prog.o"], env, :cache)
subject.run("prog", ["prog.o"], :cache, env, "CC" => "sp-c++")
end
it "supports overriding LDCMD construction variable" do
expect(subject).to receive(:standard_build).with("LD prog.exe", "prog.exe", ["special", "LD!", "prog.o"], ["prog.o"], env, :cache)
subject.run("prog.exe", ["prog.o"], :cache, env, "LDCMD" => ["special", "LD!", "${_SOURCES}"])
end
end
end
end
+84 -84
View File
@@ -1,11 +1,11 @@
module Rscons module Rscons
describe Cache do describe Cache do
before do before do
File.stub(:read) { nil } allow(File).to receive(:read) { nil }
end end
def build_from(cache) def build_from(cache)
JSON.stub(:load) do allow(JSON).to receive(:load) do
cache cache
end end
Cache.instance.tap do |cache| Cache.instance.tap do |cache|
@@ -16,64 +16,64 @@ module Rscons
describe "#initialize" do describe "#initialize" do
context "when corrupt" do context "when corrupt" do
it "prints a warning and defaults to an empty hash" do it "prints a warning and defaults to an empty hash" do
JSON.should_receive(:load).and_return("string") expect(JSON).to receive(:load).and_return("string")
$stderr.should_receive(:puts).with(/Warning:.*was.corrupt/) expect($stderr).to receive(:puts).with(/Warning:.*was.corrupt/)
c = Cache.instance c = Cache.instance
c.send(:initialize!) c.send(:initialize!)
c.instance_variable_get(:@cache).is_a?(Hash).should be_true expect(c.instance_variable_get(:@cache).is_a?(Hash)).to be_truthy
end end
end end
end end
describe "#clear" do describe "#clear" do
it "removes the cache file" do it "removes the cache file" do
FileUtils.should_receive(:rm_f).with(Cache::CACHE_FILE) expect(FileUtils).to receive(:rm_f).with(Cache::CACHE_FILE)
JSON.stub(:load) {{}} allow(JSON).to receive(:load) {{}}
Cache.instance.clear Cache.instance.clear
end end
end end
describe "#write" do describe "#write" do
it "should fill in 'version' and write to file" do it "fills in 'version' and write to file" do
cache = {} cache = {}
fh = $stdout fh = $stdout
fh.should_receive(:puts) expect(fh).to receive(:puts)
File.should_receive(:open).and_yield(fh) expect(File).to receive(:open).and_yield(fh)
build_from(cache).write build_from(cache).write
cache["version"].should == Rscons::VERSION expect(cache["version"]).to eq Rscons::VERSION
end end
end end
describe "#up_to_date?" do describe "#up_to_date?" do
empty_env = "env" empty_env = "env"
before do before do
empty_env.stub(:get_user_deps) { nil } allow(empty_env).to receive(:get_user_deps) { nil }
end end
it "returns false when target file does not exist" do it "returns false when target file does not exist" do
File.should_receive(:exists?).with("target").and_return(false) expect(File).to receive(:exists?).with("target").and_return(false)
build_from({}).up_to_date?("target", "command", [], empty_env).should be_false expect(build_from({}).up_to_date?("target", "command", [], empty_env)).to be_falsey
end end
it "returns false when target is not registered in the cache" do it "returns false when target is not registered in the cache" do
File.should_receive(:exists?).with("target").and_return(true) expect(File).to receive(:exists?).with("target").and_return(true)
build_from({}).up_to_date?("target", "command", [], empty_env).should be_false expect(build_from({}).up_to_date?("target", "command", [], empty_env)).to be_falsey
end end
it "returns false when the target's checksum does not match" do it "returns false when the target's checksum does not match" do
_cache = {"targets" => {"target" => {"checksum" => "abc"}}} _cache = {"targets" => {"target" => {"checksum" => "abc"}}}
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true) expect(File).to receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("def") expect(cache).to receive(:calculate_checksum).with("target").and_return("def")
cache.up_to_date?("target", "command", [], empty_env).should be_false expect(cache.up_to_date?("target", "command", [], empty_env)).to be_falsey
end end
it "returns false when the build command has changed" do it "returns false when the build command has changed" do
_cache = {"targets" => {"target" => {"checksum" => "abc", "command" => Digest::MD5.hexdigest("old command".inspect)}}} _cache = {"targets" => {"target" => {"checksum" => "abc", "command" => Digest::MD5.hexdigest("old command".inspect)}}}
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true) expect(File).to receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc") expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", [], empty_env).should be_false expect(cache.up_to_date?("target", "command", [], empty_env)).to be_falsey
end end
it "returns false when there is a new dependency" do it "returns false when there is a new dependency" do
@@ -81,9 +81,9 @@ module Rscons
"command" => Digest::MD5.hexdigest("command".inspect), "command" => Digest::MD5.hexdigest("command".inspect),
"deps" => [{"fname" => "dep.1"}]}}} "deps" => [{"fname" => "dep.1"}]}}}
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true) expect(File).to receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc") expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env).should be_false expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env)).to be_falsey
end end
it "returns false when a dependency's checksum has changed" do it "returns false when a dependency's checksum has changed" do
@@ -97,11 +97,11 @@ module Rscons
"checksum" => "extra.dep.chk"}], "checksum" => "extra.dep.chk"}],
"user_deps" => []}}} "user_deps" => []}}}
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true) expect(File).to receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc") expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk") expect(cache).to receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.changed") expect(cache).to receive(:calculate_checksum).with("dep.2").and_return("dep.2.changed")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env).should be_false expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env)).to be_falsey
end end
it "returns false with strict_deps=true when cache has an extra dependency" do it "returns false with strict_deps=true when cache has an extra dependency" do
@@ -115,9 +115,9 @@ module Rscons
"checksum" => "extra.dep.chk"}], "checksum" => "extra.dep.chk"}],
"user_deps" => []}}} "user_deps" => []}}}
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true) expect(File).to receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc") expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env, strict_deps: true).should be_false expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env, strict_deps: true)).to be_falsey
end end
it "returns false when there is a new user dependency" do it "returns false when there is a new user dependency" do
@@ -127,10 +127,10 @@ module Rscons
"user_deps" => []}}} "user_deps" => []}}}
cache = build_from(_cache) cache = build_from(_cache)
env = "env" env = "env"
env.should_receive(:get_user_deps).with("target").and_return(["file.ld"]) expect(env).to receive(:get_user_deps).with("target").and_return(["file.ld"])
File.should_receive(:exists?).with("target").and_return(true) expect(File).to receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc") expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", ["dep.1"], env).should be_false expect(cache.up_to_date?("target", "command", ["dep.1"], env)).to be_falsey
end end
it "returns false when a user dependency checksum has changed" do it "returns false when a user dependency checksum has changed" do
@@ -146,14 +146,14 @@ module Rscons
"checksum" => "user.dep.chk"}]}}} "checksum" => "user.dep.chk"}]}}}
cache = build_from(_cache) cache = build_from(_cache)
env = "env" env = "env"
env.should_receive(:get_user_deps).with("target").and_return(["user.dep"]) expect(env).to receive(:get_user_deps).with("target").and_return(["user.dep"])
File.should_receive(:exists?).with("target").and_return(true) expect(File).to receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc") expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk") expect(cache).to receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk") expect(cache).to receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk")
cache.should_receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk") expect(cache).to receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk")
cache.should_receive(:calculate_checksum).with("user.dep").and_return("INCORRECT") expect(cache).to receive(:calculate_checksum).with("user.dep").and_return("INCORRECT")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], env).should be_false expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], env)).to be_falsey
end end
it "returns true when no condition for false is met" do it "returns true when no condition for false is met" do
@@ -167,12 +167,12 @@ module Rscons
"checksum" => "extra.dep.chk"}], "checksum" => "extra.dep.chk"}],
"user_deps" => []}}} "user_deps" => []}}}
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true) expect(File).to receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc") expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk") expect(cache).to receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk") expect(cache).to receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk")
cache.should_receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk") expect(cache).to receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env).should be_true expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env)).to be_truthy
end end
end end
@@ -181,21 +181,21 @@ module Rscons
_cache = {} _cache = {}
cache = build_from(_cache) cache = build_from(_cache)
env = "env" env = "env"
env.should_receive(:get_user_deps).with("the target").and_return(["user.dep"]) expect(env).to receive(:get_user_deps).with("the target").and_return(["user.dep"])
cache.should_receive(:calculate_checksum).with("the target").and_return("the checksum") expect(cache).to receive(:calculate_checksum).with("the target").and_return("the checksum")
cache.should_receive(:calculate_checksum).with("dep 1").and_return("dep 1 checksum") expect(cache).to receive(:calculate_checksum).with("dep 1").and_return("dep 1 checksum")
cache.should_receive(:calculate_checksum).with("dep 2").and_return("dep 2 checksum") expect(cache).to receive(:calculate_checksum).with("dep 2").and_return("dep 2 checksum")
cache.should_receive(:calculate_checksum).with("user.dep").and_return("user.dep checksum") expect(cache).to receive(:calculate_checksum).with("user.dep").and_return("user.dep checksum")
cache.register_build("the target", "the command", ["dep 1", "dep 2"], env) cache.register_build("the target", "the command", ["dep 1", "dep 2"], env)
cached_target = cache.instance_variable_get(:@cache)["targets"]["the target"] cached_target = cache.instance_variable_get(:@cache)["targets"]["the target"]
cached_target.should_not be_nil expect(cached_target).to_not be_nil
cached_target["command"].should == Digest::MD5.hexdigest("the command".inspect) expect(cached_target["command"]).to eq Digest::MD5.hexdigest("the command".inspect)
cached_target["checksum"].should == "the checksum" expect(cached_target["checksum"]).to eq "the checksum"
cached_target["deps"].should == [ expect(cached_target["deps"]).to eq [
{"fname" => "dep 1", "checksum" => "dep 1 checksum"}, {"fname" => "dep 1", "checksum" => "dep 1 checksum"},
{"fname" => "dep 2", "checksum" => "dep 2 checksum"}, {"fname" => "dep 2", "checksum" => "dep 2 checksum"},
] ]
cached_target["user_deps"].should == [ expect(cached_target["user_deps"]).to eq [
{"fname" => "user.dep", "checksum" => "user.dep checksum"}, {"fname" => "user.dep", "checksum" => "user.dep checksum"},
] ]
end end
@@ -204,7 +204,7 @@ module Rscons
describe "#targets" do describe "#targets" do
it "returns a list of targets that are cached" do it "returns a list of targets that are cached" do
cache = {"targets" => {"t1" => {}, "t2" => {}, "t3" => {}}} cache = {"targets" => {"t1" => {}, "t2" => {}, "t3" => {}}}
build_from(cache).targets.should == ["t1", "t2", "t3"] expect(build_from(cache).targets).to eq ["t1", "t2", "t3"]
end end
end end
@@ -212,35 +212,35 @@ module Rscons
it "makes directories and records any created in the cache" do it "makes directories and records any created in the cache" do
_cache = {} _cache = {}
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("one").and_return(true) expect(File).to receive(:exists?).with("one").and_return(true)
File.should_receive(:exists?).with("one/two").and_return(false) expect(File).to receive(:exists?).with("one/two").and_return(false)
FileUtils.should_receive(:mkdir).with("one/two") expect(FileUtils).to receive(:mkdir).with("one/two")
File.should_receive(:exists?).with("one/two/three").and_return(false) expect(File).to receive(:exists?).with("one/two/three").and_return(false)
FileUtils.should_receive(:mkdir).with("one/two/three") expect(FileUtils).to receive(:mkdir).with("one/two/three")
File.should_receive(:exists?).with("one").and_return(true) expect(File).to receive(:exists?).with("one").and_return(true)
File.should_receive(:exists?).with("one/two").and_return(true) expect(File).to receive(:exists?).with("one/two").and_return(true)
File.should_receive(:exists?).with("one/two/four").and_return(false) expect(File).to receive(:exists?).with("one/two/four").and_return(false)
FileUtils.should_receive(:mkdir).with("one/two/four") expect(FileUtils).to receive(:mkdir).with("one/two/four")
cache.mkdir_p("one/two/three") cache.mkdir_p("one/two/three")
cache.mkdir_p("one\\two\\four") cache.mkdir_p("one\\two\\four")
cache.directories.should == ["one/two", "one/two/three", "one/two/four"] expect(cache.directories).to eq ["one/two", "one/two/three", "one/two/four"]
end end
it "handles absolute paths" do it "handles absolute paths" do
_cache = {} _cache = {}
cache = build_from(_cache) cache = build_from(_cache)
File.should_receive(:exists?).with("/one").and_return(true) expect(File).to receive(:exists?).with("/one").and_return(true)
File.should_receive(:exists?).with("/one/two").and_return(false) expect(File).to receive(:exists?).with("/one/two").and_return(false)
FileUtils.should_receive(:mkdir).with("/one/two") expect(FileUtils).to receive(:mkdir).with("/one/two")
cache.mkdir_p("/one/two") cache.mkdir_p("/one/two")
cache.directories.should == ["/one/two"] expect(cache.directories).to eq ["/one/two"]
end end
end end
describe "#directories" do describe "#directories" do
it "returns a list of directories that are cached" do it "returns a list of directories that are cached" do
_cache = {"directories" => {"dir1" => true, "dir2" => true}} _cache = {"directories" => {"dir1" => true, "dir2" => true}}
build_from(_cache).directories.should == ["dir1", "dir2"] expect(build_from(_cache).directories).to eq ["dir1", "dir2"]
end end
end end
@@ -248,23 +248,23 @@ module Rscons
it "does not re-calculate the checksum when it is already cached" do it "does not re-calculate the checksum when it is already cached" do
cache = build_from({}) cache = build_from({})
cache.instance_variable_set(:@lookup_checksums, {"f1" => "f1.chk"}) cache.instance_variable_set(:@lookup_checksums, {"f1" => "f1.chk"})
cache.should_not_receive(:calculate_checksum) expect(cache).to_not receive(:calculate_checksum)
cache.send(:lookup_checksum, "f1").should == "f1.chk" expect(cache.send(:lookup_checksum, "f1")).to eq "f1.chk"
end end
it "calls calculate_checksum when the checksum is not cached" do it "calls calculate_checksum when the checksum is not cached" do
cache = build_from({}) cache = build_from({})
cache.should_receive(:calculate_checksum).with("f1").and_return("ck") expect(cache).to receive(:calculate_checksum).with("f1").and_return("ck")
cache.send(:lookup_checksum, "f1").should == "ck" expect(cache.send(:lookup_checksum, "f1")).to eq "ck"
end end
end end
describe "#calculate_checksum" do describe "#calculate_checksum" do
it "calculates the MD5 of the file contents" do it "calculates the MD5 of the file contents" do
contents = "contents" contents = "contents"
File.should_receive(:read).with("fname", mode: "rb").and_return(contents) expect(File).to receive(:read).with("fname", mode: "rb").and_return(contents)
Digest::MD5.should_receive(:hexdigest).with(contents).and_return("the_checksum") expect(Digest::MD5).to receive(:hexdigest).with(contents).and_return("the_checksum")
build_from({}).send(:calculate_checksum, "fname").should == "the_checksum" expect(build_from({}).send(:calculate_checksum, "fname")).to eq "the_checksum"
end end
end end
end end
+189 -87
View File
@@ -3,42 +3,63 @@ module Rscons
describe "#initialize" do describe "#initialize" do
it "adds the default builders when they are not excluded" do it "adds the default builders when they are not excluded" do
env = Environment.new env = Environment.new
env.builders.size.should be > 0 expect(env.builders.size).to be > 0
env.builders.map {|name, builder| builder.is_a?(Builder)}.all?.should be_true expect(env.builders.map {|name, builder| builder.is_a?(Builder)}.all?).to be_truthy
env.builders.find {|name, builder| name == "Object"}.should_not be_nil expect(env.builders.find {|name, builder| name == "Object"}).to_not be_nil
env.builders.find {|name, builder| name == "Program"}.should_not be_nil expect(env.builders.find {|name, builder| name == "Program"}).to_not be_nil
env.builders.find {|name, builder| name == "Library"}.should_not be_nil expect(env.builders.find {|name, builder| name == "Library"}).to_not be_nil
end end
it "excludes the default builders with exclude_builders: :all" do it "excludes the default builders with exclude_builders: :all" do
env = Environment.new(exclude_builders: true) env = Environment.new(exclude_builders: true)
env.builders.size.should == 0 expect(env.builders.size).to eq 0
end end
context "when a block is given" do context "when a block is given" do
it "yields self and invokes #process()" do it "yields self and invokes #process()" do
env = Environment.new do |env| env = Environment.new do |env|
env.should_receive(:process) expect(env).to receive(:process)
end end
end end
end end
end end
describe "#clone" do describe "#clone" do
it 'should create unique copies of each construction variable' do it 'creates unique copies of each construction variable' do
env = Environment.new env = Environment.new
env["CPPPATH"] << "path1" env["CPPPATH"] << "path1"
env2 = env.clone env2 = env.clone
env2["CPPPATH"] << "path2" env2["CPPPATH"] << "path2"
env["CPPPATH"].should == ["path1"] expect(env["CPPPATH"]).to eq ["path1"]
env2["CPPPATH"].should == ["path1", "path2"] expect(env2["CPPPATH"]).to eq ["path1", "path2"]
end
it "supports nil, false, true, String, Symbol, Array, Hash, and Integer variables" do
env = Environment.new
env["nil"] = nil
env["false"] = false
env["true"] = true
env["String"] = "String"
env["Symbol"] = :Symbol
env["Array"] = ["a", "b"]
env["Hash"] = {"a" => "b"}
env["Integer"] = 1234
env2 = env.clone
expect(env2["nil"]).to be_nil
expect(env2["false"].object_id).to eq(false.object_id)
expect(env2["true"].object_id).to eq(true.object_id)
expect(env2["String"]).to eq("String")
expect(env2["Symbol"]).to eq(:Symbol)
expect(env2["Array"]).to eq(["a", "b"])
expect(env2["Hash"]).to eq({"a" => "b"})
expect(env2["Integer"]).to eq(1234)
end end
context "when a block is given" do context "when a block is given" do
it "yields self and invokes #process()" do it "yields self and invokes #process()" do
env = Environment.new env = Environment.new
env.clone do |env2| env.clone do |env2|
env2.should_receive(:process) expect(env2).to receive(:process)
end end
end end
end end
@@ -47,9 +68,9 @@ module Rscons
describe "#add_builder" do describe "#add_builder" do
it "adds the builder to the list of builders" do it "adds the builder to the list of builders" do
env = Environment.new(exclude_builders: true) env = Environment.new(exclude_builders: true)
env.builders.keys.should == [] expect(env.builders.keys).to eq []
env.add_builder(Rscons::Builders::Object.new) env.add_builder(Rscons::Builders::Object.new)
env.builders.keys.should == ["Object"] expect(env.builders.keys).to eq ["Object"]
end end
end end
@@ -57,20 +78,20 @@ module Rscons
context "with no build directories" do context "with no build directories" do
it "returns the name of the source file with suffix changed" do it "returns the name of the source file with suffix changed" do
env = Environment.new env = Environment.new
env.get_build_fname("src/dir/file.c", ".o").should == "src/dir/file.o" expect(env.get_build_fname("src/dir/file.c", ".o")).to eq "src/dir/file.o"
env.get_build_fname("src\\dir\\other.d", ".a").should == "src/dir/other.a" expect(env.get_build_fname("src\\dir\\other.d", ".a")).to eq "src/dir/other.a"
env.get_build_fname("source.cc", ".o").should == "source.o" expect(env.get_build_fname("source.cc", ".o")).to eq "source.o"
end end
context "with a build_root" do context "with a build_root" do
it "uses the build_root unless the path is absolute" do it "uses the build_root unless the path is absolute" do
env = Environment.new env = Environment.new
env.build_root = "build/proj" env.build_root = "build/proj"
env.get_build_fname("src/dir/file.c", ".o").should == "build/proj/src/dir/file.o" expect(env.get_build_fname("src/dir/file.c", ".o")).to eq "build/proj/src/dir/file.o"
env.get_build_fname("/some/lib.c", ".a").should == "/some/lib.a" expect(env.get_build_fname("/some/lib.c", ".a")).to eq "/some/lib.a"
env.get_build_fname("C:\\abspath\\mod.cc", ".o").should == "C:/abspath/mod.o" expect(env.get_build_fname("C:\\abspath\\mod.cc", ".o")).to eq "C:/abspath/mod.o"
env.get_build_fname("build\\proj\\generated.c", ".o").should == "build/proj/generated.o" expect(env.get_build_fname("build\\proj\\generated.c", ".o")).to eq "build/proj/generated.o"
env.get_build_fname("build/proj.XX", ".yy").should == "build/proj/build/proj.yy" expect(env.get_build_fname("build/proj.XX", ".yy")).to eq "build/proj/build/proj.yy"
end end
end end
end end
@@ -80,10 +101,10 @@ module Rscons
env = Environment.new env = Environment.new
env.build_dir("src", "bld") env.build_dir("src", "bld")
env.build_dir(%r{^libs/([^/]+)}, 'build/libs/\1') env.build_dir(%r{^libs/([^/]+)}, 'build/libs/\1')
env.get_build_fname("src/input.cc", ".o").should == "bld/input.o" expect(env.get_build_fname("src/input.cc", ".o")).to eq "bld/input.o"
env.get_build_fname("libs/lib1/some/file.c", ".o").should == "build/libs/lib1/some/file.o" expect(env.get_build_fname("libs/lib1/some/file.c", ".o")).to eq "build/libs/lib1/some/file.o"
env.get_build_fname("libs/otherlib/otherlib.cc", ".o").should == "build/libs/otherlib/otherlib.o" expect(env.get_build_fname("libs/otherlib/otherlib.cc", ".o")).to eq "build/libs/otherlib/otherlib.o"
env.get_build_fname("other_directory/o.d", ".a").should == "other_directory/o.a" expect(env.get_build_fname("other_directory/o.d", ".a")).to eq "other_directory/o.a"
end end
context "with a build_root" do context "with a build_root" do
@@ -93,11 +114,11 @@ module Rscons
env.build_dir(%r{^libs/([^/]+)}, 'build/libs/\1') env.build_dir(%r{^libs/([^/]+)}, 'build/libs/\1')
env.build_root = "bldit" env.build_root = "bldit"
env.get_build_fname("src/input.cc", ".o").should == "bld/input.o" expect(env.get_build_fname("src/input.cc", ".o")).to eq "bld/input.o"
env.get_build_fname("libs/lib1/some/file.c", ".o").should == "build/libs/lib1/some/file.o" expect(env.get_build_fname("libs/lib1/some/file.c", ".o")).to eq "build/libs/lib1/some/file.o"
env.get_build_fname("libs/otherlib/otherlib.cc", ".o").should == "build/libs/otherlib/otherlib.o" expect(env.get_build_fname("libs/otherlib/otherlib.cc", ".o")).to eq "build/libs/otherlib/otherlib.o"
env.get_build_fname("other_directory/o.d", ".a").should == "bldit/other_directory/o.a" expect(env.get_build_fname("other_directory/o.d", ".a")).to eq "bldit/other_directory/o.a"
env.get_build_fname("bldit/some/mod.d", ".a").should == "bldit/some/mod.a" expect(env.get_build_fname("bldit/some/mod.d", ".a")).to eq "bldit/some/mod.a"
end end
end end
end end
@@ -107,7 +128,7 @@ module Rscons
it "allows reading construction variables" do it "allows reading construction variables" do
env = Environment.new env = Environment.new
env["CFLAGS"] = ["-g", "-Wall"] env["CFLAGS"] = ["-g", "-Wall"]
env["CFLAGS"].should == ["-g", "-Wall"] expect(env["CFLAGS"]).to eq ["-g", "-Wall"]
end end
end end
@@ -117,9 +138,9 @@ module Rscons
env["CFLAGS"] = ["-g", "-Wall"] env["CFLAGS"] = ["-g", "-Wall"]
env["CFLAGS"] -= ["-g"] env["CFLAGS"] -= ["-g"]
env["CFLAGS"] += ["-O3"] env["CFLAGS"] += ["-O3"]
env["CFLAGS"].should == ["-Wall", "-O3"] expect(env["CFLAGS"]).to eq ["-Wall", "-O3"]
env["other_var"] = "val33" env["other_var"] = "val33"
env["other_var"].should == "val33" expect(env["other_var"]).to eq "val33"
end end
end end
@@ -129,8 +150,8 @@ module Rscons
env["CFLAGS"] = ["-g"] env["CFLAGS"] = ["-g"]
env["CPPPATH"] = ["inc"] env["CPPPATH"] = ["inc"]
env.append("CFLAGS" => ["-Wall"], "CPPPATH" => ["include"]) env.append("CFLAGS" => ["-Wall"], "CPPPATH" => ["include"])
env["CFLAGS"].should == ["-Wall"] expect(env["CFLAGS"]).to eq ["-Wall"]
env["CPPPATH"].should == ["include"] expect(env["CPPPATH"]).to eq ["include"]
end end
end end
@@ -140,10 +161,10 @@ module Rscons
env.Program("a.out", "main.c") env.Program("a.out", "main.c")
cache = "cache" cache = "cache"
Cache.should_receive(:instance).and_return(cache) expect(Cache).to receive(:instance).and_return(cache)
cache.should_receive(:clear_checksum_cache!) expect(cache).to receive(:clear_checksum_cache!)
env.should_receive(:run_builder).with(anything, "a.out", ["main.c"], cache, {}).and_return(true) expect(env).to receive(:run_builder).with(anything, "a.out", ["main.c"], cache, {}).and_return(true)
cache.should_receive(:write) expect(cache).to receive(:write)
env.process env.process
end end
@@ -154,11 +175,11 @@ module Rscons
env.Object("main.o", "other.cc") env.Object("main.o", "other.cc")
cache = "cache" cache = "cache"
Cache.should_receive(:instance).and_return(cache) expect(Cache).to receive(:instance).and_return(cache)
cache.should_receive(:clear_checksum_cache!) expect(cache).to receive(:clear_checksum_cache!)
env.should_receive(:run_builder).with(anything, "main.o", ["other.cc"], cache, {}).and_return("main.o") expect(env).to receive(:run_builder).with(anything, "main.o", ["other.cc"], cache, {}).and_return("main.o")
env.should_receive(:run_builder).with(anything, "a.out", ["main.o"], cache, {}).and_return("a.out") expect(env).to receive(:run_builder).with(anything, "a.out", ["main.o"], cache, {}).and_return("a.out")
cache.should_receive(:write) expect(cache).to receive(:write)
env.process env.process
end end
@@ -169,10 +190,10 @@ module Rscons
env.Object("main.o", "other.cc") env.Object("main.o", "other.cc")
cache = "cache" cache = "cache"
Cache.should_receive(:instance).and_return(cache) expect(Cache).to receive(:instance).and_return(cache)
cache.should_receive(:clear_checksum_cache!) expect(cache).to receive(:clear_checksum_cache!)
env.should_receive(:run_builder).with(anything, "main.o", ["other.cc"], cache, {}).and_return(false) expect(env).to receive(:run_builder).with(anything, "main.o", ["other.cc"], cache, {}).and_return(false)
cache.should_receive(:write) expect(cache).to receive(:write)
expect { env.process }.to raise_error BuildError, /Failed.to.build.main.o/ expect { env.process }.to raise_error BuildError, /Failed.to.build.main.o/
end end
@@ -182,12 +203,12 @@ module Rscons
env.Object("module.o", "module.c") env.Object("module.o", "module.c")
cache = "cache" cache = "cache"
Cache.should_receive(:instance).and_return(cache) expect(Cache).to receive(:instance).and_return(cache)
cache.should_receive(:clear_checksum_cache!) expect(cache).to receive(:clear_checksum_cache!)
env.stub(:run_builder) do |builder, target, sources, cache, vars| allow(env).to receive(:run_builder) do |builder, target, sources, cache, vars|
raise "Ruby exception thrown by builder" raise "Ruby exception thrown by builder"
end end
cache.should_receive(:write) expect(cache).to receive(:write)
expect { env.process }.to raise_error RuntimeError, /Ruby exception thrown by builder/ expect { env.process }.to raise_error RuntimeError, /Ruby exception thrown by builder/
end end
@@ -213,7 +234,7 @@ module Rscons
env["specialflag"] = "-z" env["specialflag"] = "-z"
template = ["cmd", "-I${path}", "${flags}", "${_source}", "${_dest}"] template = ["cmd", "-I${path}", "${flags}", "${_source}", "${_dest}"]
cmd = env.build_command(template, "_source" => "infile", "_dest" => "outfile") cmd = env.build_command(template, "_source" => "infile", "_dest" => "outfile")
cmd.should == ["cmd", "-Idir1", "-Idir2", "-x", "-y", "-z", "infile", "outfile"] expect(cmd).to eq ["cmd", "-Idir1", "-Idir2", "-x", "-y", "-z", "infile", "outfile"]
end end
end end
@@ -223,11 +244,12 @@ module Rscons
env["path"] = ["dir1", "dir2"] env["path"] = ["dir1", "dir2"]
env["flags"] = ["-x", "-y", "${specialflag}"] env["flags"] = ["-x", "-y", "${specialflag}"]
env["specialflag"] = "-z" env["specialflag"] = "-z"
env.expand_varref(["-p${path}", "${flags}"]).should == ["-pdir1", "-pdir2", "-x", "-y", "-z"] env["foo"] = {}
env.expand_varref("foo").should == "foo" expect(env.expand_varref(["-p${path}", "${flags}"])).to eq ["-pdir1", "-pdir2", "-x", "-y", "-z"]
expect {env.expand_varref("${foo}")}.to raise_error /expand.a.variable.reference/ expect(env.expand_varref("foo")).to eq "foo"
env.expand_varref("${specialflag}").should == "-z" expect {env.expand_varref("${foo}")}.to raise_error /Unknown.varref.type/
env.expand_varref("${path}").should == ["dir1", "dir2"] expect(env.expand_varref("${specialflag}")).to eq "-z"
expect(env.expand_varref("${path}")).to eq ["dir1", "dir2"]
end end
end end
@@ -236,8 +258,8 @@ module Rscons
context "with no errors" do context "with no errors" do
it "prints the short description and executes the command" do it "prints the short description and executes the command" do
env = Environment.new(echo: :short) env = Environment.new(echo: :short)
env.should_receive(:puts).with("short desc") expect(env).to receive(:puts).with("short desc")
env.should_receive(:system).with("a", "command").and_return(true) expect(env).to receive(:system).with(*Rscons.command_executer, "a", "command").and_return(true)
env.execute("short desc", ["a", "command"]) env.execute("short desc", ["a", "command"])
end end
end end
@@ -245,10 +267,10 @@ module Rscons
context "with errors" do context "with errors" do
it "prints the short description, executes the command, and prints the failed command line" do it "prints the short description, executes the command, and prints the failed command line" do
env = Environment.new(echo: :short) env = Environment.new(echo: :short)
env.should_receive(:puts).with("short desc") expect(env).to receive(:puts).with("short desc")
env.should_receive(:system).with("a", "command").and_return(false) expect(env).to receive(:system).with(*Rscons.command_executer, "a", "command").and_return(false)
$stdout.should_receive(:write).with("Failed command was: ") expect($stdout).to receive(:write).with("Failed command was: ")
env.should_receive(:puts).with("a command") expect(env).to receive(:puts).with("a command")
env.execute("short desc", ["a", "command"]) env.execute("short desc", ["a", "command"])
end end
end end
@@ -257,8 +279,8 @@ module Rscons
context "with echo: :command" do context "with echo: :command" do
it "prints the command executed and executes the command" do it "prints the command executed and executes the command" do
env = Environment.new(echo: :command) env = Environment.new(echo: :command)
env.should_receive(:puts).with("a command '--arg=val with spaces'") expect(env).to receive(:puts).with("a command '--arg=val with spaces'")
env.should_receive(:system).with({modified: :environment}, "a", "command", "--arg=val with spaces", {opt: :val}).and_return(false) expect(env).to receive(:system).with({modified: :environment}, *Rscons.command_executer, "a", "command", "--arg=val with spaces", {opt: :val}).and_return(false)
env.execute("short desc", ["a", "command", "--arg=val with spaces"], env: {modified: :environment}, options: {opt: :val}) env.execute("short desc", ["a", "command", "--arg=val with spaces"], env: {modified: :environment}, options: {opt: :val})
end end
end end
@@ -272,14 +294,14 @@ module Rscons
it "records the target when the target method is a known builder" do it "records the target when the target method is a known builder" do
env = Environment.new env = Environment.new
env.instance_variable_get(:@targets).should == {} expect(env.instance_variable_get(:@targets)).to eq({})
env.Program("target", ["src1", "src2"], var: "val") env.Object("target.o", ["src1.c", "src2.c"], var: "val")
target = env.instance_variable_get(:@targets)["target"] target = env.instance_variable_get(:@targets)["target.o"]
target.should_not be_nil expect(target).to_not be_nil
target[:builder].is_a?(Builder).should be_true expect(target[:builder].is_a?(Builder)).to be_truthy
target[:sources].should == ["src1", "src2"] expect(target[:sources]).to eq ["src1.c", "src2.c"]
target[:vars].should == {var: "val"} expect(target[:vars]).to eq({var: "val"})
target[:args].should == [] expect(target[:args]).to eq []
end end
it "raises an error when vars is not a Hash" do it "raises an error when vars is not a Hash" do
@@ -292,13 +314,20 @@ module Rscons
it "records the given dependencies in @user_deps" do it "records the given dependencies in @user_deps" do
env = Environment.new env = Environment.new
env.depends("foo", "bar", "baz") env.depends("foo", "bar", "baz")
env.instance_variable_get(:@user_deps).should == {"foo" => ["bar", "baz"]} expect(env.instance_variable_get(:@user_deps)).to eq({"foo" => ["bar", "baz"]})
end end
it "records user dependencies only once" do it "records user dependencies only once" do
env = Environment.new env = Environment.new
env.instance_variable_set(:@user_deps, {"foo" => ["bar"]}) env.instance_variable_set(:@user_deps, {"foo" => ["bar"]})
env.depends("foo", "bar", "baz") env.depends("foo", "bar", "baz")
env.instance_variable_get(:@user_deps).should == {"foo" => ["bar", "baz"]} expect(env.instance_variable_get(:@user_deps)).to eq({"foo" => ["bar", "baz"]})
end
it "expands arguments for construction variable references" do
env = Environment.new
env["foo"] = "foo.exe"
env["bar"] = "bar.c"
env.depends("${foo}", "${bar}", "a.h")
expect(env.instance_variable_get(:@user_deps)).to eq({"foo.exe" => ["bar.c", "a.h"]})
end end
end end
@@ -313,9 +342,9 @@ module Rscons
cache = "cache" cache = "cache"
env = Environment.new env = Environment.new
env.add_builder(ABuilder.new) env.add_builder(ABuilder.new)
env.builders["Object"].should_receive(:run).with("mod.o", ["mod.c"], cache, env, anything).and_return("mod.o") expect(env.builders["Object"]).to receive(:run).with("mod.o", ["mod.c"], cache, env, anything).and_return("mod.o")
env.builders["ABuilder"].should_receive(:run).with("mod2.ab_out", ["mod2.ab_in"], cache, env, anything).and_return("mod2.ab_out") expect(env.builders["ABuilder"]).to receive(:run).with("mod2.ab_out", ["mod2.ab_in"], cache, env, anything).and_return("mod2.ab_out")
env.build_sources(["precompiled.o", "mod.c", "mod2.ab_in"], [".o", ".ab_out"], cache, {}).should == ["precompiled.o", "mod.o", "mod2.ab_out"] expect(env.build_sources(["precompiled.o", "mod.c", "mod2.ab_in"], [".o", ".ab_out"], cache, {})).to eq ["precompiled.o", "mod.o", "mod2.ab_out"]
end end
end end
@@ -327,32 +356,105 @@ module Rscons
build_op[:vars]["CFLAGS"] += ["-O3", "-DSPECIAL"] build_op[:vars]["CFLAGS"] += ["-O3", "-DSPECIAL"]
end end
end end
env.builders["Object"].stub(:run) do |target, sources, cache, env, vars| allow(env.builders["Object"]).to receive(:run) do |target, sources, cache, env, vars|
vars["CFLAGS"].should == [] expect(vars["CFLAGS"]).to eq []
end end
env.run_builder(env.builders["Object"], "build/normal/module.o", ["src/normal/module.c"], "cache", {}) env.run_builder(env.builders["Object"], "build/normal/module.o", ["src/normal/module.c"], "cache", {})
env.builders["Object"].stub(:run) do |target, sources, cache, env, vars| allow(env.builders["Object"]).to receive(:run) do |target, sources, cache, env, vars|
vars["CFLAGS"].should == ["-O3", "-DSPECIAL"] expect(vars["CFLAGS"]).to eq ["-O3", "-DSPECIAL"]
end end
env.run_builder(env.builders["Object"], "build/special/module.o", ["src/special/module.c"], "cache", {}) env.run_builder(env.builders["Object"], "build/special/module.o", ["src/special/module.c"], "cache", {})
end end
end end
describe "#shell" do
it "executes the given shell command and returns the results" do
env = Environment.new
expect(env.shell("echo hello").strip).to eq("hello")
end
it "determines shell flag to be /c when SHELL is specified as 'cmd'" do
env = Environment.new
env["SHELL"] = "cmd"
expect(IO).to receive(:popen).with(["cmd", "/c", "my_cmd"])
env.shell("my_cmd")
end
it "determines shell flag to be -c when SHELL is specified as something else" do
env = Environment.new
env["SHELL"] = "my_shell"
expect(IO).to receive(:popen).with(["my_shell", "-c", "my_cmd"])
env.shell("my_cmd")
end
end
describe "#parse_flags" do
it "executes the shell command and parses the returned flags when the input argument begins with !" do
env = Environment.new
env["CFLAGS"] = ["-g"]
expect(env).to receive(:shell).with("my_command").and_return(%[-arch my_arch -Done=two -include ii -isysroot sr -Iincdir -Llibdir -lmy_lib -mno-cygwin -mwindows -pthread -std=c99 -Wa,'asm,args 1 2' -Wl,linker,"args 1 2" -Wp,cpp,args,1,2 -arbitrary +other_arbitrary some_lib /a/b/c/lib])
rv = env.parse_flags("!my_command")
expect(rv).to eq({
"CCFLAGS" => %w[-arch my_arch -include ii -isysroot sr -mno-cygwin -pthread -arbitrary +other_arbitrary],
"LDFLAGS" => %w[-arch my_arch -isysroot sr -mno-cygwin -mwindows -pthread] + ["linker", "args 1 2"] + %w[+other_arbitrary],
"CPPPATH" => %w[incdir],
"LIBS" => %w[my_lib some_lib /a/b/c/lib],
"LIBPATH" => %w[libdir],
"CPPDEFINES" => %w[one=two],
"CFLAGS" => %w[-std=c99],
"ASFLAGS" => ["asm", "args 1 2"],
"CPPFLAGS" => %w[cpp args 1 2],
})
expect(env["CFLAGS"]).to eq(["-g"])
expect(env["ASFLAGS"]).to eq([])
env.merge_flags(rv)
expect(env["CFLAGS"]).to eq(["-g", "-std=c99"])
expect(env["ASFLAGS"]).to eq(["asm", "args 1 2"])
end
end
describe "#parse_flags!" do
it "parses the given build flags and merges them into the Environment" do
env = Environment.new
env["CFLAGS"] = ["-g"]
rv = env.parse_flags!("-I incdir -D my_define -L /a/libdir -l /some/lib")
expect(rv).to eq({
"CPPPATH" => %w[incdir],
"LIBS" => %w[/some/lib],
"LIBPATH" => %w[/a/libdir],
"CPPDEFINES" => %w[my_define],
})
expect(env["CPPPATH"]).to eq(%w[incdir])
expect(env["LIBS"]).to eq(%w[/some/lib])
expect(env["LIBPATH"]).to eq(%w[/a/libdir])
expect(env["CPPDEFINES"]).to eq(%w[my_define])
end
end
describe "#merge_flags" do
it "appends array contents and replaces other variable values" do
env = Environment.new
env["CPPPATH"] = ["incdir"]
env["CSUFFIX"] = ".x"
env.merge_flags("CPPPATH" => ["a"], "CSUFFIX" => ".c")
expect(env["CPPPATH"]).to eq(%w[incdir a])
expect(env["CSUFFIX"]).to eq(".c")
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) expect(File).to receive(:read).with('makefile').and_return(<<EOS)
module.o: source.cc module.o: source.cc
EOS EOS
Environment.parse_makefile_deps('makefile', 'module.o').should == ['source.cc'] expect(Environment.parse_makefile_deps('makefile', 'module.o')).to eq ['source.cc']
end end
it 'handles dependencies split across many lines' do it 'handles dependencies split across many lines' do
File.should_receive(:read).with('makefile').and_return(<<EOS) expect(File).to receive(:read).with('makefile').and_return(<<EOS)
module.o: module.c \\ module.o: module.c \\
module.h \\ module.h \\
other.h other.h
EOS EOS
Environment.parse_makefile_deps('makefile', 'module.o').should == [ expect(Environment.parse_makefile_deps('makefile', 'module.o')).to eq [
'module.c', 'module.h', 'other.h'] 'module.c', 'module.h', 'other.h']
end end
end end
+49 -36
View File
@@ -3,20 +3,20 @@ module Rscons
describe '#initialize' do describe '#initialize' do
it "initializes variables from a Hash" do it "initializes variables from a Hash" do
v = VarSet.new({"one" => 1, "two" => :two}) v = VarSet.new({"one" => 1, "two" => :two})
v["one"].should == 1 expect(v["one"]).to eq(1)
v["two"].should == :two expect(v["two"]).to eq(:two)
end end
it "initializes variables from another VarSet" do it "initializes variables from another VarSet" do
v = VarSet.new({"one" => 1}) v = VarSet.new({"one" => 1})
v2 = VarSet.new(v) v2 = VarSet.new(v)
v2["one"].should == 1 expect(v2["one"]).to eq 1
end end
it "makes a deep copy of the given VarSet" do it "makes a deep copy of the given VarSet" do
v = VarSet.new({"array" => [1, 2, 3]}) v = VarSet.new({"array" => [1, 2, 3]})
v2 = VarSet.new(v) v2 = VarSet.new(v)
v["array"] << 4 v["array"] << 4
v["array"].should == [1, 2, 3, 4] expect(v["array"]).to eq([1, 2, 3, 4])
v2["array"].should == [1, 2, 3] expect(v2["array"]).to eq([1, 2, 3])
end end
end end
@@ -24,11 +24,11 @@ module Rscons
it "allows accessing a variable with its verbatim value if type is not specified" do it "allows accessing a variable with its verbatim value if type is not specified" do
v = VarSet.new({"fuz" => "a string", "foo" => 42, "bar" => :baz, v = VarSet.new({"fuz" => "a string", "foo" => 42, "bar" => :baz,
"qax" => [3, 6], "qux" => {a: :b}}) "qax" => [3, 6], "qux" => {a: :b}})
v["fuz"].should == "a string" expect(v["fuz"]).to eq("a string")
v["foo"].should == 42 expect(v["foo"]).to eq(42)
v["bar"].should == :baz expect(v["bar"]).to eq(:baz)
v["qax"].should == [3, 6] expect(v["qax"]).to eq([3, 6])
v["qux"].should == {a: :b} expect(v["qux"]).to eq({a: :b})
end end
end end
@@ -36,8 +36,8 @@ module Rscons
it "allows assigning to variables" do it "allows assigning to variables" do
v = VarSet.new("CFLAGS" => ["-Wall", "-O3"]) v = VarSet.new("CFLAGS" => ["-Wall", "-O3"])
v["CPPPATH"] = ["one", "two"] v["CPPPATH"] = ["one", "two"]
v["CFLAGS"].should == ["-Wall", "-O3"] expect(v["CFLAGS"]).to eq(["-Wall", "-O3"])
v["CPPPATH"].should == ["one", "two"] expect(v["CPPPATH"]).to eq(["one", "two"])
end end
end end
@@ -45,20 +45,20 @@ module Rscons
it "returns whether the variable is in the VarSet" do it "returns whether the variable is in the VarSet" do
v = VarSet.new("CFLAGS" => [], :foo => :bar) v = VarSet.new("CFLAGS" => [], :foo => :bar)
expect(v.include?("CFLAGS")).to be_true expect(v.include?("CFLAGS")).to be_truthy
expect(v.include?(:CFLAGS)).to be_false expect(v.include?(:CFLAGS)).to be_falsey
expect(v.include?(:foo)).to be_true expect(v.include?(:foo)).to be_truthy
expect(v.include?("foo")).to be_false expect(v.include?("foo")).to be_falsey
expect(v.include?("bar")).to be_false expect(v.include?("bar")).to be_falsey
v2 = v.clone v2 = v.clone
v2.append("bar" => []) v2.append("bar" => [])
expect(v2.include?("CFLAGS")).to be_true expect(v2.include?("CFLAGS")).to be_truthy
expect(v2.include?(:CFLAGS)).to be_false expect(v2.include?(:CFLAGS)).to be_falsey
expect(v2.include?(:foo)).to be_true expect(v2.include?(:foo)).to be_truthy
expect(v2.include?("foo")).to be_false expect(v2.include?("foo")).to be_falsey
expect(v2.include?("bar")).to be_true expect(v2.include?("bar")).to be_truthy
end end
end end
@@ -125,34 +125,47 @@ module Rscons
"CC" => "gcc", "CC" => "gcc",
"CPPPATH" => ["dir1", "dir2"], "CPPPATH" => ["dir1", "dir2"],
"compiler" => "${CC}", "compiler" => "${CC}",
"cmd" => ["${CC}", "-c", "${CFLAGS}", "-I${CPPPATH}"]) "cmd" => ["${CC}", "-c", "${CFLAGS}", "-I${CPPPATH}"],
"lambda" => lambda {|args| "#{args[:v]}--12"})
it "expands to the string itself if the string is not a variable reference" do it "expands to the string itself if the string is not a variable reference" do
v.expand_varref("CC").should == "CC" expect(v.expand_varref("CC", :lambda_args)).to eq("CC")
v.expand_varref("CPPPATH").should == "CPPPATH" expect(v.expand_varref("CPPPATH", :lambda_args)).to eq("CPPPATH")
v.expand_varref("str").should == "str" expect(v.expand_varref("str", :lambda_args)).to eq("str")
end end
it "expands a single variable reference beginning with a '$'" do it "expands a single variable reference beginning with a '$'" do
v.expand_varref("${CC}").should == "gcc" expect(v.expand_varref("${CC}", :lambda_args)).to eq("gcc")
v.expand_varref("${CPPPATH}").should == ["dir1", "dir2"] expect(v.expand_varref("${CPPPATH}", :lambda_args)).to eq(["dir1", "dir2"])
end end
it "expands a single variable reference in ${arr} notation" do it "expands a single variable reference in ${arr} notation" do
v.expand_varref("prefix${CFLAGS}suffix").should == ["prefix-Wallsuffix", "prefix-O2suffix"] expect(v.expand_varref("prefix${CFLAGS}suffix", :lambda_args)).to eq(["prefix-Wallsuffix", "prefix-O2suffix"])
v.expand_varref(v["cmd"]).should == ["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"] expect(v.expand_varref(v["cmd"], :lambda_args)).to eq(["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"])
end end
it "expands a variable reference recursively" do it "expands a variable reference recursively" do
v.expand_varref("${compiler}").should == "gcc" expect(v.expand_varref("${compiler}", :lambda_args)).to eq("gcc")
v.expand_varref("${cmd}").should == ["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"] expect(v.expand_varref("${cmd}", :lambda_args)).to eq(["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"])
end end
it "resolves multiple variable references in one element by enumerating all combinations" do it "resolves multiple variable references in one element by enumerating all combinations" do
v.expand_varref("cflag: ${CFLAGS}, cpppath: ${CPPPATH}, compiler: ${compiler}").should == [ expect(v.expand_varref("cflag: ${CFLAGS}, cpppath: ${CPPPATH}, compiler: ${compiler}", :lambda_args)).to eq([
"cflag: -Wall, cpppath: dir1, compiler: gcc", "cflag: -Wall, cpppath: dir1, compiler: gcc",
"cflag: -O2, cpppath: dir1, compiler: gcc", "cflag: -O2, cpppath: dir1, compiler: gcc",
"cflag: -Wall, cpppath: dir2, compiler: gcc", "cflag: -Wall, cpppath: dir2, compiler: gcc",
"cflag: -O2, cpppath: dir2, compiler: gcc", "cflag: -O2, cpppath: dir2, compiler: gcc",
] ])
end end
it "raises an error when a variable reference refers to a non-existent variable" do it "returns an empty string when a variable reference refers to a non-existent variable" do
expect { v.expand_varref("${not_here}") }.to raise_error /I do not know how to expand a variable reference to a NilClass/ expect(v.expand_varref("${not_here}", :lambda_args)).to eq("")
end
it "calls a lambda with the given lambda arguments" do
expect(v.expand_varref("${lambda}", [v: "fez"])).to eq("fez--12")
end
it "raises an error when given an invalid argument" do
expect { v.expand_varref({a: :b}, :lambda_args) }.to raise_error /Unknown varref type: Hash/
end
it "raises an error when an expanded variable is an unexpected type" do
expect(v).to receive(:[]).at_least(1).times.with("bad").and_return("bad_val")
expect(v).to receive(:expand_varref).with("bad_val", :lambda_args).and_return({a: :b})
expect(v).to receive(:expand_varref).and_call_original
expect { v.expand_varref("${bad}", :lambda_args) }.to raise_error /I do not know how to expand a variable reference to a Hash/
end end
end end
end end
+106 -17
View File
@@ -2,25 +2,114 @@ describe Rscons do
describe ".clean" do describe ".clean" do
it "removes all build targets and created directories" do it "removes all build targets and created directories" do
cache = "cache" cache = "cache"
Rscons::Cache.should_receive(:instance).and_return(cache) expect(Rscons::Cache).to receive(:instance).and_return(cache)
cache.should_receive(:targets).and_return(["build/a.out", "build/main.o"]) expect(cache).to receive(:targets).and_return(["build/a.out", "build/main.o"])
FileUtils.should_receive(:rm_f).with("build/a.out") expect(FileUtils).to receive(:rm_f).with("build/a.out")
FileUtils.should_receive(:rm_f).with("build/main.o") expect(FileUtils).to receive(:rm_f).with("build/main.o")
cache.should_receive(:directories).and_return(["build/one", "build/one/two", "build", "other"]) expect(cache).to receive(:directories).and_return(["build/one", "build/one/two", "build", "other"])
File.should_receive(:directory?).with("build/one/two").and_return(true) expect(File).to receive(:directory?).with("build/one/two").and_return(true)
Dir.should_receive(:entries).with("build/one/two").and_return([".", ".."]) expect(Dir).to receive(:entries).with("build/one/two").and_return([".", ".."])
Dir.should_receive(:rmdir).with("build/one/two") expect(Dir).to receive(:rmdir).with("build/one/two")
File.should_receive(:directory?).with("build/one").and_return(true) expect(File).to receive(:directory?).with("build/one").and_return(true)
Dir.should_receive(:entries).with("build/one").and_return([".", ".."]) expect(Dir).to receive(:entries).with("build/one").and_return([".", ".."])
Dir.should_receive(:rmdir).with("build/one") expect(Dir).to receive(:rmdir).with("build/one")
File.should_receive(:directory?).with("build").and_return(true) expect(File).to receive(:directory?).with("build").and_return(true)
Dir.should_receive(:entries).with("build").and_return([".", ".."]) expect(Dir).to receive(:entries).with("build").and_return([".", ".."])
Dir.should_receive(:rmdir).with("build") expect(Dir).to receive(:rmdir).with("build")
File.should_receive(:directory?).with("other").and_return(true) expect(File).to receive(:directory?).with("other").and_return(true)
Dir.should_receive(:entries).with("other").and_return([".", "..", "other.file"]) expect(Dir).to receive(:entries).with("other").and_return([".", "..", "other.file"])
cache.should_receive(:clear) expect(cache).to receive(:clear)
Rscons.clean Rscons.clean
end end
end end
describe ".get_system_shell" do
before(:each) do
Rscons.class_variable_set(:@@shell, nil)
end
after(:each) do
Rscons.class_variable_set(:@@shell, nil)
end
it "uses the SHELL environment variable if it tests successfully" do
my_ENV = {"SHELL" => "my_shell"}
allow(ENV).to receive(:[]) {|*args| my_ENV[*args]}
io = StringIO.new("success\n")
expect(IO).to receive(:popen).with(["my_shell", "-c", "echo success"]).and_yield(io)
expect(Rscons.get_system_shell).to eq(["my_shell", "-c"])
end
it "uses sh -c on a mingw platform if it tests successfully" do
my_ENV = {"SHELL" => nil}
allow(ENV).to receive(:[]) {|*args| my_ENV[*args]}
io = StringIO.new("success\n")
expect(IO).to receive(:popen).with(["sh", "-c", "echo success"]).and_yield(io)
expect(Object).to receive(:const_get).with("RUBY_PLATFORM").and_return("x86-mingw")
expect(Rscons.get_system_shell).to eq(["sh", "-c"])
end
it "uses cmd /c on a mingw platform if sh -c does not test successfully" do
my_ENV = {"SHELL" => nil}
allow(ENV).to receive(:[]) {|*args| my_ENV[*args]}
io = StringIO.new("success\n")
expect(IO).to receive(:popen).with(["sh", "-c", "echo success"]).and_raise "ENOENT"
expect(Object).to receive(:const_get).with("RUBY_PLATFORM").and_return("x86-mingw")
expect(Rscons.get_system_shell).to eq(["cmd", "/c"])
end
it "uses sh -c on a non-mingw platform if SHELL is not specified" do
my_ENV = {"SHELL" => nil}
allow(ENV).to receive(:[]) {|*args| my_ENV[*args]}
expect(Object).to receive(:const_get).with("RUBY_PLATFORM").and_return("x86-linux")
expect(Rscons.get_system_shell).to eq(["sh", "-c"])
end
end
context "command executer" do
describe ".command_executer" do
before(:each) do
Rscons.class_variable_set(:@@command_executer, nil)
end
after(:each) do
Rscons.class_variable_set(:@@command_executer, nil)
end
it "returns ['env'] if mingw platform in MSYS and 'env' works" do
expect(Object).to receive(:const_get).and_return("x86-mingw")
expect(ENV).to receive(:keys).and_return(["MSYSCON"])
io = StringIO.new("success\n")
expect(IO).to receive(:popen).with(["env", "echo", "success"]).and_yield(io)
expect(Rscons.command_executer).to eq(["env"])
end
it "returns [] if mingw platform in MSYS and 'env' does not work" do
expect(Object).to receive(:const_get).and_return("x86-mingw")
expect(ENV).to receive(:keys).and_return(["MSYSCON"])
expect(IO).to receive(:popen).with(["env", "echo", "success"]).and_raise "ENOENT"
expect(Rscons.command_executer).to eq([])
end
it "returns [] if mingw platform not in MSYS" do
expect(Object).to receive(:const_get).and_return("x86-mingw")
expect(ENV).to receive(:keys).and_return(["COMSPEC"])
expect(Rscons.command_executer).to eq([])
end
it "returns [] if not mingw platform" do
expect(Object).to receive(:const_get).and_return("x86-linux")
expect(Rscons.command_executer).to eq([])
end
end
describe ".command_executer=" do
it "overrides the value of @@command_executer" do
Rscons.class_variable_set(:@@command_executer, ["env"])
Rscons.command_executer = []
expect(Rscons.class_variable_get(:@@command_executer)).to eq([])
end
end
end
end end