Compare commits

..
31 Commits
Author SHA1 Message Date
josh dcf90cc12b v1.8.0 2014-10-22 10:36:28 -04:00
josh f93ca62c07 add v1.8.0 release notes 2014-10-22 10:31:52 -04:00
josh d9e3129ad5 add Environment#dump 2014-10-22 10:30:57 -04:00
josh d5e0475e6c minor documentation improvements 2014-10-21 17:00:38 -04:00
josh b05eed36eb add VarSet#to_h 2014-10-21 16:06:15 -04:00
josh 7ea0721cc2 add a Builder#run method which raises an error unless overridden 2014-10-16 16:52:26 -04:00
josh e654df008d add rdoc to bundle 2014-10-16 16:42:03 -04:00
Michael D. Lowis 92d4dd8396 Implemented SimpleBuilder and updated Environment#add_builder method to support it 2014-10-16 16:37:37 -04:00
Michael D. Lowis e279959d79 Added a command builder to the list of default builders 2014-10-13 12:27:05 -04:00
josh 469f575564 v1.7.0 2014-09-25 11:35:00 -04:00
josh 07725581e9 add v1.7.0 release notes 2014-09-25 11:34:37 -04:00
josh ebb69cedc2 add @since tag to Environment#add_post_build_hook 2014-09-25 11:31:18 -04:00
josh c98b377ce8 use "rake/clean" module now that it isn' so noisy; clean .yardoc folder 2014-09-25 11:14:39 -04:00
josh a0874e7cf1 add tests for *SUFFIX variables containing Arrays - close #9 2014-09-25 11:12:11 -04:00
josh 0b04229e3d fix Environment#build_dir documentation 2014-09-25 10:47:21 -04:00
josh 20f3da3b04 allow trailing slashes to be passed to env.build_dir() 2014-09-25 10:45:52 -04:00
josh a6a79b8ef1 test cloning post-build hooks 2014-09-23 12:44:02 -04:00
josh 6d4f27602e update README.md notes about build hooks 2014-09-19 12:41:54 -04:00
josh 453295a49b add post-build hooks - close #14 2014-09-19 12:36:57 -04:00
josh dee2b98f6e allow build hooks to register new build targets - close #15 2014-09-19 11:10:59 -04:00
josh b186b3102c clear build targets after processing an Environment - close #17 2014-09-19 10:45:49 -04:00
josh 23c94f7841 fix YARD curly brace escapes 2014-09-11 13:53:10 -04:00
josh 6acb209ef4 v1.6.1 2014-08-19 09:12:35 -04:00
josh 43e12819d7 update .gitignore, version Gemfile.lock 2014-08-19 09:12:21 -04:00
josh 1bfb7dd100 add v1.6.1 release notes 2014-08-19 09:09:37 -04:00
josh df1e3be829 fix #12 - allow overriding dependency file suffix with DEPFILESUFFIX construction variable 2014-08-18 15:53:02 -04:00
josh 851adae66f fix #13 - Environment#depends should expand its arguments for construction variables 2014-08-18 15:39:33 -04:00
josh eb28e09d8c add v1.6.0 release notes 2014-07-10 14:34:35 -04:00
josh 01a84e8819 v1.6.0 2014-06-18 10:45:05 -04:00
josh 28bedfce11 Support lambdas as construction variable values 2014-06-18 10:43:35 -04:00
josh e792fd473b add v1.5.0 release notes 2014-06-18 09:15:49 -04:00
22 changed files with 781 additions and 133 deletions

No files matched your search

