Compare commits

..
42 Commits
Author SHA1 Message Date
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
26 changed files with 1458 additions and 477 deletions

No files matched your search

+29 -2
View File
@@ -268,8 +268,8 @@ env.Preprocess(target, source)
env.Preprocess("module-preprocessed.cc", "module.cc")
```
The Preprocess builder invokes either ${CC} or ${CXX} (depending on if the
source contains an extension in ${CXXSUFFIX} or not) and writes the
The Preprocess builder invokes either `${CC}` or `${CXX}` (depending on if the
source contains an extension in `${CXXSUFFIX}` or not) and writes the
preprocessed output to the target file.
#### Program
@@ -329,6 +329,33 @@ http://rubydoc.info/github/holtrop/rscons/frames.
## Release Notes
### 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
- add CFile builder
+75 -5
View File
@@ -1,3 +1,4 @@
require_relative "rscons/build_target"
require_relative "rscons/builder"
require_relative "rscons/cache"
require_relative "rscons/environment"
@@ -14,6 +15,8 @@ require_relative "rscons/builders/program"
# Namespace module for rscons classes
module Rscons
# Names of the default builders which will be added to all newly created
# {Environment} objects.
DEFAULT_BUILDERS = [
:CFile,
:Disassemble,
@@ -23,9 +26,10 @@ module Rscons
:Program,
]
# Class to represent a fatal error while building a target.
class BuildError < RuntimeError; end
# Remove all generated files
# Remove all generated files.
def self.clean
cache = Cache.instance
# remove all built files
@@ -42,16 +46,82 @@ module Rscons
cache.clear
end
# Return whether the given path is an absolute filesystem path or not
# @param path [String] the path to examine
# Return whether the given path is an absolute filesystem path.
#
# @param path [String] the path to examine.
#
# @return [Boolean] Whether the given path is an absolute filesystem path.
def self.absolute_path?(path)
path =~ %r{^(/|\w:[\\/])}
end
# 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)
path.sub(/\.[^.]*$/, suffix)
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
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 Builder
# Return the name of the builder.
#
# If not overridden this defaults to the last component of the class name.
def name
self.class.name.split(":").last
end
# Return a set of default variable values for the Environment to use
# unless the user overrides any.
# Return a set of default construction variables for the builder.
#
# @param env [Environment] The Environment.
#
# @return [Hash] Default construction variables.
def default_variables(env)
{}
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
# 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)
false
end
# Check if the cache is up to date for the target and if not execute the
# build command.
# Return the name of the target or false on failure.
#
# @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)
unless cache.up_to_date?(target, command, sources, env)
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("lex.yy.cc", "parser.ll")
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)
{
"YACC" => "bison",
@@ -18,6 +23,16 @@ module Rscons
}
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)
vars = vars.merge({
"_TARGET" => target,
@@ -32,7 +47,7 @@ module Rscons
else
raise "Unknown source file #{sources.first.inspect} for CFile builder"
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)
end
end
+16 -1
View File
@@ -2,6 +2,11 @@ module Rscons
module Builders
# The Disassemble builder produces a disassembly listing of a source file.
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)
{
"OBJDUMP" => "objdump",
@@ -10,9 +15,19 @@ module Rscons
}
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)
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)
cache.mkdir_p(File.dirname(target))
return false unless env.execute("Disassemble #{target}", command, options: {out: target})
+19 -4
View File
@@ -2,24 +2,39 @@ module Rscons
module Builders
# A default Rscons builder that produces a static library archive.
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)
{
'AR' => 'ar',
'LIBSUFFIX' => '.a',
'ARFLAGS' => [],
'ARCMD' => ['${AR}', 'rcs', '${ARFLAGS}', '${_TARGET}', '${_SOURCES}']
'ARFLAGS' => ['rcs'],
'ARCMD' => ['${AR}', '${ARFLAGS}', '${_TARGET}', '${_SOURCES}']
}
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)
# 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
vars = vars.merge({
'_TARGET' => target,
'_SOURCES' => objects,
})
command = env.build_command(env['ARCMD'], vars)
command = env.build_command("${ARCMD}", vars)
standard_build("AR #{target}", target, command, objects, env, cache)
end
end
+39 -7
View File
@@ -3,6 +3,7 @@ module Rscons
# A default Rscons builder which knows how to produce an object file from
# various types of source files.
class Object < Builder
# Mapping of known sources from which to build object files.
KNOWN_SUFFIXES = {
"AS" => "ASSUFFIX",
"CC" => "CSUFFIX",
@@ -10,47 +11,78 @@ module Rscons
"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)
{
'OBJSUFFIX' => '.o',
'CPPDEFPREFIX' => '-D',
'INCPREFIX' => '-I',
'AS' => '${CC}',
'ASFLAGS' => [],
'ASSUFFIX' => '.S',
'ASPPPATH' => '${CPPPATH}',
'ASPPFLAGS' => '${CPPFLAGS}',
'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' => [],
'CCFLAGS' => [],
'CC' => 'gcc',
'CFLAGS' => [],
'CSUFFIX' => '.c',
'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++',
'CXXFLAGS' => [],
'CXXSUFFIX' => '.cc',
'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',
'DFLAGS' => [],
'DSUFFIX' => '.d',
'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
# 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)
target.end_with?(*env['OBJSUFFIX']) and KNOWN_SUFFIXES.find do |compiler, suffix_var|
source.end_with?(*env[suffix_var])
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)
vars = vars.merge({
'_TARGET' => target,
@@ -58,11 +90,11 @@ module Rscons
'_DEPFILE' => Rscons.set_suffix(target, '.mf'),
})
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|
v.nil? and raise "Error: unknown input file type: #{sources.first.inspect}"
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)
cache.mkdir_p(File.dirname(target))
FileUtils.rm_f(target)
+19 -4
View File
@@ -2,22 +2,37 @@ module Rscons
module Builders
# The Preprocess builder invokes the C preprocessor
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)
{
"CPP_CMD" => ["${_PREPROCESS_CC}", "-E", "-o", "${_TARGET}", "-I${CPPPATH}", "${CPPFLAGS}", "${CFLAGS}", "${_SOURCES}"],
}
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)
pp_cc = if sources.find {|s| s.end_with?(*env["CXXSUFFIX"])}
env["CXX"]
pp_cc = if sources.find {|s| s.end_with?(*env.expand_varref("${CXXSUFFIX}", vars))}
"${CXX}"
else
env["CC"]
"${CC}"
end
vars = vars.merge("_PREPROCESS_CC" => pp_cc,
"_TARGET" => target,
"_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)
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
# executable program.
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)
{
'LD' => nil,
'OBJSUFFIX' => '.o',
'PROGSUFFIX' => (Object.const_get("RUBY_PLATFORM") =~ /mingw|cygwin/ ? ".exe" : ""),
'LD' => nil,
'LIBSUFFIX' => '.a',
'LDFLAGS' => [],
'LIBPATH' => [],
'LIBDIRPREFIX' => '-L',
'LIBLINKPREFIX' => '-l',
'LIBS' => [],
'LDCMD' => ['${LD}', '-o', '${_TARGET}', '${LDFLAGS}', '${_SOURCES}', '-L${LIBPATH}', '-l${LIBS}']
'LDCMD' => ['${LD}', '-o', '${_TARGET}', '${LDFLAGS}', '${_SOURCES}', '${LIBDIRPREFIX}${LIBPATH}', '${LIBLINKPREFIX}${LIBS}']
}
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)
# 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
ld = if env["LD"]
env["LD"]
elsif sources.find {|s| s.end_with?(*env["DSUFFIX"])}
env["DC"]
elsif sources.find {|s| s.end_with?(*env["CXXSUFFIX"])}
env["CXX"]
ld = env.expand_varref("${LD}", vars)
ld = if ld != ""
ld
elsif sources.find {|s| s.end_with?(*env.expand_varref("${DSUFFIX}", vars))}
"${DC}"
elsif sources.find {|s| s.end_with?(*env.expand_varref("${CXXSUFFIX}", vars))}
"${CXX}"
else
env["CC"]
"${CC}"
end
vars = vars.merge({
'_TARGET' => target,
'_SOURCES' => objects,
'LD' => ld,
})
command = env.build_command(env['LDCMD'], vars)
command = env.build_command("${LDCMD}", vars)
standard_build("LD #{target}", target, command, objects, env, cache)
end
end
+26 -6
View File
@@ -84,7 +84,8 @@ module Rscons
@dirty = false
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 command [String, Array] The command used to build the target.
# @param deps [Array] List of the target's dependency files.
@@ -93,6 +94,7 @@ module Rscons
# @option options [Boolean] :strict_deps
# Only consider a target up to date if its list of dependencies is
# exactly equal (including order) to the cached list of dependencies
#
# @return [Boolean]
# True value if the targets are all up to date, meaning that,
# for each target:
@@ -144,11 +146,14 @@ module Rscons
true
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 command [String, Array] The command used to build the target.
# @param deps [Array] List of dependencies for the target.
# @param env [Environment] The {Rscons::Environment}.
#
# @return [void]
def register_build(targets, command, deps, env)
Array(targets).each do |target|
@cache["targets"][target] = {
@@ -171,13 +176,19 @@ module Rscons
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
@cache["targets"].keys
end
# Make any needed directories and record the ones that are created for
# removal upon a "clean" operation.
#
# @param path [String] Directory to create.
#
# @return [void]
def mkdir_p(path)
parts = path.split(/[\\\/]/)
parts.each_index do |i|
@@ -191,7 +202,10 @@ module Rscons
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
@cache["directories"].keys
end
@@ -213,14 +227,20 @@ module Rscons
end
# Return a file's checksum, or the previously calculated checksum for
# the same file
# the same file.
#
# @param file [String] The file name.
#
# @return [String] The file's checksum.
def lookup_checksum(file)
@lookup_checksums[file] || calculate_checksum(file)
end
# Calculate and return a file's checksum
# Calculate and return a file's checksum.
#
# @param file [String] The file name.
#
# @return [String] The file's checksum.
def calculate_checksum(file)
@lookup_checksums[file] = Digest::MD5.hexdigest(File.read(file, mode: "rb")) rescue ""
end
+304 -51
View File
@@ -1,30 +1,39 @@
require 'set'
require 'fileutils'
require "fileutils"
require "set"
require "shellwords"
module Rscons
# The Environment class is the main programmatic interface to Rscons. It
# contains a collection of construction variables, options, builders, and
# rules for building targets.
class Environment
# Hash of +{"builder_name" => builder_object}+ pairs.
# @return [Hash] Set of \{"builder_name" => builder_object} pairs.
attr_reader :builders
# :command, :short, or :off
attr_accessor :echo
# String or +nil+
# @return [String, nil] The build root.
attr_reader :build_root
# Set the build root.
#
# @param build_root [String] The build root.
def build_root=(build_root)
@build_root = build_root
@build_root.gsub!('\\', '/') if @build_root
end
# Create an Environment object.
#
# @param options [Hash]
# Possible options keys:
# :echo => :command, :short, or :off (default :short)
# :build_root => String specifying build root directory (default nil)
# :exclude_builders => true to omit adding default builders (default false)
# @option options [Symbol] :echo
# :command, :short, or :off (default :short)
# @option options [String] :build_root
# 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
# when the block returns, the {#process} method is automatically called.
def initialize(options = {})
@@ -107,6 +116,10 @@ module Rscons
end
# Add a {Builder} object to the Environment.
#
# @param builder [Builder] The {Builder} object to add.
#
# @return [void]
def add_builder(builder)
@builders[builder.name] = builder
var_defs = builder.default_variables(self)
@@ -118,19 +131,45 @@ module Rscons
end
# 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)
@build_hooks << block
end
# Specify a build directory for this Environment.
#
# Source files from src_dir will produce object files under obj_dir.
#
# @param src_dir [String] Path to the source directory.
# @param obj_dir [String] Path to the object directory.
#
# @return [void]
def build_dir(src_dir, obj_dir)
src_dir = src_dir.gsub('\\', '/') if src_dir.is_a?(String)
@build_dirs << [src_dir, obj_dir]
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.
#
# @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)
build_fname = Rscons.set_suffix(source_fname, suffix).gsub('\\', '/')
found_match = @build_dirs.find do |src_dir, obj_dir|
@@ -150,29 +189,35 @@ module Rscons
end
# Access a construction variable or environment option.
#
# @see VarSet#[]
def [](*args)
@varset.send(:[], *args)
end
# Set a construction variable or environment option.
#
# @see VarSet#[]=
def []=(*args)
@varset.send(:[]=, *args)
end
# Add a set of construction variables or environment options.
#
# @see VarSet#append
def append(*args)
@varset.append(*args)
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
# called after the block returns.
#
# @return [void]
def process
unless @targets.empty?
clean_target_paths!
expand_paths!
cache = Cache.instance
cache.clear_checksum_cache!
targets_processed = {}
@@ -209,30 +254,35 @@ module Rscons
@targets = {}
end
# Build a command line from the given template, resolving references to
# variables using the Environment's construction variables and any extra
# variables specified.
# @param command_template [Array] template for the command with variable
# references
# @param extra_vars [Hash, VarSet] extra variables to use in addition to
# (or replace) the Environment's construction variables when building
# the command
def build_command(command_template, extra_vars)
@varset.merge(extra_vars).expand_varref(command_template)
# Expand a construction variable reference.
#
# @param varref [Array, String] Variable reference to expand.
# @param extra_vars [Hash, VarSet]
# Extra variables to use in addition to (or replace) the Environment's
# construction variables when expanding the variable reference.
#
# @return [Array, String] Expansion of the variable reference.
def expand_varref(varref, extra_vars = nil)
vars = if extra_vars.nil?
@varset
else
@varset.merge(extra_vars)
end
lambda_args = [env: self, vars: vars]
vars.expand_varref(varref, lambda_args)
end
alias_method :build_command, :expand_varref
# Expand a construction variable reference (String or Array)
def expand_varref(varref)
@varset.expand_varref(varref)
end
# Execute a builder command
# Execute a builder command.
#
# @param short_desc [String] Message to print if the Environment's echo
# mode is set to :short
# @param command [Array] The command to execute.
# @param options [Hash] Optional options, possible keys:
# - :env - environment 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 = {})
print_command = proc do
puts command.map { |c| c =~ /\s/ ? "'#{c}'" : c }.join(' ')
@@ -244,7 +294,7 @@ module Rscons
end
env_args = options[:env] ? [options[:env]] : []
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
$stdout.write "Failed command was: "
print_command.call
@@ -252,19 +302,39 @@ module Rscons
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)
if @builders.has_key?(method.to_s)
target, sources, vars, *rest = args
unless vars.nil? or vars.is_a?(Hash) or vars.is_a?(VarSet)
raise "Unexpected construction variable set: #{vars.inspect}"
end
sources = [sources] unless sources.is_a?(Array)
add_target(target, @builders[method.to_s], sources, vars, rest)
sources = Array(sources)
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
super
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)
@targets[target] = {
builder: builder,
@@ -274,27 +344,40 @@ module Rscons
}
end
# Manually record a given target as depending on the specified
# dependency files.
# Manually record a given target as depending on the specified files.
#
# @param target [String,BuildTarget] Target file.
# @param user_deps [Array<String>] Dependency files.
#
# @return [void]
def depends(target, *user_deps)
target = target.to_s
@user_deps[target] ||= []
@user_deps[target] = (@user_deps[target] + user_deps).uniq
end
# Return the list of user dependencies for a given target, or +nil+ for
# none.
# Return the list of user dependencies for a given target.
#
# @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)
@user_deps[target]
end
# Build a list of source files into files containing one of the suffixes
# given by suffixes.
#
# This method is used internally by Rscons builders.
#
# @param sources [Array] List of source files to build.
# @param suffixes [Array] List of suffixes to try to convert source files into.
# @param cache [Cache] The Cache.
# @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)
sources.map do |source|
if source.end_with?(*suffixes)
@@ -316,12 +399,14 @@ module Rscons
end
# Invoke a builder to build the given target based on the given sources.
#
# @param builder [Builder] The Builder to use.
# @param target [String] The target output file.
# @param sources [Array] List of source files.
# @param cache [Cache] The Cache.
# @param vars [Hash] Extra variables to pass to the builder.
# Return the result of the builder's run() method.
#
# @return [String,false] Return value from the {Builder}'s +run+ method.
def run_builder(builder, target, sources, cache, vars)
vars = @varset.merge(vars)
@build_hooks.each do |build_hook_block|
@@ -337,31 +422,199 @@ module Rscons
builder.run(target, sources, cache, self, vars)
end
# Expand a path to be relative to the Environment's build root.
#
# Paths beginning with "^/" are expanded by replacing "^" with the
# Environment's build root.
#
# @param path [String] The path to expand.
#
# @return [String] The expanded path.
def expand_path(path)
path.sub(%r{^\^(?=[\\/])}, @build_root)
end
# Execute a command using the system shell.
#
# The shell is automatically determined but can be overridden by the SHELL
# 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
IO.popen([*shell_cmd, command]) do |io|
io.read
end
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 all target paths that begin with ^/ to be relative to the
# Environment's build root, if present
def clean_target_paths!
if @build_root
expand = lambda do |path|
path.sub(%r{^\^(?=[\\/])}, @build_root)
# 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
new_targets = {}
@targets.each_pair do |target, target_params|
target_params[:sources].map! do |source|
expand[source]
end
new_targets[expand[target]] = target_params
end
@targets = new_targets
target = expand_path(target) if @build_root
target = expand_varref(target)
result[target] = target_params.merge(sources: sources)
result
end
end
# Parse dependencies for a given target from a Makefile.
#
# This method is used internally by Rscons builders.
#
# @param mf_fname [String] File name of the Makefile to read.
# @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)
deps = []
buildup = ''
+53 -21
View File
@@ -1,9 +1,9 @@
module Rscons
# This class represents a collection of variables which can be accessed
# as certain types.
# Only nil, strings, arrays, and hashes should be stored in a VarSet.
# This class represents a collection of variables which supports efficient
# deep cloning.
class VarSet
# Create a VarSet.
#
# @param vars [Hash] Optional initial variables.
def initialize(vars = {})
@my_vars = {}
@@ -11,8 +11,10 @@ module Rscons
append(vars)
end
# Access the value of variable as a particular type
# Access the value of variable.
#
# @param key [String, Symbol] The variable name.
#
# @return [Object] The variable's value.
def [](key)
if @my_vars.include?(key)
@@ -29,15 +31,19 @@ module Rscons
end
# Assign a value to a variable.
#
# @param key [String, Symbol] The variable name.
#
# @param val [Object] The value to set.
def []=(key, val)
@my_vars[key] = val
end
# Check if the VarSet contains a variable.
#
# @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)
if @my_vars.include?(key)
true
@@ -48,8 +54,11 @@ module Rscons
end
end
# Add or overwrite a set of variables
# Add or overwrite a set of variables.
#
# @param values [VarSet, Hash] New set of variables.
#
# @return [self]
def append(values)
coa!
if values.is_a?(VarSet)
@@ -62,7 +71,10 @@ module Rscons
end
# Create a new VarSet object based on the first merged with other.
#
# @param other [VarSet, Hash] Other variables to add or overwrite.
#
# @return [VarSet] The newly created VarSet.
def merge(other = {})
coa!
varset = self.class.new
@@ -71,34 +83,49 @@ module Rscons
end
alias_method :clone, :merge
# Replace "$" variable references in varref with the variables values,
# recursively.
# @param varref [String, Array] Value containing references to variables.
def expand_varref(varref)
if varref.is_a?(Array)
varref.map do |ent|
expand_varref(ent)
end.flatten
else
# Replace "$\{var}" variable references in varref with the expanded
# variables' values, recursively.
#
# @param varref [nil, String, Array, Proc]
# Value containing references to variables.
# @param lambda_args [Array]
# Arguments to pass to any lambda variable values to be expanded.
#
# @return [nil, String, Array]
# Expanded value with "$\{var}" variable references replaced.
def expand_varref(varref, lambda_args)
if varref.is_a?(String)
if varref =~ /^(.*)\$\{([^}]+)\}(.*)$/
prefix, varname, suffix = $1, $2, $3
varval = expand_varref(self[varname])
if varval.is_a?(String)
expand_varref("#{prefix}#{varval}#{suffix}")
varval = expand_varref(self[varname], lambda_args)
if varval.is_a?(String) or varval.nil?
expand_varref("#{prefix}#{varval}#{suffix}", lambda_args)
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
raise "I do not know how to expand a variable reference to a #{varval.class.name} (from #{varname.inspect} => #{self[varname].inspect})"
end
else
varref
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
private
# Move all VarSet variables into the copy-on-access list.
#
# @return [void]
def coa!
unless @my_vars.empty?
@coa_vars.unshift(@my_vars)
@@ -107,8 +134,13 @@ module Rscons
end
# 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)
obj_class = obj.class
if obj_class == Hash
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons
# gem version
VERSION = "1.4.0"
VERSION = "1.6.0"
end
+8 -10
View File
@@ -1,30 +1,28 @@
# -*- encoding: utf-8 -*-
lib = File.expand_path('../lib', __FILE__)
lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'rscons/version'
require "rscons/version"
Gem::Specification.new do |gem|
gem.name = "rscons"
gem.version = Rscons::VERSION
gem.authors = ["Josh Holtrop"]
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.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.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.require_paths = ["lib"]
gem.add_development_dependency "rspec-core"
gem.add_development_dependency "rspec-mocks"
gem.add_development_dependency "rspec-expectations"
gem.add_dependency "json", "~> 1.0"
gem.add_development_dependency "rspec"
gem.add_development_dependency "rake"
gem.add_development_dependency "simplecov"
gem.add_development_dependency "json"
gem.add_development_dependency 'rdoc'
gem.add_development_dependency "yard"
gem.add_development_dependency "redcarpet"
end
+202 -124
View File
@@ -12,24 +12,42 @@ end
describe Rscons do
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
FileUtils.rm_rf(BUILD_TEST_RUN_DIR)
rm_rf(BUILD_TEST_RUN_DIR)
@owd = Dir.pwd
end
before(:each) do
@output = ""
$stdout.stub(:write) do |content|
@output += content
end
$stderr.stub(:write) do |content|
@output += content
end
@saved_stdout = $stdout
$stdout = StringIO.new
@saved_stderr = $stderr
$stderr = StringIO.new
end
after(:each) do
$stdout = @saved_stdout
$stderr = @saved_stderr
Dir.chdir(@owd)
FileUtils.rm_rf(BUILD_TEST_RUN_DIR)
rm_rf(BUILD_TEST_RUN_DIR)
end
def test_dir(build_test_directory)
@@ -43,15 +61,16 @@ describe Rscons do
contents.each_line do |line|
replaced += yield(line)
end
File.open(fname, 'w') do |fh|
File.open(fname, 'wb') do |fh|
fh.write(replaced)
end
end
def lines
@output.lines.map(&:rstrip).tap do |v|
@output = ""
end
rv = ($stdout.string + $stderr.string).lines.map(&:rstrip)
$stdout.string = ""
$stderr.string = ""
rv
end
###########################################################################
@@ -63,30 +82,30 @@ describe Rscons do
Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c'])
end
File.exists?('simple.o').should be_true
`./simple`.should == "This is a simple C program\n"
expect(File.exists?('simple.o')).to be_truthy
expect(`./simple`).to eq "This is a simple C program\n"
end
it 'prints commands as they are executed' do
test_dir('simple')
Rscons::Environment.new(echo: :command) do |env|
env = Rscons::Environment.new(echo: :command) do |env|
env["LD"] = "gcc"
env.Program('simple', Dir['*.c'])
end
lines.should == [
expect(lines).to eq [
'gcc -c -o simple.o -MMD -MF simple.mf simple.c',
'gcc -o simple simple.o',
"gcc -o simple#{env["PROGSUFFIX"]} simple.o",
]
end
it 'prints short representations of the commands being executed' do
test_dir('header')
Rscons::Environment.new do |env|
env = Rscons::Environment.new do |env|
env.Program('header', Dir['*.c'])
end
lines.should == [
expect(lines).to eq [
'CC header.o',
'LD header',
"LD header#{env["PROGSUFFIX"]}",
]
end
@@ -95,8 +114,8 @@ describe Rscons do
Rscons::Environment.new do |env|
env.Program('header', Dir['*.c'])
end
File.exists?('header.o').should be_true
`./header`.should == "The value is 2\n"
expect(File.exists?('header.o')).to be_truthy
expect(`./header`).to eq "The value is 2\n"
end
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.Program('header', Dir['*.c'])
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')}
env.process
`./header`.should == "The value is 5\n"
expect(`./header`).to eq "The value is 5\n"
end
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.Program('header', Dir['*.c'])
end
`./header`.should == "The value is 2\n"
lines.should == [
expect(`./header`).to eq "The value is 2\n"
expect(lines).to eq [
'CC header.o',
'LD header',
"LD header#{env["PROGSUFFIX"]}",
]
env.process
lines.should == []
expect(lines).to eq []
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')
env = Rscons::Environment.new do |env|
env.Program('header', Dir['*.c'])
end
`./header`.should == "The value is 2\n"
lines.should == [
expect(`./header`).to eq "The value is 2\n"
expect(lines).to eq [
'CC header.o',
'LD header',
"LD header#{env["PROGSUFFIX"]}",
]
sleep 0.05
file_sub('header.c') {|line| line}
env.process
lines.should == []
expect(lines).to eq []
end
it 're-links a program when the link flags have changed' do
test_dir('simple')
Rscons::Environment.new(echo: :command) do |env|
env = Rscons::Environment.new(echo: :command) do |env|
env.Program('simple', Dir['*.c'])
end
lines.should == [
expect(lines).to eq [
'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|
env["LIBS"] += ["c"]
e2 = Rscons::Environment.new(echo: :command) do |env|
env["LIBPATH"] += ["libdir"]
env.Program('simple', Dir['*.c'])
end
lines.should == ['gcc -o simple simple.o -lc']
expect(lines).to eq ["gcc -o simple#{env["PROGSUFFIX"]} simple.o -Llibdir"]
end
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.Program('build_dir', Dir['src/**/*.c'])
end
`./build_dir`.should == "Hello from two()\n"
File.exists?('build_one/one.o').should be_true
File.exists?('build_two/two.o').should be_true
expect(`./build_dir`).to eq "Hello from two()\n"
expect(File.exists?('build_one/one.o')).to be_truthy
expect(File.exists?('build_two/two.o')).to be_truthy
end
it 'uses build directories before build root' do
test_dir('build_dir')
Rscons::Environment.new do |env|
env = Rscons::Environment.new do |env|
env.append('CPPPATH' => Dir['src/**/*/'])
env.build_dir("src", "build")
env.build_root = "build_root"
env.Program('build_dir', Dir['src/**/*.c'])
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
it 'uses build_root if no build directories match' do
@@ -184,14 +204,14 @@ describe Rscons do
env.append('CPPPATH' => Dir['src/**/*/'])
env.build_dir("src2", "build")
env.build_root = "build_root"
env.Program('build_dir', Dir['src/**/*.c'])
env.Program('build_dir.exe', Dir['src/**/*.c'])
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
it "expands target and source paths starting with ^/ to be relative to the build root" do
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.build_root = "build_root"
FileUtils.mkdir_p(env.build_root)
@@ -199,10 +219,10 @@ describe Rscons do
env.Object("^/one.o", "^/one.c")
env.Program('build_dir', Dir['src/**/*.c'] + ["^/one.o"])
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/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
@@ -213,15 +233,15 @@ describe Rscons do
env.build_dir(%r{^src/([^/]+)/}, 'build_\\1/')
env.Program('build_dir', Dir['src/**/*.c'])
end
`./build_dir`.should == "Hello from two()\n"
File.exists?('build_one/one.o').should be_true
File.exists?('build_two/two.o').should be_true
expect(`./build_dir`).to eq "Hello from two()\n"
expect(File.exists?('build_one/one.o')).to be_truthy
expect(File.exists?('build_two/two.o')).to be_truthy
Rscons.clean
File.exists?('build_one/one.o').should be_false
File.exists?('build_two/two.o').should be_false
File.exists?('build_one').should be_false
File.exists?('build_two').should be_false
File.exists?('src/one/one.c').should be_true
expect(File.exists?('build_one/one.o')).to be_falsey
expect(File.exists?('build_two/two.o')).to be_falsey
expect(File.exists?('build_one')).to be_falsey
expect(File.exists?('build_two')).to be_falsey
expect(File.exists?('src/one/one.c')).to be_truthy
end
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.Program('build_dir', Dir['src/**/*.c'])
end
`./build_dir`.should == "Hello from two()\n"
File.exists?('build_one/one.o').should be_true
File.exists?('build_two/two.o').should be_true
expect(`./build_dir`).to eq "Hello from two()\n"
expect(File.exists?('build_one/one.o')).to be_truthy
expect(File.exists?('build_two/two.o')).to be_truthy
File.open('build_two/tmp', 'w') { |fh| fh.puts "dum" }
Rscons.clean
File.exists?('build_one/one.o').should be_false
File.exists?('build_two/two.o').should be_false
File.exists?('build_one').should be_false
File.exists?('build_two').should be_true
File.exists?('src/one/one.c').should be_true
expect(File.exists?('build_one/one.o')).to be_falsey
expect(File.exists?('build_two/two.o')).to be_falsey
expect(File.exists?('build_one')).to be_falsey
expect(File.exists?('build_two')).to be_truthy
expect(File.exists?('src/one/one.c')).to be_truthy
end
it 'allows Ruby classes as custom builders to be used to construct files' do
@@ -256,15 +276,15 @@ EOF
end
end
Rscons::Environment.new do |env|
env = Rscons::Environment.new do |env|
env.add_builder(MySource.new)
env.MySource('inc.h', [])
env.Program('program', Dir['*.c'])
end
lines.should == ['CC program.o', 'LD program']
File.exists?('inc.h').should be_true
`./program`.should == "The value is 5678\n"
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 5678\n"
end
it 'supports custom builders with multiple targets' do
@@ -289,15 +309,15 @@ EOF
env.Program("program", Dir["*.c"] + ["inc.c"])
end
lines.should == ["CHGen inc.c", "CC program.o", "CC inc.o", "LD program"]
File.exists?("inc.c").should be_true
File.exists?("inc.h").should be_true
`./program`.should == "The value is 42\n"
expect(lines).to eq ["CHGen inc.c", "CC program.o", "CC inc.o", "LD program#{env["PROGSUFFIX"]}"]
expect(File.exists?("inc.c")).to be_truthy
expect(File.exists?("inc.h")).to be_truthy
expect(`./program`).to eq "The value is 42\n"
File.open("inc.c", "w") {|fh| fh.puts "int THE_VALUE = 33;"}
env.process
lines.should == ["CHGen inc.c"]
`./program`.should == "The value is 42\n"
expect(lines).to eq ["CHGen inc.c"]
expect(`./program`).to eq "The value is 42\n"
end
it 'allows cloning Environment objects' do
@@ -316,11 +336,11 @@ EOF
env.Program('program-release', Dir['src/*.c'])
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 -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 -o program-release release/program.o},
%Q{gcc -o program-release#{debug["PROGSUFFIX"]} release/program.o},
]
end
@@ -340,10 +360,10 @@ EOF
env.Program('program2', Dir['src/*.c'])
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 -o program build/program.o},
%q{gcc -o program2 build/program.o},
%Q{gcc -o program#{env1["PROGSUFFIX"]} build/program.o},
%Q{gcc -o program2#{env2["PROGSUFFIX"]} build/program.o},
]
end
@@ -352,45 +372,45 @@ EOF
Rscons::Environment.new do |env|
env.Program('simple', Dir['*.cc'])
end
File.exists?('simple.o').should be_true
`./simple`.should == "This is a simple C++ program\n"
expect(File.exists?('simple.o')).to be_truthy
expect(`./simple`).to eq "This is a simple C++ program\n"
end
it 'allows overriding construction variables for individual builder calls' do
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.Program('two_sources', ['one.o', 'two.c'])
end
lines.should == [
expect(lines).to eq [
'gcc -c -o one.o -MMD -MF one.mf -DONE one.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
`./two_sources`.should == "This is a C program with two sources.\n"
expect(File.exists?("two_sources#{env["PROGSUFFIX"]}")).to be_truthy
expect(`./two_sources`).to eq "This is a C program with two sources.\n"
end
it 'builds a static library archive' do
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.Library("lib.a", ['one.c', 'two.c'], 'CPPFLAGS' => ['-Dmake_lib'])
end
lines.should == [
expect(lines).to eq [
'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',
'ar rcs lib.a one.o two.o',
'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
`ar t lib.a`.should == "one.o\ntwo.o\n"
expect(File.exists?("library#{env["PROGSUFFIX"]}")).to be_truthy
expect(`ar t lib.a`).to eq "one.o\ntwo.o\n"
end
it 'supports build hooks to override construction variables' do
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.build_dir(%r{^src/([^/]+)/}, 'build_\\1/')
env.add_build_hook do |build_op|
@@ -400,58 +420,57 @@ EOF
build_op[:vars]["CFLAGS"] << "-O2"
end
end
env.Program('build_hook', Dir['src/**/*.c'])
env.Program('build_hook.exe', Dir['src/**/*.c'])
end
`./build_hook`.should == "Hello from two()\n"
lines.should =~ [
expect(`./build_hook.exe`).to eq "Hello from two()\n"
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_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
it 'rebuilds when user-specified dependencies change' do
test_dir('simple')
Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c'])
env = Rscons::Environment.new do |env|
env.Program('simple.exe', Dir['*.c']).depends("file.ld")
File.open("file.ld", "w") do |fh|
fh.puts("foo")
end
env.depends('simple', 'file.ld')
end
lines.should == ["CC simple.o", "LD simple"]
File.exists?('simple.o').should be_true
`./simple`.should == "This is a simple C program\n"
Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c'])
expect(lines).to eq ["CC simple.o", "LD simple.exe"]
expect(File.exists?('simple.o')).to be_truthy
expect(`./simple.exe`).to eq "This is a simple C program\n"
e2 = Rscons::Environment.new do |env|
program = env.Program('simple.exe', Dir['*.c'])
env.depends(program, "file.ld")
File.open("file.ld", "w") do |fh|
fh.puts("bar")
end
env.depends('simple', 'file.ld')
end
lines.should == ["LD simple"]
Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c'])
expect(lines).to eq ["LD simple.exe"]
e3 = Rscons::Environment.new do |env|
env.Program('simple.exe', Dir['*.c'])
File.unlink("file.ld")
end
lines.should == ["LD simple"]
expect(lines).to eq ["LD simple.exe"]
Rscons::Environment.new do |env|
env.Program('simple', Dir['*.c'])
env.Program('simple.exe', Dir['*.c'])
end
lines.should == []
expect(lines).to eq []
end
unless ENV["omit_gdc_tests"]
it "supports building D sources" do
test_dir("d")
Rscons::Environment.new(echo: :command) do |env|
env.Program("hello-d", Dir["*.d"])
env.Program("hello-d.exe", Dir["*.d"])
end
lines.should == [
expect(lines).to eq [
"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
@@ -461,8 +480,8 @@ EOF
env.Object("simple.o", "simple.c")
env.Disassemble("simple.txt", "simple.o")
end
File.exists?("simple.txt").should be_true
File.read("simple.txt").should =~ /Disassembly of section .text:/
expect(File.exists?("simple.txt")).to be_truthy
expect(File.read("simple.txt")).to match /Disassembly of section .text:/
end
it "supports preprocessing C sources" do
@@ -471,8 +490,8 @@ EOF
env.Preprocess("simplepp.c", "simple.c")
env.Program("simple", "simplepp.c")
end
File.read("simplepp.c").should =~ /# \d+ "simple.c"/
`./simple`.should == "This is a simple C program\n"
expect(File.read("simplepp.c")).to match /# \d+ "simple.c"/
expect(`./simple`).to eq "This is a simple C program\n"
end
it "supports preprocessing C++ sources" do
@@ -481,7 +500,66 @@ EOF
env.Preprocess("simplepp.cc", "simple.cc")
env.Program("simple", "simplepp.cc")
end
File.read("simplepp.cc").should =~ /# \d+ "simple.cc"/
`./simple`.should == "This is a simple C++ program\n"
expect(File.read("simplepp.cc")).to match /# \d+ "simple.cc"/
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
+7 -2
View File
@@ -5,16 +5,21 @@ module Rscons
subject {CFile.new}
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, {})
end
it "invokes a custom lexer to create a .cc file from a .ll file" do
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, {})
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
expect {subject.run("file.c", ["foo.bar"], :cache, env, {})}.to raise_error /Unknown source file .foo.bar. for CFile builder/
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
+23
View File
@@ -0,0 +1,23 @@
module Rscons
module Builders
describe Object do
let(:env) {Environment.new}
subject {Object.new}
it "supports overriding CCCMD 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) { }
allow(FileUtils).to receive(:rm_f) { }
allow(File).to receive(:exists?) { false }
expect(env).to receive(:execute).with("CC mod.o", ["llc", "mod.c"]).and_return(true)
subject.run("mod.o", ["mod.c"], cache, env, "CCCMD" => ["llc", "${_SOURCES}"])
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
describe Cache do
before do
File.stub(:read) { nil }
allow(File).to receive(:read) { nil }
end
def build_from(cache)
JSON.stub(:load) do
allow(JSON).to receive(:load) do
cache
end
Cache.instance.tap do |cache|
@@ -16,64 +16,64 @@ module Rscons
describe "#initialize" do
context "when corrupt" do
it "prints a warning and defaults to an empty hash" do
JSON.should_receive(:load).and_return("string")
$stderr.should_receive(:puts).with(/Warning:.*was.corrupt/)
expect(JSON).to receive(:load).and_return("string")
expect($stderr).to receive(:puts).with(/Warning:.*was.corrupt/)
c = Cache.instance
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
describe "#clear" do
it "removes the cache file" do
FileUtils.should_receive(:rm_f).with(Cache::CACHE_FILE)
JSON.stub(:load) {{}}
expect(FileUtils).to receive(:rm_f).with(Cache::CACHE_FILE)
allow(JSON).to receive(:load) {{}}
Cache.instance.clear
end
end
describe "#write" do
it "should fill in 'version' and write to file" do
it "fills in 'version' and write to file" do
cache = {}
fh = $stdout
fh.should_receive(:puts)
File.should_receive(:open).and_yield(fh)
expect(fh).to receive(:puts)
expect(File).to receive(:open).and_yield(fh)
build_from(cache).write
cache["version"].should == Rscons::VERSION
expect(cache["version"]).to eq Rscons::VERSION
end
end
describe "#up_to_date?" do
empty_env = "env"
before do
empty_env.stub(:get_user_deps) { nil }
allow(empty_env).to receive(:get_user_deps) { nil }
end
it "returns false when target file does not exist" do
File.should_receive(:exists?).with("target").and_return(false)
build_from({}).up_to_date?("target", "command", [], empty_env).should be_false
expect(File).to receive(:exists?).with("target").and_return(false)
expect(build_from({}).up_to_date?("target", "command", [], empty_env)).to be_falsey
end
it "returns false when target is not registered in the cache" do
File.should_receive(:exists?).with("target").and_return(true)
build_from({}).up_to_date?("target", "command", [], empty_env).should be_false
expect(File).to receive(:exists?).with("target").and_return(true)
expect(build_from({}).up_to_date?("target", "command", [], empty_env)).to be_falsey
end
it "returns false when the target's checksum does not match" do
_cache = {"targets" => {"target" => {"checksum" => "abc"}}}
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("def")
cache.up_to_date?("target", "command", [], empty_env).should be_false
expect(File).to receive(:exists?).with("target").and_return(true)
expect(cache).to receive(:calculate_checksum).with("target").and_return("def")
expect(cache.up_to_date?("target", "command", [], empty_env)).to be_falsey
end
it "returns false when the build command has changed" do
_cache = {"targets" => {"target" => {"checksum" => "abc", "command" => Digest::MD5.hexdigest("old command".inspect)}}}
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", [], empty_env).should be_false
expect(File).to receive(:exists?).with("target").and_return(true)
expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
expect(cache.up_to_date?("target", "command", [], empty_env)).to be_falsey
end
it "returns false when there is a new dependency" do
@@ -81,9 +81,9 @@ module Rscons
"command" => Digest::MD5.hexdigest("command".inspect),
"deps" => [{"fname" => "dep.1"}]}}}
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env).should be_false
expect(File).to receive(:exists?).with("target").and_return(true)
expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env)).to be_falsey
end
it "returns false when a dependency's checksum has changed" do
@@ -97,11 +97,11 @@ module Rscons
"checksum" => "extra.dep.chk"}],
"user_deps" => []}}}
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.changed")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env).should be_false
expect(File).to receive(:exists?).with("target").and_return(true)
expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
expect(cache).to receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
expect(cache).to receive(:calculate_checksum).with("dep.2").and_return("dep.2.changed")
expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env)).to be_falsey
end
it "returns false with strict_deps=true when cache has an extra dependency" do
@@ -115,9 +115,9 @@ module Rscons
"checksum" => "extra.dep.chk"}],
"user_deps" => []}}}
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env, strict_deps: true).should be_false
expect(File).to receive(:exists?).with("target").and_return(true)
expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env, strict_deps: true)).to be_falsey
end
it "returns false when there is a new user dependency" do
@@ -127,10 +127,10 @@ module Rscons
"user_deps" => []}}}
cache = build_from(_cache)
env = "env"
env.should_receive(:get_user_deps).with("target").and_return(["file.ld"])
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.up_to_date?("target", "command", ["dep.1"], env).should be_false
expect(env).to receive(:get_user_deps).with("target").and_return(["file.ld"])
expect(File).to receive(:exists?).with("target").and_return(true)
expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
expect(cache.up_to_date?("target", "command", ["dep.1"], env)).to be_falsey
end
it "returns false when a user dependency checksum has changed" do
@@ -146,14 +146,14 @@ module Rscons
"checksum" => "user.dep.chk"}]}}}
cache = build_from(_cache)
env = "env"
env.should_receive(:get_user_deps).with("target").and_return(["user.dep"])
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk")
cache.should_receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk")
cache.should_receive(:calculate_checksum).with("user.dep").and_return("INCORRECT")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], env).should be_false
expect(env).to receive(:get_user_deps).with("target").and_return(["user.dep"])
expect(File).to receive(:exists?).with("target").and_return(true)
expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
expect(cache).to receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
expect(cache).to receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk")
expect(cache).to receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk")
expect(cache).to receive(:calculate_checksum).with("user.dep").and_return("INCORRECT")
expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], env)).to be_falsey
end
it "returns true when no condition for false is met" do
@@ -167,12 +167,12 @@ module Rscons
"checksum" => "extra.dep.chk"}],
"user_deps" => []}}}
cache = build_from(_cache)
File.should_receive(:exists?).with("target").and_return(true)
cache.should_receive(:calculate_checksum).with("target").and_return("abc")
cache.should_receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
cache.should_receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk")
cache.should_receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk")
cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env).should be_true
expect(File).to receive(:exists?).with("target").and_return(true)
expect(cache).to receive(:calculate_checksum).with("target").and_return("abc")
expect(cache).to receive(:calculate_checksum).with("dep.1").and_return("dep.1.chk")
expect(cache).to receive(:calculate_checksum).with("dep.2").and_return("dep.2.chk")
expect(cache).to receive(:calculate_checksum).with("extra.dep").and_return("extra.dep.chk")
expect(cache.up_to_date?("target", "command", ["dep.1", "dep.2"], empty_env)).to be_truthy
end
end
@@ -181,21 +181,21 @@ module Rscons
_cache = {}
cache = build_from(_cache)
env = "env"
env.should_receive(:get_user_deps).with("the target").and_return(["user.dep"])
cache.should_receive(:calculate_checksum).with("the target").and_return("the checksum")
cache.should_receive(:calculate_checksum).with("dep 1").and_return("dep 1 checksum")
cache.should_receive(:calculate_checksum).with("dep 2").and_return("dep 2 checksum")
cache.should_receive(:calculate_checksum).with("user.dep").and_return("user.dep checksum")
expect(env).to receive(:get_user_deps).with("the target").and_return(["user.dep"])
expect(cache).to receive(:calculate_checksum).with("the target").and_return("the checksum")
expect(cache).to receive(:calculate_checksum).with("dep 1").and_return("dep 1 checksum")
expect(cache).to receive(:calculate_checksum).with("dep 2").and_return("dep 2 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)
cached_target = cache.instance_variable_get(:@cache)["targets"]["the target"]
cached_target.should_not be_nil
cached_target["command"].should == Digest::MD5.hexdigest("the command".inspect)
cached_target["checksum"].should == "the checksum"
cached_target["deps"].should == [
expect(cached_target).to_not be_nil
expect(cached_target["command"]).to eq Digest::MD5.hexdigest("the command".inspect)
expect(cached_target["checksum"]).to eq "the checksum"
expect(cached_target["deps"]).to eq [
{"fname" => "dep 1", "checksum" => "dep 1 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"},
]
end
@@ -204,7 +204,7 @@ module Rscons
describe "#targets" do
it "returns a list of targets that are cached" do
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
@@ -212,35 +212,35 @@ module Rscons
it "makes directories and records any created in the cache" do
_cache = {}
cache = build_from(_cache)
File.should_receive(:exists?).with("one").and_return(true)
File.should_receive(:exists?).with("one/two").and_return(false)
FileUtils.should_receive(:mkdir).with("one/two")
File.should_receive(:exists?).with("one/two/three").and_return(false)
FileUtils.should_receive(:mkdir).with("one/two/three")
File.should_receive(:exists?).with("one").and_return(true)
File.should_receive(:exists?).with("one/two").and_return(true)
File.should_receive(:exists?).with("one/two/four").and_return(false)
FileUtils.should_receive(:mkdir).with("one/two/four")
expect(File).to receive(:exists?).with("one").and_return(true)
expect(File).to receive(:exists?).with("one/two").and_return(false)
expect(FileUtils).to receive(:mkdir).with("one/two")
expect(File).to receive(:exists?).with("one/two/three").and_return(false)
expect(FileUtils).to receive(:mkdir).with("one/two/three")
expect(File).to receive(:exists?).with("one").and_return(true)
expect(File).to receive(:exists?).with("one/two").and_return(true)
expect(File).to receive(:exists?).with("one/two/four").and_return(false)
expect(FileUtils).to receive(:mkdir).with("one/two/four")
cache.mkdir_p("one/two/three")
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
it "handles absolute paths" do
_cache = {}
cache = build_from(_cache)
File.should_receive(:exists?).with("/one").and_return(true)
File.should_receive(:exists?).with("/one/two").and_return(false)
FileUtils.should_receive(:mkdir).with("/one/two")
expect(File).to receive(:exists?).with("/one").and_return(true)
expect(File).to receive(:exists?).with("/one/two").and_return(false)
expect(FileUtils).to receive(:mkdir).with("/one/two")
cache.mkdir_p("/one/two")
cache.directories.should == ["/one/two"]
expect(cache.directories).to eq ["/one/two"]
end
end
describe "#directories" do
it "returns a list of directories that are cached" do
_cache = {"directories" => {"dir1" => true, "dir2" => true}}
build_from(_cache).directories.should == ["dir1", "dir2"]
expect(build_from(_cache).directories).to eq ["dir1", "dir2"]
end
end
@@ -248,23 +248,23 @@ module Rscons
it "does not re-calculate the checksum when it is already cached" do
cache = build_from({})
cache.instance_variable_set(:@lookup_checksums, {"f1" => "f1.chk"})
cache.should_not_receive(:calculate_checksum)
cache.send(:lookup_checksum, "f1").should == "f1.chk"
expect(cache).to_not receive(:calculate_checksum)
expect(cache.send(:lookup_checksum, "f1")).to eq "f1.chk"
end
it "calls calculate_checksum when the checksum is not cached" do
cache = build_from({})
cache.should_receive(:calculate_checksum).with("f1").and_return("ck")
cache.send(:lookup_checksum, "f1").should == "ck"
expect(cache).to receive(:calculate_checksum).with("f1").and_return("ck")
expect(cache.send(:lookup_checksum, "f1")).to eq "ck"
end
end
describe "#calculate_checksum" do
it "calculates the MD5 of the file contents" do
contents = "contents"
File.should_receive(:read).with("fname", mode: "rb").and_return(contents)
Digest::MD5.should_receive(:hexdigest).with(contents).and_return("the_checksum")
build_from({}).send(:calculate_checksum, "fname").should == "the_checksum"
expect(File).to receive(:read).with("fname", mode: "rb").and_return(contents)
expect(Digest::MD5).to receive(:hexdigest).with(contents).and_return("the_checksum")
expect(build_from({}).send(:calculate_checksum, "fname")).to eq "the_checksum"
end
end
end
+182 -87
View File
@@ -3,42 +3,63 @@ module Rscons
describe "#initialize" do
it "adds the default builders when they are not excluded" do
env = Environment.new
env.builders.size.should be > 0
env.builders.map {|name, builder| builder.is_a?(Builder)}.all?.should be_true
env.builders.find {|name, builder| name == "Object"}.should_not be_nil
env.builders.find {|name, builder| name == "Program"}.should_not be_nil
env.builders.find {|name, builder| name == "Library"}.should_not be_nil
expect(env.builders.size).to be > 0
expect(env.builders.map {|name, builder| builder.is_a?(Builder)}.all?).to be_truthy
expect(env.builders.find {|name, builder| name == "Object"}).to_not be_nil
expect(env.builders.find {|name, builder| name == "Program"}).to_not be_nil
expect(env.builders.find {|name, builder| name == "Library"}).to_not be_nil
end
it "excludes the default builders with exclude_builders: :all" do
env = Environment.new(exclude_builders: true)
env.builders.size.should == 0
expect(env.builders.size).to eq 0
end
context "when a block is given" do
it "yields self and invokes #process()" do
env = Environment.new do |env|
env.should_receive(:process)
expect(env).to receive(:process)
end
end
end
end
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["CPPPATH"] << "path1"
env2 = env.clone
env2["CPPPATH"] << "path2"
env["CPPPATH"].should == ["path1"]
env2["CPPPATH"].should == ["path1", "path2"]
expect(env["CPPPATH"]).to eq ["path1"]
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
context "when a block is given" do
it "yields self and invokes #process()" do
env = Environment.new
env.clone do |env2|
env2.should_receive(:process)
expect(env2).to receive(:process)
end
end
end
@@ -47,9 +68,9 @@ module Rscons
describe "#add_builder" do
it "adds the builder to the list of builders" do
env = Environment.new(exclude_builders: true)
env.builders.keys.should == []
expect(env.builders.keys).to eq []
env.add_builder(Rscons::Builders::Object.new)
env.builders.keys.should == ["Object"]
expect(env.builders.keys).to eq ["Object"]
end
end
@@ -57,20 +78,20 @@ module Rscons
context "with no build directories" do
it "returns the name of the source file with suffix changed" do
env = Environment.new
env.get_build_fname("src/dir/file.c", ".o").should == "src/dir/file.o"
env.get_build_fname("src\\dir\\other.d", ".a").should == "src/dir/other.a"
env.get_build_fname("source.cc", ".o").should == "source.o"
expect(env.get_build_fname("src/dir/file.c", ".o")).to eq "src/dir/file.o"
expect(env.get_build_fname("src\\dir\\other.d", ".a")).to eq "src/dir/other.a"
expect(env.get_build_fname("source.cc", ".o")).to eq "source.o"
end
context "with a build_root" do
it "uses the build_root unless the path is absolute" do
env = Environment.new
env.build_root = "build/proj"
env.get_build_fname("src/dir/file.c", ".o").should == "build/proj/src/dir/file.o"
env.get_build_fname("/some/lib.c", ".a").should == "/some/lib.a"
env.get_build_fname("C:\\abspath\\mod.cc", ".o").should == "C:/abspath/mod.o"
env.get_build_fname("build\\proj\\generated.c", ".o").should == "build/proj/generated.o"
env.get_build_fname("build/proj.XX", ".yy").should == "build/proj/build/proj.yy"
expect(env.get_build_fname("src/dir/file.c", ".o")).to eq "build/proj/src/dir/file.o"
expect(env.get_build_fname("/some/lib.c", ".a")).to eq "/some/lib.a"
expect(env.get_build_fname("C:\\abspath\\mod.cc", ".o")).to eq "C:/abspath/mod.o"
expect(env.get_build_fname("build\\proj\\generated.c", ".o")).to eq "build/proj/generated.o"
expect(env.get_build_fname("build/proj.XX", ".yy")).to eq "build/proj/build/proj.yy"
end
end
end
@@ -80,10 +101,10 @@ module Rscons
env = Environment.new
env.build_dir("src", "bld")
env.build_dir(%r{^libs/([^/]+)}, 'build/libs/\1')
env.get_build_fname("src/input.cc", ".o").should == "bld/input.o"
env.get_build_fname("libs/lib1/some/file.c", ".o").should == "build/libs/lib1/some/file.o"
env.get_build_fname("libs/otherlib/otherlib.cc", ".o").should == "build/libs/otherlib/otherlib.o"
env.get_build_fname("other_directory/o.d", ".a").should == "other_directory/o.a"
expect(env.get_build_fname("src/input.cc", ".o")).to eq "bld/input.o"
expect(env.get_build_fname("libs/lib1/some/file.c", ".o")).to eq "build/libs/lib1/some/file.o"
expect(env.get_build_fname("libs/otherlib/otherlib.cc", ".o")).to eq "build/libs/otherlib/otherlib.o"
expect(env.get_build_fname("other_directory/o.d", ".a")).to eq "other_directory/o.a"
end
context "with a build_root" do
@@ -93,11 +114,11 @@ module Rscons
env.build_dir(%r{^libs/([^/]+)}, 'build/libs/\1')
env.build_root = "bldit"
env.get_build_fname("src/input.cc", ".o").should == "bld/input.o"
env.get_build_fname("libs/lib1/some/file.c", ".o").should == "build/libs/lib1/some/file.o"
env.get_build_fname("libs/otherlib/otherlib.cc", ".o").should == "build/libs/otherlib/otherlib.o"
env.get_build_fname("other_directory/o.d", ".a").should == "bldit/other_directory/o.a"
env.get_build_fname("bldit/some/mod.d", ".a").should == "bldit/some/mod.a"
expect(env.get_build_fname("src/input.cc", ".o")).to eq "bld/input.o"
expect(env.get_build_fname("libs/lib1/some/file.c", ".o")).to eq "build/libs/lib1/some/file.o"
expect(env.get_build_fname("libs/otherlib/otherlib.cc", ".o")).to eq "build/libs/otherlib/otherlib.o"
expect(env.get_build_fname("other_directory/o.d", ".a")).to eq "bldit/other_directory/o.a"
expect(env.get_build_fname("bldit/some/mod.d", ".a")).to eq "bldit/some/mod.a"
end
end
end
@@ -107,7 +128,7 @@ module Rscons
it "allows reading construction variables" do
env = Environment.new
env["CFLAGS"] = ["-g", "-Wall"]
env["CFLAGS"].should == ["-g", "-Wall"]
expect(env["CFLAGS"]).to eq ["-g", "-Wall"]
end
end
@@ -117,9 +138,9 @@ module Rscons
env["CFLAGS"] = ["-g", "-Wall"]
env["CFLAGS"] -= ["-g"]
env["CFLAGS"] += ["-O3"]
env["CFLAGS"].should == ["-Wall", "-O3"]
expect(env["CFLAGS"]).to eq ["-Wall", "-O3"]
env["other_var"] = "val33"
env["other_var"].should == "val33"
expect(env["other_var"]).to eq "val33"
end
end
@@ -129,8 +150,8 @@ module Rscons
env["CFLAGS"] = ["-g"]
env["CPPPATH"] = ["inc"]
env.append("CFLAGS" => ["-Wall"], "CPPPATH" => ["include"])
env["CFLAGS"].should == ["-Wall"]
env["CPPPATH"].should == ["include"]
expect(env["CFLAGS"]).to eq ["-Wall"]
expect(env["CPPPATH"]).to eq ["include"]
end
end
@@ -140,10 +161,10 @@ module Rscons
env.Program("a.out", "main.c")
cache = "cache"
Cache.should_receive(:instance).and_return(cache)
cache.should_receive(:clear_checksum_cache!)
env.should_receive(:run_builder).with(anything, "a.out", ["main.c"], cache, {}).and_return(true)
cache.should_receive(:write)
expect(Cache).to receive(:instance).and_return(cache)
expect(cache).to receive(:clear_checksum_cache!)
expect(env).to receive(:run_builder).with(anything, "a.out", ["main.c"], cache, {}).and_return(true)
expect(cache).to receive(:write)
env.process
end
@@ -154,11 +175,11 @@ module Rscons
env.Object("main.o", "other.cc")
cache = "cache"
Cache.should_receive(:instance).and_return(cache)
cache.should_receive(:clear_checksum_cache!)
env.should_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")
cache.should_receive(:write)
expect(Cache).to receive(:instance).and_return(cache)
expect(cache).to receive(:clear_checksum_cache!)
expect(env).to receive(:run_builder).with(anything, "main.o", ["other.cc"], cache, {}).and_return("main.o")
expect(env).to receive(:run_builder).with(anything, "a.out", ["main.o"], cache, {}).and_return("a.out")
expect(cache).to receive(:write)
env.process
end
@@ -169,10 +190,10 @@ module Rscons
env.Object("main.o", "other.cc")
cache = "cache"
Cache.should_receive(:instance).and_return(cache)
cache.should_receive(:clear_checksum_cache!)
env.should_receive(:run_builder).with(anything, "main.o", ["other.cc"], cache, {}).and_return(false)
cache.should_receive(:write)
expect(Cache).to receive(:instance).and_return(cache)
expect(cache).to receive(:clear_checksum_cache!)
expect(env).to receive(:run_builder).with(anything, "main.o", ["other.cc"], cache, {}).and_return(false)
expect(cache).to receive(:write)
expect { env.process }.to raise_error BuildError, /Failed.to.build.main.o/
end
@@ -182,12 +203,12 @@ module Rscons
env.Object("module.o", "module.c")
cache = "cache"
Cache.should_receive(:instance).and_return(cache)
cache.should_receive(:clear_checksum_cache!)
env.stub(:run_builder) do |builder, target, sources, cache, vars|
expect(Cache).to receive(:instance).and_return(cache)
expect(cache).to receive(:clear_checksum_cache!)
allow(env).to receive(:run_builder) do |builder, target, sources, cache, vars|
raise "Ruby exception thrown by builder"
end
cache.should_receive(:write)
expect(cache).to receive(:write)
expect { env.process }.to raise_error RuntimeError, /Ruby exception thrown by builder/
end
@@ -213,7 +234,7 @@ module Rscons
env["specialflag"] = "-z"
template = ["cmd", "-I${path}", "${flags}", "${_source}", "${_dest}"]
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
@@ -223,11 +244,12 @@ module Rscons
env["path"] = ["dir1", "dir2"]
env["flags"] = ["-x", "-y", "${specialflag}"]
env["specialflag"] = "-z"
env.expand_varref(["-p${path}", "${flags}"]).should == ["-pdir1", "-pdir2", "-x", "-y", "-z"]
env.expand_varref("foo").should == "foo"
expect {env.expand_varref("${foo}")}.to raise_error /expand.a.variable.reference/
env.expand_varref("${specialflag}").should == "-z"
env.expand_varref("${path}").should == ["dir1", "dir2"]
env["foo"] = {}
expect(env.expand_varref(["-p${path}", "${flags}"])).to eq ["-pdir1", "-pdir2", "-x", "-y", "-z"]
expect(env.expand_varref("foo")).to eq "foo"
expect {env.expand_varref("${foo}")}.to raise_error /Unknown.varref.type/
expect(env.expand_varref("${specialflag}")).to eq "-z"
expect(env.expand_varref("${path}")).to eq ["dir1", "dir2"]
end
end
@@ -236,8 +258,8 @@ module Rscons
context "with no errors" do
it "prints the short description and executes the command" do
env = Environment.new(echo: :short)
env.should_receive(:puts).with("short desc")
env.should_receive(:system).with("a", "command").and_return(true)
expect(env).to receive(:puts).with("short desc")
expect(env).to receive(:system).with(*Rscons.command_executer, "a", "command").and_return(true)
env.execute("short desc", ["a", "command"])
end
end
@@ -245,10 +267,10 @@ module Rscons
context "with errors" do
it "prints the short description, executes the command, and prints the failed command line" do
env = Environment.new(echo: :short)
env.should_receive(:puts).with("short desc")
env.should_receive(:system).with("a", "command").and_return(false)
$stdout.should_receive(:write).with("Failed command was: ")
env.should_receive(:puts).with("a command")
expect(env).to receive(:puts).with("short desc")
expect(env).to receive(:system).with(*Rscons.command_executer, "a", "command").and_return(false)
expect($stdout).to receive(:write).with("Failed command was: ")
expect(env).to receive(:puts).with("a command")
env.execute("short desc", ["a", "command"])
end
end
@@ -257,8 +279,8 @@ module Rscons
context "with echo: :command" do
it "prints the command executed and executes the command" do
env = Environment.new(echo: :command)
env.should_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(:puts).with("a command '--arg=val with spaces'")
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})
end
end
@@ -272,14 +294,14 @@ module Rscons
it "records the target when the target method is a known builder" do
env = Environment.new
env.instance_variable_get(:@targets).should == {}
env.Program("target", ["src1", "src2"], var: "val")
target = env.instance_variable_get(:@targets)["target"]
target.should_not be_nil
target[:builder].is_a?(Builder).should be_true
target[:sources].should == ["src1", "src2"]
target[:vars].should == {var: "val"}
target[:args].should == []
expect(env.instance_variable_get(:@targets)).to eq({})
env.Object("target.o", ["src1.c", "src2.c"], var: "val")
target = env.instance_variable_get(:@targets)["target.o"]
expect(target).to_not be_nil
expect(target[:builder].is_a?(Builder)).to be_truthy
expect(target[:sources]).to eq ["src1.c", "src2.c"]
expect(target[:vars]).to eq({var: "val"})
expect(target[:args]).to eq []
end
it "raises an error when vars is not a Hash" do
@@ -292,13 +314,13 @@ module Rscons
it "records the given dependencies in @user_deps" do
env = Environment.new
env.depends("foo", "bar", "baz")
env.instance_variable_get(:@user_deps).should == {"foo" => ["bar", "baz"]}
expect(env.instance_variable_get(:@user_deps)).to eq({"foo" => ["bar", "baz"]})
end
it "records user dependencies only once" do
env = Environment.new
env.instance_variable_set(:@user_deps, {"foo" => ["bar"]})
env.depends("foo", "bar", "baz")
env.instance_variable_get(:@user_deps).should == {"foo" => ["bar", "baz"]}
expect(env.instance_variable_get(:@user_deps)).to eq({"foo" => ["bar", "baz"]})
end
end
@@ -313,9 +335,9 @@ module Rscons
cache = "cache"
env = Environment.new
env.add_builder(ABuilder.new)
env.builders["Object"].should_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")
env.build_sources(["precompiled.o", "mod.c", "mod2.ab_in"], [".o", ".ab_out"], cache, {}).should == ["precompiled.o", "mod.o", "mod2.ab_out"]
expect(env.builders["Object"]).to receive(:run).with("mod.o", ["mod.c"], cache, env, anything).and_return("mod.o")
expect(env.builders["ABuilder"]).to receive(:run).with("mod2.ab_out", ["mod2.ab_in"], cache, env, anything).and_return("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
@@ -327,32 +349,105 @@ module Rscons
build_op[:vars]["CFLAGS"] += ["-O3", "-DSPECIAL"]
end
end
env.builders["Object"].stub(:run) do |target, sources, cache, env, vars|
vars["CFLAGS"].should == []
allow(env.builders["Object"]).to receive(:run) do |target, sources, cache, env, vars|
expect(vars["CFLAGS"]).to eq []
end
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|
vars["CFLAGS"].should == ["-O3", "-DSPECIAL"]
allow(env.builders["Object"]).to receive(:run) do |target, sources, cache, env, vars|
expect(vars["CFLAGS"]).to eq ["-O3", "-DSPECIAL"]
end
env.run_builder(env.builders["Object"], "build/special/module.o", ["src/special/module.c"], "cache", {})
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
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
EOS
Environment.parse_makefile_deps('makefile', 'module.o').should == ['source.cc']
expect(Environment.parse_makefile_deps('makefile', 'module.o')).to eq ['source.cc']
end
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.h \\
other.h
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']
end
end
+49 -36
View File
@@ -3,20 +3,20 @@ module Rscons
describe '#initialize' do
it "initializes variables from a Hash" do
v = VarSet.new({"one" => 1, "two" => :two})
v["one"].should == 1
v["two"].should == :two
expect(v["one"]).to eq(1)
expect(v["two"]).to eq(:two)
end
it "initializes variables from another VarSet" do
v = VarSet.new({"one" => 1})
v2 = VarSet.new(v)
v2["one"].should == 1
expect(v2["one"]).to eq 1
end
it "makes a deep copy of the given VarSet" do
v = VarSet.new({"array" => [1, 2, 3]})
v2 = VarSet.new(v)
v["array"] << 4
v["array"].should == [1, 2, 3, 4]
v2["array"].should == [1, 2, 3]
expect(v["array"]).to eq([1, 2, 3, 4])
expect(v2["array"]).to eq([1, 2, 3])
end
end
@@ -24,11 +24,11 @@ module Rscons
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,
"qax" => [3, 6], "qux" => {a: :b}})
v["fuz"].should == "a string"
v["foo"].should == 42
v["bar"].should == :baz
v["qax"].should == [3, 6]
v["qux"].should == {a: :b}
expect(v["fuz"]).to eq("a string")
expect(v["foo"]).to eq(42)
expect(v["bar"]).to eq(:baz)
expect(v["qax"]).to eq([3, 6])
expect(v["qux"]).to eq({a: :b})
end
end
@@ -36,8 +36,8 @@ module Rscons
it "allows assigning to variables" do
v = VarSet.new("CFLAGS" => ["-Wall", "-O3"])
v["CPPPATH"] = ["one", "two"]
v["CFLAGS"].should == ["-Wall", "-O3"]
v["CPPPATH"].should == ["one", "two"]
expect(v["CFLAGS"]).to eq(["-Wall", "-O3"])
expect(v["CPPPATH"]).to eq(["one", "two"])
end
end
@@ -45,20 +45,20 @@ module Rscons
it "returns whether the variable is in the VarSet" do
v = VarSet.new("CFLAGS" => [], :foo => :bar)
expect(v.include?("CFLAGS")).to be_true
expect(v.include?(:CFLAGS)).to be_false
expect(v.include?(:foo)).to be_true
expect(v.include?("foo")).to be_false
expect(v.include?("bar")).to be_false
expect(v.include?("CFLAGS")).to be_truthy
expect(v.include?(:CFLAGS)).to be_falsey
expect(v.include?(:foo)).to be_truthy
expect(v.include?("foo")).to be_falsey
expect(v.include?("bar")).to be_falsey
v2 = v.clone
v2.append("bar" => [])
expect(v2.include?("CFLAGS")).to be_true
expect(v2.include?(:CFLAGS)).to be_false
expect(v2.include?(:foo)).to be_true
expect(v2.include?("foo")).to be_false
expect(v2.include?("bar")).to be_true
expect(v2.include?("CFLAGS")).to be_truthy
expect(v2.include?(:CFLAGS)).to be_falsey
expect(v2.include?(:foo)).to be_truthy
expect(v2.include?("foo")).to be_falsey
expect(v2.include?("bar")).to be_truthy
end
end
@@ -125,34 +125,47 @@ module Rscons
"CC" => "gcc",
"CPPPATH" => ["dir1", "dir2"],
"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
v.expand_varref("CC").should == "CC"
v.expand_varref("CPPPATH").should == "CPPPATH"
v.expand_varref("str").should == "str"
expect(v.expand_varref("CC", :lambda_args)).to eq("CC")
expect(v.expand_varref("CPPPATH", :lambda_args)).to eq("CPPPATH")
expect(v.expand_varref("str", :lambda_args)).to eq("str")
end
it "expands a single variable reference beginning with a '$'" do
v.expand_varref("${CC}").should == "gcc"
v.expand_varref("${CPPPATH}").should == ["dir1", "dir2"]
expect(v.expand_varref("${CC}", :lambda_args)).to eq("gcc")
expect(v.expand_varref("${CPPPATH}", :lambda_args)).to eq(["dir1", "dir2"])
end
it "expands a single variable reference in ${arr} notation" do
v.expand_varref("prefix${CFLAGS}suffix").should == ["prefix-Wallsuffix", "prefix-O2suffix"]
v.expand_varref(v["cmd"]).should == ["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"]
expect(v.expand_varref("prefix${CFLAGS}suffix", :lambda_args)).to eq(["prefix-Wallsuffix", "prefix-O2suffix"])
expect(v.expand_varref(v["cmd"], :lambda_args)).to eq(["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"])
end
it "expands a variable reference recursively" do
v.expand_varref("${compiler}").should == "gcc"
v.expand_varref("${cmd}").should == ["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"]
expect(v.expand_varref("${compiler}", :lambda_args)).to eq("gcc")
expect(v.expand_varref("${cmd}", :lambda_args)).to eq(["gcc", "-c", "-Wall", "-O2", "-Idir1", "-Idir2"])
end
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: -O2, cpppath: dir1, compiler: gcc",
"cflag: -Wall, cpppath: dir2, compiler: gcc",
"cflag: -O2, cpppath: dir2, compiler: gcc",
]
])
end
it "raises an error when a variable reference refers to a non-existent variable" do
expect { v.expand_varref("${not_here}") }.to raise_error /I do not know how to expand a variable reference to a NilClass/
it "returns an empty string when a variable reference refers to a non-existent variable" do
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
+106 -17
View File
@@ -2,25 +2,114 @@ describe Rscons do
describe ".clean" do
it "removes all build targets and created directories" do
cache = "cache"
Rscons::Cache.should_receive(:instance).and_return(cache)
cache.should_receive(:targets).and_return(["build/a.out", "build/main.o"])
FileUtils.should_receive(:rm_f).with("build/a.out")
FileUtils.should_receive(:rm_f).with("build/main.o")
cache.should_receive(:directories).and_return(["build/one", "build/one/two", "build", "other"])
File.should_receive(:directory?).with("build/one/two").and_return(true)
Dir.should_receive(:entries).with("build/one/two").and_return([".", ".."])
Dir.should_receive(:rmdir).with("build/one/two")
File.should_receive(:directory?).with("build/one").and_return(true)
Dir.should_receive(:entries).with("build/one").and_return([".", ".."])
Dir.should_receive(:rmdir).with("build/one")
File.should_receive(:directory?).with("build").and_return(true)
Dir.should_receive(:entries).with("build").and_return([".", ".."])
Dir.should_receive(:rmdir).with("build")
File.should_receive(:directory?).with("other").and_return(true)
Dir.should_receive(:entries).with("other").and_return([".", "..", "other.file"])
cache.should_receive(:clear)
expect(Rscons::Cache).to receive(:instance).and_return(cache)
expect(cache).to receive(:targets).and_return(["build/a.out", "build/main.o"])
expect(FileUtils).to receive(:rm_f).with("build/a.out")
expect(FileUtils).to receive(:rm_f).with("build/main.o")
expect(cache).to receive(:directories).and_return(["build/one", "build/one/two", "build", "other"])
expect(File).to receive(:directory?).with("build/one/two").and_return(true)
expect(Dir).to receive(:entries).with("build/one/two").and_return([".", ".."])
expect(Dir).to receive(:rmdir).with("build/one/two")
expect(File).to receive(:directory?).with("build/one").and_return(true)
expect(Dir).to receive(:entries).with("build/one").and_return([".", ".."])
expect(Dir).to receive(:rmdir).with("build/one")
expect(File).to receive(:directory?).with("build").and_return(true)
expect(Dir).to receive(:entries).with("build").and_return([".", ".."])
expect(Dir).to receive(:rmdir).with("build")
expect(File).to receive(:directory?).with("other").and_return(true)
expect(Dir).to receive(:entries).with("other").and_return([".", "..", "other.file"])
expect(cache).to receive(:clear)
Rscons.clean
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