+6 -18
View File
@@ -1,18 +1,6 @@
*.gem
*.rbc
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
build_test_run
/.yardoc/
/_yardoc/
/coverage/
/doc/
/pkg/
/build_test_run/
+48
View File
@@ -0,0 +1,48 @@
PATH
remote: .
specs:
rscons (1.8.0)
json (~> 1.0)
GEM
remote: https://rubygems.org/
specs:
diff-lcs (1.2.5)
docile (1.1.5)
json (1.8.1)
multi_json (1.10.1)
rake (10.3.2)
rdoc (4.1.2)
json (~> 1.4)
redcarpet (3.1.2)
rspec (3.0.0)
rspec-core (~> 3.0.0)
rspec-expectations (~> 3.0.0)
rspec-mocks (~> 3.0.0)
rspec-core (3.0.1)
rspec-support (~> 3.0.0)
rspec-expectations (3.0.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.0.0)
rspec-mocks (3.0.1)
rspec-support (~> 3.0.0)
rspec-support (3.0.0)
simplecov (0.8.2)
docile (~> 1.1.0)
multi_json
simplecov-html (~> 0.8.0)
simplecov-html (0.8.0)
yard (0.8.7.4)
PLATFORMS
ruby
x86-mingw32
DEPENDENCIES
rake
rdoc
redcarpet
rscons!
rspec
simplecov
yard
+91
View File
@@ -154,6 +154,31 @@ Rscons::Environment.new do |env|
end
```
### Example: Custom Builder Using Environment#add_builder()
The `add_builder` method of the `Rscons::Environment` class optionally allows
you to define and register a builder by providing a name and action block. This
can be useful if the builder you are trying to define is easily expressed as a
short ruby procedure. When `add_builder` is called in this manner a new builder
will be registered with the environment with the given name. When this builder
is used it will call the provided block in order to build the target.
```ruby
Rscons::Environment.new do |env|
env.add_builder(:JsonToYaml) do |target, sources, cache, env, vars|
unless cache.up_to_date?(target, :JsonToYaml, sources, env)
cache.mkdir_p(File.dirname(target))
File.open(target, 'w') do |f|
f.write(YAML.dump(JSON.load(IO.read(sources.first))))
end
cache.register_build(target, :JsonToYaml, sources, env)
end
target
end
env.JsonToYaml('foo.yml','foo.json')
end
```
### Example: Using different compilation flags for some sources
```ruby
@@ -173,6 +198,9 @@ Each build hook block will be invoked for every build operation, so the block
should test the target or sources if its action should only apply to some
subset of build targets or source files.
Build hooks can alter construction variable values for a particular build
operation. Build hooks can also register new build targets.
The `build_op` parameter to the build hook block is a Hash describing the
build operation with the following keys:
* `:builder` - `Builder` instance in use
@@ -207,6 +235,7 @@ end
Rscons ships with a number of builders:
* Command, which executes a user-defined command to produce the target
* CFile, which builds a C or C++ source file from a lex or yacc input file
* Disassemble, which disassembles an object file to a disassembly listing
* Library, which collects object files into a static library archive file
@@ -217,6 +246,18 @@ Rscons ships with a number of builders:
If you want to create an Environment that does not contain any builders,
you can use the `exclude_builders` key to the Environment constructor.
#### Command
```ruby
env.Command(target, sources, 'CMD' => command)
# Example
env.Command("docs.html", "docs.md",
CMD => ['pandoc', '-fmarkdown', '-thtml', '-o${_TARGET}', '${_SOURCES}'])
```
The command builder executes a user-defined command in order to produce the
desired target file based on the provided source files.
#### CFile
```ruby
@@ -317,6 +358,19 @@ cloned_env["CPPPATH"] << "three"
`base_env["CPPPATH"]` will not include "three".
#### Build Hooks
Environments can have build hooks which are added with `env.add_build_hook()`.
Build hooks are invoked immediately before a builder executes.
Build hooks can modify the construction variables in use for the build
operation.
They can also register new build targets.
Environments can also have post-build hooks added with `env.add_post_build_hook()`.
Post-build hooks are only invoked if the build operation was a success.
Post-build hooks can invoke commands using the newly-built files, or register
new build targets.
### Construction Variable Naming
* uppercase strings - the default construction variables that Rscons uses
@@ -329,6 +383,43 @@ http://rubydoc.info/github/holtrop/rscons/frames.
## Release Notes
### v1.8.0
- new Command builder to execute arbitrary user commands
- new SimpleBuilder class
- create new builders quickly by passing a block to Environment#add_builder
- improved YARD documentation
- add Environment#dump to debug Environment construction variables
### v1.7.0
- allow build hooks to register new build targets
- add post-build hooks (register with Environment#add_post_build_hook)
- clear all build targets after processing an Environment
- allow trailing slashes in arguments to Environment#build_dir
### v1.6.1
- add DEPFILESUFFIX construction variable to override dependency file suffix
- fix Environment#depends to expand its arguments for construction variables
### v1.6.0
- support lambdas as construction variable values
### v1.5.0
- add "json" as a runtime dependency
- update construction variables to match SCons more closely
- add CPPDEFPREFIX, INCPREFIX, CPPDEFINES, CCFLAGS, LIBDIRPREFIX, and LIBLINKPREFIX
- add Environment#shell
- add Environment#parse_flags, #parse_flags!, #merge_flags
- unbuffer $stdout by default
- add PROGSUFFIX construction variable (defaults to .exe on MinGW/Cygwin)
- add Rscons::BuildTarget and Builder#create_build_target
- update specs to RSpec 3.x and fix to run on MinGW/Cygwin/Linux
- add YARD documentation to get to 100% coverage
### v1.4.3
- fix builders properly using construction variable overrides
+4 -8
View File
@@ -8,6 +8,10 @@ end
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "yard"
require "rake/clean"
CLEAN.include %w[build_test_run .yardoc doc coverage]
CLOBBER.include %w[pkg]
RSpec::Core::RakeTask.new(:spec)
@@ -16,11 +20,3 @@ YARD::Rake::YardocTask.new do |yard|
end
task :default => :spec
task :clean do
FileUtils.rm_rf(["build_test_run", "doc", "coverage"])
end
task :clobber => :clean do
FileUtils.rm_rf(["pkg"])
end
+1
View File
@@ -0,0 +1 @@
{ "key": "value" }
+7
View File
@@ -6,18 +6,21 @@ require_relative "rscons/varset"
require_relative "rscons/version"
# default builders
require_relative "rscons/builders/command"
require_relative "rscons/builders/cfile"
require_relative "rscons/builders/disassemble"
require_relative "rscons/builders/library"
require_relative "rscons/builders/object"
require_relative "rscons/builders/preprocess"
require_relative "rscons/builders/program"
require_relative "rscons/builders/simple_builder"
# Namespace module for rscons classes
module Rscons
# Names of the default builders which will be added to all newly created
# {Environment} objects.
DEFAULT_BUILDERS = [
:Command,
:CFile,
:Disassemble,
:Library,
@@ -30,6 +33,8 @@ module Rscons
class BuildError < RuntimeError; end
# Remove all generated files.
#
# @return [void]
def self.clean
cache = Cache.instance
# remove all built files
@@ -118,6 +123,8 @@ module Rscons
# Set the command executer array.
#
# @param val [Array<String>] Command used to execute commands.
#
# @return [Array<String>] Command used to execute commands.
def self.command_executer=(val)
@@command_executer = val
end
+17 -1
View File
@@ -9,6 +9,8 @@ module Rscons
# Return the name of the builder.
#
# If not overridden this defaults to the last component of the class name.
#
# @return [String] The name of the builder.
def name
self.class.name.split(":").last
end
@@ -44,7 +46,7 @@ module Rscons
# 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 source [String] The source file name.
# @param env [Environment] The Environment.
#
# @return [Boolean]
@@ -54,6 +56,20 @@ module Rscons
false
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)
raise "This method must be overridden in a subclass"
end
# Check if the cache is up to date for the target and if not execute the
# build command.
#
+32
View File
@@ -0,0 +1,32 @@
module Rscons
module Builders
# Execute a command that will produce the given target based on the given
# sources.
#
# @since 1.8.0
#
# Example:
# env.Command("docs.html", "docs.md",
# CMD => %w[pandoc -fmarkdown -thtml -o${_TARGET} ${_SOURCES}])
class Command < Builder
# 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,
"_SOURCES" => sources,
})
command = env.build_command("${CMD}", vars)
standard_build("CMD #{target}", target, command, sources, env, cache)
end
end
end
end
+3 -2
View File
@@ -19,6 +19,7 @@ module Rscons
def default_variables(env)
{
'OBJSUFFIX' => '.o',
'DEPFILESUFFIX' => '.mf',
'CPPDEFPREFIX' => '-D',
'INCPREFIX' => '-I',
@@ -61,7 +62,7 @@ module Rscons
# 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 source [String] The source file name.
# @param env [Environment] The Environment.
#
# @return [Boolean]
@@ -87,7 +88,7 @@ module Rscons
vars = vars.merge({
'_TARGET' => target,
'_SOURCES' => sources,
'_DEPFILE' => Rscons.set_suffix(target, '.mf'),
'_DEPFILE' => Rscons.set_suffix(target, env.expand_varref("${DEPFILESUFFIX}", vars)),
})
com_prefix = KNOWN_SUFFIXES.find do |compiler, suffix_var|
sources.first.end_with?(*env.expand_varref("${#{suffix_var}}"))
+39
View File
@@ -0,0 +1,39 @@
module Rscons
module Builders
# A Generic builder class whose name and operation is defined at
# instantiation.
#
# @since 1.8.0
class SimpleBuilder < Builder
# @return [String] The name of this builder.
attr_reader :name
# Create a new builder with the given name and action.
#
# @param name [String,Symbol] The name of the builder when registered.
# @param block [Block]
# The action to perform when the builder is processed. The provided
# block must return the target file on success or false on failure.
# The provided block should have the same signature as {Builder#run}.
def initialize(name, &block)
@name = name.to_s
@block = block
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)
@block.call(target, sources, cache, env, vars)
end
end
end
end
+22 -6
View File
@@ -63,17 +63,23 @@ module Rscons
end
# Remove the cache file.
#
# @return [void]
def clear
FileUtils.rm_f(CACHE_FILE)
initialize!
end
# Clear the cached file checksums.
#
# @return [void]
def clear_checksum_cache!
@lookup_checksums = {}
end
# Write the cache to disk to be loaded next time.
#
# @return [void]
def write
if @dirty || (@cache["version"] != VERSION)
@cache["version"] = VERSION
@@ -86,9 +92,14 @@ module Rscons
# 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.
# @param targets [String, Array<String>] The name(s) of the target file(s).
# @param command [String, Array, Hash]
# The command used to build the target. The command parameter can
# actually be a String, Array, or Hash and could contain information
# other than just the actual command used to build the target. For the
# purposes of the Cache, any difference in the command argument will
# trigger a rebuild.
# @param deps [Array<String>] List of the target's dependency files.
# @param env [Environment] The Rscons::Environment.
# @param options [Hash] Optional options.
# @option options [Boolean] :strict_deps
@@ -148,9 +159,14 @@ module Rscons
# 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 targets [String, Array<String>] The name of the target(s) built.
# @param command [String, Array, Hash]
# The command used to build the target. The command parameter can
# actually be a String, Array, or Hash and could contain information
# other than just the actual command used to build the target. For the
# purposes of the Cache, any difference in the command argument will
# trigger a rebuild.
# @param deps [Array<String>] List of dependencies for the target.
# @param env [Environment] The {Rscons::Environment}.
#
# @return [void]
+132 -48
View File
@@ -7,10 +7,10 @@ module Rscons
# contains a collection of construction variables, options, builders, and
# rules for building targets.
class Environment
# @return [Hash] Set of \{"builder_name" => builder_object} pairs.
# @return [Hash] Set of !{"builder_name" => builder_object} pairs.
attr_reader :builders
# :command, :short, or :off
# @return [Symbol] :command, :short, or :off
attr_accessor :echo
# @return [String, nil] The build root.
@@ -42,7 +42,7 @@ module Rscons
@user_deps = {}
@builders = {}
@build_dirs = []
@build_hooks = []
@build_hooks = {pre: [], post: []}
unless options[:exclude_builders]
DEFAULT_BUILDERS.each do |builder_class_name|
builder_class = Builders.const_get(builder_class_name)
@@ -79,7 +79,7 @@ module Rscons
#
# Any options that #initialize receives can also be specified here.
#
# @return a new {Environment} object.
# @return [Environment] The newly created {Environment} object.
def clone(options = {})
clone = options[:clone] || Set[:variables, :builders]
clone = Set[:variables, :builders, :build_root, :build_dirs, :build_hooks] if clone == :all
@@ -103,9 +103,12 @@ module Rscons
end
end
if clone.include?(:build_hooks)
@build_hooks.each do |build_hook_block|
@build_hooks[:pre].each do |build_hook_block|
env.add_build_hook(&build_hook_block)
end
@build_hooks[:post].each do |build_hook_block|
env.add_post_build_hook(&build_hook_block)
end
end
if block_given?
@@ -117,10 +120,32 @@ module Rscons
# Add a {Builder} object to the Environment.
#
# @param builder [Builder] The {Builder} object to add.
# @overload add_builder(builder)
#
# Add the given builder to the Environment.
#
# @param builder [Builder] An instance of the builder to register.
#
# @overload add_builder(builder,&action)
#
# Create a new {Builders::SimpleBuilder} instance and add it to the
# environment.
#
# @since 1.8.0
#
# @param builder [String,Symbol]
# The name of the builder to add.
#
# @param action [Block]
# A block that will be called when the builder is executed to generate
# a target file. The provided block should have the same prototype as
# {Rscons::Builder#run}.
#
# @return [void]
def add_builder(builder)
def add_builder(builder, &action)
if not builder.is_a? Rscons::Builder
builder = Rscons::Builders::SimpleBuilder.new(builder, &action)
end
@builders[builder.name] = builder
var_defs = builder.default_variables(self)
if var_defs
@@ -132,6 +157,12 @@ module Rscons
# Add a build hook to the Environment.
#
# Build hooks are Ruby blocks which are invoked immediately before a
# build operation takes place. Build hooks have an opportunity to modify
# the construction variables in use for the build operation based on the
# builder in use, target file name, or sources. Build hooks can also
# register new build targets.
#
# @yield [build_op]
# Invoke the given block with the current build operation.
# @yieldparam build_op [Hash]
@@ -144,19 +175,50 @@ module Rscons
#
# @return [void]
def add_build_hook(&block)
@build_hooks << block
@build_hooks[:pre] << block
end
# Add a post build hook to the Environment.
#
# Post-build hooks are Ruby blocks which are invoked immediately after a
# build operation takes place. Post-build hooks are only invoked if the
# build operation succeeded. Post-build hooks can register new build
# targets.
#
# @since 1.7.0
#
# @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_post_build_hook(&block)
@build_hooks[:post] << 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.
# @param src_dir [String, Regexp]
# Path to the source directory. If a Regexp is given, it will be matched
# to source file names.
# @param obj_dir [String]
# Path to the object directory. If a Regexp is given as src_dir, then
# obj_dir can contain backreferences to groups matched from the source
# file name.
#
# @return [void]
def build_dir(src_dir, obj_dir)
src_dir = src_dir.gsub('\\', '/') if src_dir.is_a?(String)
if src_dir.is_a?(String)
src_dir = src_dir.gsub("\\", "/").sub(%r{/*$}, "")
end
@build_dirs << [src_dir, obj_dir]
end
@@ -188,25 +250,27 @@ module Rscons
build_fname
end
# Access a construction variable or environment option.
# Get a construction variable's value.
#
# @see VarSet#[]
def [](*args)
@varset.send(:[], *args)
end
# Set a construction variable or environment option.
# Set a construction variable's value.
#
# @see VarSet#[]=
def []=(*args)
@varset.send(:[]=, *args)
end
# Add a set of construction variables or environment options.
# Add a set of construction variables to the Environment.
#
# @see VarSet#append
def append(*args)
@varset.append(*args)
# @param values [VarSet, Hash] New set of variables.
#
# @return [void]
def append(values)
@varset.append(values)
end
# Build all build targets specified in the Environment.
@@ -216,23 +280,25 @@ module Rscons
#
# @return [void]
def process
unless @targets.empty?
expand_paths!
expand_paths!
while @targets.size > 0
targets = @targets
@targets = {}
cache = Cache.instance
cache.clear_checksum_cache!
targets_processed = {}
process_target = proc do |target|
targets_processed[target] ||= begin
@targets[target][:sources].each do |src|
if @targets.include?(src) and not targets_processed.include?(src)
targets[target][:sources].each do |src|
if targets.include?(src) and not targets_processed.include?(src)
process_target.call(src)
end
end
result = run_builder(@targets[target][:builder],
result = run_builder(targets[target][:builder],
target,
@targets[target][:sources],
targets[target][:sources],
cache,
@targets[target][:vars] || {})
targets[target][:vars] || {})
unless result
raise BuildError.new("Failed to build #{target}")
end
@@ -240,7 +306,7 @@ module Rscons
end
end
begin
@targets.each do |target, target_params|
targets.each do |target, target_params|
process_target.call(target)
end
ensure
@@ -250,6 +316,8 @@ module Rscons
end
# Clear all targets registered for the Environment.
#
# @return [void]
def clear_targets
@targets = {}
end
@@ -263,11 +331,13 @@ module Rscons
#
# @return [Array, String] Expansion of the variable reference.
def expand_varref(varref, extra_vars = nil)
if extra_vars.nil?
@varset
else
@varset.merge(extra_vars)
end.expand_varref(varref)
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
@@ -349,7 +419,8 @@ module Rscons
#
# @return [void]
def depends(target, *user_deps)
target = target.to_s
target = expand_varref(target.to_s)
user_deps = user_deps.map {|ud| expand_varref(ud)}
@user_deps[target] ||= []
@user_deps[target] = (@user_deps[target] + user_deps).uniq
end
@@ -370,8 +441,9 @@ module Rscons
#
# 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 sources [Array<String>] List of source files to build.
# @param suffixes [Array<String>]
# 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.
#
@@ -400,24 +472,29 @@ module Rscons
#
# @param builder [Builder] The Builder to use.
# @param target [String] The target output file.
# @param sources [Array] List of source files.
# @param sources [Array<String>] List of source files.
# @param cache [Cache] The Cache.
# @param vars [Hash] Extra variables to pass to the builder.
#
# @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|
build_operation = {
builder: builder,
target: target,
sources: sources,
vars: vars,
env: self,
}
build_hook_block.call(build_operation)
call_build_hooks = lambda do |sec|
@build_hooks[sec].each do |build_hook_block|
build_operation = {
builder: builder,
target: target,
sources: sources,
vars: vars,
env: self,
}
build_hook_block.call(build_operation)
end
end
builder.run(target, sources, cache, self, vars)
call_build_hooks[:pre]
rv = builder.run(target, sources, cache, self, vars)
call_build_hooks[:post] if rv
rv
end
# Expand a path to be relative to the Environment's build root.
@@ -461,10 +538,10 @@ module Rscons
# 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.
# For {#parse_flags}, 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
@@ -583,6 +660,13 @@ module Rscons
end
end
# Print the Environment's construction variables for debugging.
def dump
@varset.to_h.sort.each do |var, val|
puts "#{var} => #{val.inspect}"
end
end
private
# Expand target and source paths before invoking builders.
+43 -14
View File
@@ -58,7 +58,7 @@ module Rscons
#
# @param values [VarSet, Hash] New set of variables.
#
# @return [self]
# @return [VarSet] Returns self.
def append(values)
coa!
if values.is_a?(VarSet)
@@ -83,32 +83,61 @@ module Rscons
end
alias_method :clone, :merge
# Replace "$\{var}" variable references in varref with the expanded
# Replace "$!{var}" variable references in varref with the expanded
# variables' values, recursively.
#
# @param varref [String, Array] Value containing references to variables.
# @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 [String, Array]
# Expanded value with "$\{var}" variable references replaced.
def expand_varref(varref)
if varref.is_a?(Array)
varref.map do |ent|
expand_varref(ent)
end.flatten
else
# @return [nil, String, Array]
# Expanded value with "$!{var}" variable references replaced.
def expand_varref(varref, lambda_args)
if varref.is_a?(String)
if varref =~ /^(.*)\$\{([^}]+)\}(.*)$/
prefix, varname, suffix = $1, $2, $3
varval = expand_varref(self[varname])
varval = expand_varref(self[varname], lambda_args)
if varval.is_a?(String) or varval.nil?
expand_varref("#{prefix}#{varval}#{suffix}")
expand_varref("#{prefix}#{varval}#{suffix}", lambda_args)
elsif varval.is_a?(Array)
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
# Return a Hash containing all variables in the VarSet.
#
# @since 1.8.0
#
# This method is not terribly efficient. It is intended to be used only by
# debugging code to dump out a VarSet's variables.
#
# @return [Hash] All variables in the VarSet.
def to_h
result = deep_dup(@my_vars)
@coa_vars.reduce(result) do |result, coa_vars|
coa_vars.each_pair do |key, value|
unless result.include?(key)
result[key] = deep_dup(value)
end
end
result
end
end
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons
# gem version
VERSION = "1.5.0"
VERSION = "1.8.0"
end
+1
View File
@@ -24,5 +24,6 @@ Gem::Specification.new do |gem|
gem.add_development_dependency "rake"
gem.add_development_dependency "simplecov"
gem.add_development_dependency "yard"
gem.add_development_dependency "rdoc"
gem.add_development_dependency "redcarpet"
end
+203 -14
View File
@@ -10,6 +10,7 @@ class Dir
end
describe Rscons do
BUILD_TEST_RUN_DIR = "build_test_run"
def rm_rf(dir)
@@ -120,34 +121,43 @@ describe Rscons do
it 'rebuilds a C module when a header it depends on changes' do
test_dir('header')
env = Rscons::Environment.new do |env|
env.Program('header', Dir['*.c'])
make_env = lambda do
Rscons::Environment.new do |env|
env.Program('header', Dir['*.c'])
end
end
make_env[]
expect(`./header`).to eq "The value is 2\n"
file_sub('header.h') {|line| line.sub(/2/, '5')}
env.process
make_env[]
expect(`./header`).to eq "The value is 5\n"
end
it 'does not rebuild a C module when its dependencies have not changed' do
test_dir('header')
env = Rscons::Environment.new do |env|
env.Program('header', Dir['*.c'])
make_env = lambda do
Rscons::Environment.new do |env|
env.Program('header', Dir['*.c'])
end
end
env = make_env[]
expect(`./header`).to eq "The value is 2\n"
expect(lines).to eq [
'CC header.o',
"LD header#{env["PROGSUFFIX"]}",
]
env.process
make_env[]
expect(lines).to eq []
end
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'])
make_env = lambda do
Rscons::Environment.new do |env|
env.Program('header', Dir['*.c'])
end
end
env = make_env[]
expect(`./header`).to eq "The value is 2\n"
expect(lines).to eq [
'CC header.o',
@@ -155,7 +165,7 @@ describe Rscons do
]
sleep 0.05
file_sub('header.c') {|line| line}
env.process
make_env[]
expect(lines).to eq []
end
@@ -187,6 +197,19 @@ describe Rscons do
expect(File.exists?('build_two/two.o')).to be_truthy
end
it "supports trailing slashes at the end of build_dir sources and destinations" do
test_dir("build_dir")
Rscons::Environment.new do |env|
env.append("CPPPATH" => Dir["src/**/*/"])
env.build_dir("src/one/", "build_one/")
env.build_dir("src/two", "build_two")
env.Program("build_dir", Dir["src/**/*.c"])
end
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')
env = Rscons::Environment.new do |env|
@@ -226,6 +249,27 @@ describe Rscons do
]
end
it 'supports simple builders' do
test_dir('json_to_yaml')
Rscons::Environment.new do |env|
require 'json'
require 'yaml'
env.add_builder(:JsonToYaml) do |target, sources, cache, env, vars|
unless cache.up_to_date?(target, :JsonToYaml, sources, env)
cache.mkdir_p(File.dirname(target))
File.open(target, 'w') do |f|
f.write(YAML.dump(JSON.load(IO.read(sources.first))))
end
cache.register_build(target, :JsonToYaml, sources, env)
end
target
end
env.JsonToYaml('foo.yml','foo.json')
end
expect(File.exists?('foo.yml')).to be_truthy
expect(IO.read('foo.yml')).to eq("---\nkey: value\n")
end
it 'cleans built files' do
test_dir('build_dir')
Rscons::Environment.new do |env|
@@ -303,11 +347,14 @@ EOF
end
end
env = Rscons::Environment.new do |env|
env.add_builder(CHGen.new)
env.CHGen("inc.c", ["program.c"])
env.Program("program", Dir["*.c"] + ["inc.c"])
make_env = lambda do
Rscons::Environment.new do |env|
env.add_builder(CHGen.new)
env.CHGen("inc.c", ["program.c"])
env.Program("program", %w[program.c inc.c])
end
end
env = make_env[]
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
@@ -315,7 +362,7 @@ EOF
expect(`./program`).to eq "The value is 42\n"
File.open("inc.c", "w") {|fh| fh.puts "int THE_VALUE = 33;"}
env.process
make_env[]
expect(lines).to eq ["CHGen inc.c"]
expect(`./program`).to eq "The value is 42\n"
end
@@ -347,12 +394,16 @@ EOF
it 'allows cloning all attributes of an Environment object' do
test_dir('clone_env')
built_targets = []
env1 = Rscons::Environment.new(echo: :command) do |env|
env.build_dir('src', 'build')
env['CFLAGS'] = '-O2'
env.add_build_hook do |build_op|
build_op[:vars]['CPPFLAGS'] = '-DSTRING="Hello"'
end
env.add_post_build_hook do |build_op|
built_targets << build_op[:target]
end
env.Program('program', Dir['src/*.c'])
end
@@ -365,6 +416,12 @@ EOF
%Q{gcc -o program#{env1["PROGSUFFIX"]} build/program.o},
%Q{gcc -o program2#{env2["PROGSUFFIX"]} build/program.o},
]
expect(built_targets).to eq([
"build/program.o",
"program#{env1['PROGSUFFIX']}",
"build/program.o",
"program2#{env1['PROGSUFFIX']}",
])
end
it 'builds a C++ program with one source file' do
@@ -543,4 +600,136 @@ EOF
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
it "supports registering build targets from within a build hook" do
test_dir("simple")
Rscons::Environment.new do |env|
env.Program("simple", Dir["*.c"])
env.add_build_hook do |build_op|
if build_op[:target].end_with?(".o")
env.Disassemble("#{build_op[:target]}.txt", build_op[:target])
end
end
end
expect(File.exists?("simple.o")).to be_truthy
expect(File.exists?("simple.o.txt")).to be_truthy
expect(`./simple`).to eq "This is a simple C program\n"
end
it "supports post-build hooks" do
test_dir("simple")
built_targets = []
env = Rscons::Environment.new do |env|
env.Program("simple", Dir["*.c"])
env.add_post_build_hook do |build_op|
built_targets << build_op[:target]
expect(File.exists?(build_op[:target])).to be_truthy
end
end
expect(File.exists?("simple.o")).to be_truthy
expect(`./simple`).to eq "This is a simple C program\n"
expect(built_targets).to eq ["simple.o", "simple#{env["PROGSUFFIX"]}"]
end
it "supports multiple values for CXXSUFFIX" do
test_dir("simple_cc")
File.open("other.cccc", "w") {|fh| fh.puts}
Rscons::Environment.new do |env|
env["CXXSUFFIX"] = %w[.cccc .cc]
env["CXXFLAGS"] += %w[-x c++]
env.Program("simple", Dir["*.cc"] + ["other.cccc"])
end
expect(File.exists?("simple.o")).to be_truthy
expect(File.exists?("other.o")).to be_truthy
expect(`./simple`).to eq "This is a simple C++ program\n"
end
it "supports multiple values for CSUFFIX" do
test_dir("build_dir")
FileUtils.mv("src/one/one.c", "src/one/one.yargh")
Rscons::Environment.new do |env|
env["CSUFFIX"] = %w[.yargh .c]
env["CFLAGS"] += %w[-x c]
env["CPPPATH"] += Dir["src/**/"]
env.Program("build_dir", Dir["src/**/*.{c,yargh}"])
end
expect(File.exists?("src/one/one.o")).to be_truthy
expect(File.exists?("src/two/two.o")).to be_truthy
expect(`./build_dir`).to eq "Hello from two()\n"
end
it "supports multiple values for OBJSUFFIX" do
test_dir("two_sources")
env = Rscons::Environment.new() do |env|
env["OBJSUFFIX"] = %w[.oooo .ooo]
env.Object("one.oooo", "one.c", "CPPFLAGS" => ["-DONE"])
env.Object("two.ooo", "two.c")
env.Program("two_sources", %w[one.oooo two.ooo])
end
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 "supports multiple values for LIBSUFFIX" do
test_dir("two_sources")
env = Rscons::Environment.new() do |env|
env["LIBSUFFIX"] = %w[.aaaa .aaa]
env.Library("one.aaaa", "one.c", "CPPFLAGS" => ["-DONE"])
env.Library("two.aaa", "two.c")
env.Program("two_sources", %w[one.aaaa two.aaa])
end
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 "supports multiple values for ASSUFFIX" do
test_dir("two_sources")
env = Rscons::Environment.new() do |env|
env["ASSUFFIX"] = %w[.ssss .sss]
env["CFLAGS"] += %w[-S]
env.Object("one.ssss", "one.c", "CPPFLAGS" => ["-DONE"])
env.Object("two.sss", "two.c")
env.Program("two_sources", %w[one.ssss two.sss], "ASFLAGS" => env["ASFLAGS"] + %w[-x assembler])
end
expect(lines).to eq([
"CC one.ssss",
"CC two.sss",
"AS one.o",
"AS two.o",
"LD two_sources#{env["PROGSUFFIX"]}",
])
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 "supports dumping an Environment's construction variables" do
test_dir("simple")
env = Rscons::Environment.new do |env|
env["CFLAGS"] += %w[-O2 -fomit-frame-pointer]
end
env.dump
result = lines
expect(result.include?(%{CFLAGS => ["-O2", "-fomit-frame-pointer"]})).to be_truthy
expect(result.include?(%{CPPPATH => []})).to be_truthy
end
end
+9
View File
@@ -0,0 +1,9 @@
module Rscons
describe Builder do
describe "#run" do
it "raises an error if called directly and not through a subclass" do
expect{subject.run(:target, :sources, :cache, :env, :vars)}.to raise_error /This method must be overridden in a subclass/
end
end
end
end
+16
View File
@@ -0,0 +1,16 @@
module Rscons
module Builders
describe Command do
let(:command) { ['pandoc', '-fmarkdown', '-thtml', '-o${_TARGET}', '${_SOURCES}'] }
let(:env) {Environment.new}
subject {Command.new}
it "invokes the command to build the target" do
expected_cmd = ['pandoc', '-fmarkdown', '-thtml', '-ofoo.html', 'foo.md']
expect(subject).to receive(:standard_build).with("CMD foo.html", "foo.html", expected_cmd, ["foo.md"], env, :cache)
subject.run("foo.html", ["foo.md"], :cache, env, {'CMD' => command})
end
end
end
end
+18 -6
View File
@@ -2,19 +2,31 @@ module Rscons
module Builders
describe Object do
let(:env) {Environment.new}
let(:cache) {double(Cache)}
subject {Object.new}
it "supports overriding CCCMD construction variable" do
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(cache).to receive(:up_to_date?).and_return(false)
expect(cache).to receive(:mkdir_p)
expect(FileUtils).to receive(:rm_f)
expect(env).to receive(:execute).with("CC mod.o", ["llc", "mod.c"]).and_return(true)
expect(File).to receive(:exists?).and_return(false)
expect(cache).to receive(:register_build)
subject.run("mod.o", ["mod.c"], cache, env, "CCCMD" => ["llc", "${_SOURCES}"])
end
it "supports overriding DEPFILESUFFIX construction variable" do
expect(cache).to receive(:up_to_date?).and_return(false)
expect(cache).to receive(:mkdir_p)
expect(FileUtils).to receive(:rm_f)
expect(env).to receive(:execute).with(anything, %w[gcc -c -o f.o -MMD -MF f.d in.c]).and_return(true)
expect(File).to receive(:exists?).with("f.d").and_return(false)
expect(cache).to receive(:register_build)
subject.run("f.o", ["in.c"], cache, env, "DEPFILESUFFIX" => ".d")
end
it "raises an error when given a source file with an unknown suffix" do
expect { subject.run("mod.o", ["mod.xyz"], :cache, env, {}) }.to raise_error /unknown input file type: "mod.xyz"/
end
@@ -0,0 +1,19 @@
module Rscons
module Builders
describe SimpleBuilder do
let(:env) {Environment.new}
it "should create a new builder with the given name (as a symbol) and action" do
builder = Rscons::Builders::SimpleBuilder.new(:Foo) { 0x1234 }
expect(builder.name).to eq("Foo")
expect(builder.run(1,2,3,4,5)).to eq(0x1234)
end
it "should create a new builder with the given name (as a string) and action" do
builder = Rscons::Builders::SimpleBuilder.new("Foo") { 0x1234 }
expect(builder.name).to eq("Foo")
expect(builder.run(1,2,3,4,5)).to eq(0x1234)
end
end
end
end
+15 -1
View File
@@ -72,6 +72,13 @@ module Rscons
env.add_builder(Rscons::Builders::Object.new)
expect(env.builders.keys).to eq ["Object"]
end
it "adds a new simple builder to the list of builders" do
env = Environment.new(exclude_builders: true)
expect(env.builders.keys).to eq []
env.add_builder(:Foo) {}
expect(env.builders.keys).to eq ["Foo"]
end
end
describe "#get_build_fname" do
@@ -247,7 +254,7 @@ module Rscons
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 /expand.a.variable.reference/
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
@@ -322,6 +329,13 @@ module Rscons
env.depends("foo", "bar", "baz")
expect(env.instance_variable_get(:@user_deps)).to eq({"foo" => ["bar", "baz"]})
end
it "expands arguments for construction variable references" do
env = Environment.new
env["foo"] = "foo.exe"
env["bar"] = "bar.c"
env.depends("${foo}", "${bar}", "a.h")
expect(env.instance_variable_get(:@user_deps)).to eq({"foo.exe" => ["bar.c", "a.h"]})
end
end
describe "#build_sources" do
+54 -14
View File
@@ -1,5 +1,6 @@
module Rscons
describe VarSet do
describe '#initialize' do
it "initializes variables from a Hash" do
v = VarSet.new({"one" => 1, "two" => :two})
@@ -126,26 +127,26 @@ module Rscons
"CPPPATH" => ["dir1", "dir2"],
"compiler" => "${CC}",
"cmd" => ["${CC}", "-c", "${CFLAGS}", "-I${CPPPATH}"],
"hash" => {})
"lambda" => lambda {|args| "#{args[:v]}--12"})
it "expands to the string itself if the string is not a variable reference" do
expect(v.expand_varref("CC")).to eq("CC")
expect(v.expand_varref("CPPPATH")).to eq("CPPPATH")
expect(v.expand_varref("str")).to eq("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
expect(v.expand_varref("${CC}")).to eq("gcc")
expect(v.expand_varref("${CPPPATH}")).to eq(["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
expect(v.expand_varref("prefix${CFLAGS}suffix")).to eq(["prefix-Wallsuffix", "prefix-O2suffix"])
expect(v.expand_varref(v["cmd"])).to eq(["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
expect(v.expand_varref("${compiler}")).to eq("gcc")
expect(v.expand_varref("${cmd}")).to eq(["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
expect(v.expand_varref("cflag: ${CFLAGS}, cpppath: ${CPPPATH}, compiler: ${compiler}")).to eq([
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",
@@ -153,11 +154,50 @@ module Rscons
])
end
it "returns an empty string when a variable reference refers to a non-existent variable" do
expect(v.expand_varref("${not_here}")).to eq("")
expect(v.expand_varref("${not_here}", :lambda_args)).to eq("")
end
it "raises an error when a variable reference refers to an unhandled type" do
expect { v.expand_varref("${hash}") }.to raise_error /I do not know how to expand a variable reference to a Hash/
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
describe "#to_h" do
it "returns a Hash of all variables in the VarSet" do
v = VarSet.new({"fuz" => "a string", "foo" => 42, "bar" => :baz,
"qax" => [3, 6], "qux" => {a: :b}})
v2 = v.merge("baz" => "qux", "qax" => [4, 7])
v3 = v2.merge("fuz" => nil)
v4 = v3.clone
v4["fuz"] = :fuzzy
v4["foo"] = 43
expect(v.to_h).to eq({
"fuz" => "a string", "foo" => 42, "bar" => :baz,
"qax" => [3, 6], "qux" => {a: :b},
})
expect(v2.to_h).to eq({
"fuz" => "a string", "foo" => 42, "bar" => :baz,
"qax" => [4, 7], "qux" => {a: :b}, "baz" => "qux",
})
expect(v3.to_h).to eq({
"fuz" => nil, "foo" => 42, "bar" => :baz,
"qax" => [4, 7], "qux" => {a: :b}, "baz" => "qux",
})
expect(v4.to_h).to eq({
"fuz" => :fuzzy, "foo" => 43, "bar" => :baz,
"qax" => [4, 7], "qux" => {a: :b}, "baz" => "qux",
})
expect(VarSet.new.to_h).to eq({})
end
end
end
end