Compare commits

...
32 Commits
Author SHA1 Message Date
josh aa795eea85 v1.9.0 2015-03-15 20:56:37 -04:00
josh 7dffef6bd0 update README.md 2015-02-15 19:52:33 -05:00
josh dce2114733 document Copy builder class 2015-02-15 18:42:37 -05:00
josh 0bbdda2d41 fix Command builder default short description 2015-02-15 18:41:41 -05:00
josh 3b8910075a use class instance variables instead of class variables 2015-02-15 18:38:09 -05:00
josh 34beda844e reorganize Rscons module class methods 2015-02-15 18:33:00 -05:00
josh 7a04bec2ff support phony targets - close #25 2015-02-15 18:29:33 -05:00
josh 7698fbf618 add "rscons" binary - close #24 2015-02-08 18:04:37 -05:00
josh 4eedea2563 add Copy builder as a copy of Install 2015-02-07 09:57:44 -05:00
josh f678803a5d add Install builder - close #6 2015-02-07 09:49:29 -05:00
josh 9c49a09f69 update :spec rake task to allow example pattern as an argument 2015-01-29 09:58:09 -05:00
josh 859f87ddee add a Directory builder - close #27 2015-01-29 09:31:00 -05:00
josh 73bc71a656 test expanding paths after a build target is registered from within a build hook 2015-01-28 17:15:55 -05:00
josh 29a8684f90 support registering multiple build targets with the same path - close #26 2015-01-28 17:10:36 -05:00
josh 02aff35222 allow overriding Command builder short description with CMD_DESC variable -- close #22 2015-01-24 09:40:07 -05:00
josh 897d31ebe0 fix Command builder example invocation 2015-01-21 14:07:28 -05:00
josh 76610a07a9 fix variable references that expand to arrays in build target sources - fix #20 2015-01-20 21:26:45 -05:00
josh cb3cc9a0de update 1.9.0 release notes 2015-01-20 21:18:19 -05:00
josh 0ea842fffe rework Preprocess builder to consider deep dependencies - fix #21 2015-01-20 21:16:21 -05:00
josh d28722a4bb fix Rscons.set_suffix to append the given suffix if the filename has none 2015-01-20 21:13:27 -05:00
josh 8142d0bbc8 add v1.8.1 release notes 2015-01-20 21:12:57 -05:00
josh a8023ae03c v1.8.1 2014-10-22 14:30:32 -04:00
josh 99ba015630 fix Environment#dump when construction variables are Symbols 2014-10-22 14:30:14 -04:00
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
29 changed files with 1009 additions and 95 deletions

No files matched your search

+4 -1
View File
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
rscons (1.7.0)
rscons (1.9.0)
json (~> 1.0)
GEM
@@ -12,6 +12,8 @@ GEM
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)
@@ -38,6 +40,7 @@ PLATFORMS
DEPENDENCIES
rake
rdoc
redcarpet
rscons!
rspec
+101 -10
View File
@@ -1,6 +1,7 @@
# Rscons
Software construction library inspired by SCons and implemented in Ruby
Rscons is a software construction framework inspired by SCons and implemented
in Ruby.
[![Gem Version](https://badge.fury.io/rb/rscons.png)](http://badge.fury.io/rb/rscons)
@@ -20,9 +21,10 @@ Or install it yourself as:
## Usage
Rscons is a Ruby library.
It can be called from a standalone Ruby script or it can be used with rake and
called from your Rakefile.
Rscons is implemented as a Ruby library and distributed as a gem.
It also provides a "rscons" executable which can evaluate a build script (by
default named Rsconsfile or Rsconsfile.rb).
It can also be used from rake or from a standalone Ruby script.
### Example: Building a C Program
@@ -154,6 +156,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
@@ -210,16 +237,32 @@ end
Rscons ships with a number of builders:
* 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
* Object, which compiles source files to produce an object file
* Preprocess, which invokes the C/C++ preprocessor on a source file
* Program, which links object files to produce an executable
* Command, which executes a user-defined command to produce the target.
* Copy, which is identical to Install.
* 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.
* Install, which installs files or directories to a specified destination.
* Library, which collects object files into a static library archive file.
* Object, which compiles source files to produce an object file.
* Preprocess, which invokes the C/C++ preprocessor on a source file.
* Program, which links object files to produce an executable.
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}"],
"CMD_DESC" => "PANDOC")
```
The command builder executes a user-defined command in order to produce the
desired target file based on the provided source files.
#### CFile
```ruby
@@ -242,6 +285,15 @@ env.Disassemble("module.dis", "module.o")
The Disassemble builder generates a disassembly listing using objdump from
and object file.
#### Install
```ruby
env.Install(destination, sources)
# Example
env.Install("dist/bin", "app.exe")
env.Install("dist/share", "share")
```
#### Library
```ruby
@@ -333,6 +385,15 @@ 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.
### Phony Targets
A build target name given as a Symbol instead of a String is interpreted as a
"phony" target.
Phony targets operate similarly to normal build targets, except that a file is
not expected to be produced by the builder.
Phony targets will still be "rebuilt" if any source or the command is out of
date.
### Construction Variable Naming
* uppercase strings - the default construction variables that Rscons uses
@@ -345,6 +406,36 @@ http://rubydoc.info/github/holtrop/rscons/frames.
## Release Notes
### v1.9.0
#### New Features
- #6 - add Install and Copy builders
- #22 - allow overriding Command builder short description with CMD_DESC variable
- #24 - add "rscons" executable
- #25 - add support for phony targets given as Symbols instead of Strings
- #26 - support registering multiple build targets with the same target name
- #27 - add Directory builder
#### Fixes
- #20 - fix variable references that expand to arrays in build target sources
- #21 - rework Preprocess builder to consider deep dependencies
- fix Rscons.set_suffix to append the given suffix if the filename has none
- remove ${CFLAGS} from default CPP_CMD
### v1.8.1
- fix Environment#dump when construction variables are symbols
### 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
+5 -1
View File
@@ -13,7 +13,11 @@ require "rake/clean"
CLEAN.include %w[build_test_run .yardoc doc coverage]
CLOBBER.include %w[pkg]
RSpec::Core::RakeTask.new(:spec)
RSpec::Core::RakeTask.new(:spec, :example_string) do |task, args|
if args.example_string
task.rspec_opts = %W[-e "#{args.example_string}" -f documentation]
end
end
YARD::Rake::YardocTask.new do |yard|
yard.files = ['lib/**/*.rb']
Executable
+3
View File
@@ -0,0 +1,3 @@
require "rscons/cli"
Rscons::Cli.run(ARGV)
+1
View File
@@ -0,0 +1 @@
{ "key": "value" }
+1
View File
@@ -0,0 +1 @@
#define BAR xyz42abc
+2
View File
@@ -0,0 +1,2 @@
#include "bar.h"
BAR
+36 -10
View File
@@ -7,19 +7,28 @@ require_relative "rscons/version"
# default builders
require_relative "rscons/builders/cfile"
require_relative "rscons/builders/command"
require_relative "rscons/builders/directory"
require_relative "rscons/builders/disassemble"
require_relative "rscons/builders/install"
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 = [
:CFile,
:Command,
:Copy,
:Directory,
:Disassemble,
:Install,
:Library,
:Object,
:Preprocess,
@@ -29,8 +38,12 @@ module Rscons
# Class to represent a fatal error while building a target.
class BuildError < RuntimeError; end
class << self
# Remove all generated files.
def self.clean
#
# @return [void]
def clean
cache = Cache.instance
# remove all built files
cache.targets.each do |target|
@@ -51,25 +64,34 @@ module Rscons
# @param path [String] the path to examine.
#
# @return [Boolean] Whether the given path is an absolute filesystem path.
def self.absolute_path?(path)
def absolute_path?(path)
path =~ %r{^(/|\w:[\\/])}
end
# Return whether the given target is a phony target.
#
# @param target [Symbol, String] Target name.
#
# @return [Boolean] Whether the given target is a phony target.
def phony_target?(target)
target.is_a?(Symbol)
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, e.g. ".exe".
#
# @return [String] New path.
def self.set_suffix(path, suffix)
path.sub(/\.[^.]*$/, suffix)
def 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 ||=
def get_system_shell
@shell ||=
begin
test_shell = lambda do |*args|
begin
@@ -101,8 +123,8 @@ module Rscons
# 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 ||=
def command_executer
@command_executer ||=
if Object.const_get("RUBY_PLATFORM") =~ /mingw/
if ENV.keys.find {|key| key =~ /MSYS/}
begin
@@ -118,8 +140,12 @@ module Rscons
# Set the command executer array.
#
# @param val [Array<String>] Command used to execute commands.
def self.command_executer=(val)
@@command_executer = val
#
# @return [Array<String>] Command used to execute commands.
def command_executer=(val)
@command_executer = val
end
end
end
+19 -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.
#
@@ -71,8 +87,10 @@ module Rscons
# 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)
unless Rscons.phony_target?(target)
cache.mkdir_p(File.dirname(target))
FileUtils.rm_f(target)
end
return false unless env.execute(short_cmd_string, command)
cache.register_build(target, command, sources, env)
end
+33
View File
@@ -0,0 +1,33 @@
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)
cmd_desc = vars["CMD_DESC"] || "Command"
standard_build("#{cmd_desc} #{target}", target, command, sources, env, cache)
end
end
end
end
+31
View File
@@ -0,0 +1,31 @@
module Rscons
module Builders
# The Directory builder creates a directory.
class Directory < 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)
if File.directory?(target)
target
elsif File.exists?(target)
$stderr.puts "Error: `#{target}' already exists and is not a directory"
false
else
puts "Directory #{target}"
cache.mkdir_p(target)
target
end
end
end
end
end
+64
View File
@@ -0,0 +1,64 @@
require "pathname"
module Rscons
module Builders
# The Install builder copies files/directories to new locations.
class Install < 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)
target_is_dir = (sources.length > 1) ||
Dir.exists?(sources.first) ||
Dir.exists?(target)
outdir = target_is_dir ? target : File.dirname(target)
# Collect the list of files to copy over.
file_map = {}
if target_is_dir
sources.each do |src|
if Dir.exists? src
Dir.glob("#{src}/**/*", File::FNM_DOTMATCH).select do |f|
File.file?(f)
end.each do |subfile|
subpath = Pathname.new(subfile).relative_path_from(Pathname.new(src)).to_s
file_map[subfile] = "#{outdir}/#{subpath}"
end
else
file_map[src] = "#{outdir}/#{File.basename(src)}"
end
end
else
file_map[sources.first] = target
end
printed_message = false
file_map.each do |src, dest|
# Check the cache and copy if necessary
unless cache.up_to_date?(dest, :Copy, [src], env)
unless printed_message
puts "#{name} #{target}"
printed_message = true
end
cache.mkdir_p(File.dirname(dest))
FileUtils.cp(src, dest, :preserve => true)
end
cache.register_build(dest, :Copy, [src], env)
end
target if (target_is_dir ? Dir.exists?(target) : File.exists?(target))
end
end
# The Copy builder is identical to the Install builder.
class Copy < Install; end
end
end
+1 -1
View File
@@ -62,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]
+26 -6
View File
@@ -1,7 +1,10 @@
require "fileutils"
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.
@@ -9,7 +12,9 @@ module Rscons
# @return [Hash] Default construction variables for the builder.
def default_variables(env)
{
"CPP_CMD" => ["${_PREPROCESS_CC}", "-E", "-o", "${_TARGET}", "-I${CPPPATH}", "${CPPFLAGS}", "${CFLAGS}", "${_SOURCES}"],
"CPP_CMD" => %w[
${_PREPROCESS_CC} -E ${_PREPROCESS_DEPGEN}
-o ${_TARGET} -I${CPPPATH} ${CPPFLAGS} ${_SOURCES}],
}
end
@@ -24,17 +29,32 @@ module Rscons
# @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.expand_varref("${CXXSUFFIX}", vars))}
"${CXX}"
if sources.find {|s| s.end_with?(*env.expand_varref("${CXXSUFFIX}", vars))}
pp_cc = "${CXX}"
depgen = "${CXXDEPGEN}"
else
"${CC}"
pp_cc = "${CC}"
depgen = "${CCDEPGEN}"
end
vars = vars.merge("_PREPROCESS_CC" => pp_cc,
"_PREPROCESS_DEPGEN" => depgen,
"_TARGET" => target,
"_SOURCES" => sources)
"_SOURCES" => sources,
"_DEPFILE" => Rscons.set_suffix(target, env.expand_varref("${DEPFILESUFFIX}", vars)))
command = env.build_command("${CPP_CMD}", vars)
standard_build("Preprocess #{target}", target, command, sources, env, cache)
unless cache.up_to_date?(target, command, sources, env)
cache.mkdir_p(File.dirname(target))
return false unless env.execute("Preprocess #{target}", command)
deps = sources
if File.exists?(vars["_DEPFILE"])
deps += Environment.parse_makefile_deps(vars["_DEPFILE"], nil)
FileUtils.rm_f(vars["_DEPFILE"])
end
cache.register_build(target, command, deps.uniq, env)
end
target
end
end
end
end
+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
+56 -13
View File
@@ -56,6 +56,9 @@ module Rscons
# Name of the file to store cache information in
CACHE_FILE = ".rsconscache"
# Prefix for phony cache entries.
PHONY_PREFIX = ":PHONY:"
# Create a Cache object and load in the previous contents from the cache
# file.
def initialize
@@ -63,17 +66,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 +95,15 @@ 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 [Symbol, 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
@@ -109,19 +124,25 @@ module Rscons
# stored in the cache file
def up_to_date?(targets, command, deps, env, options = {})
Array(targets).each do |target|
cache_key = get_cache_key(target)
unless Rscons.phony_target?(target)
# target file must exist on disk
return false unless File.exists?(target)
end
# target must be registered in the cache
return false unless @cache["targets"].has_key?(target)
return false unless @cache["targets"].has_key?(cache_key)
unless Rscons.phony_target?(target)
# target must have the same checksum as when it was built last
return false unless @cache["targets"][target]["checksum"] == lookup_checksum(target)
return false unless @cache["targets"][cache_key]["checksum"] == lookup_checksum(target)
end
# command used to build target must be identical
return false unless @cache["targets"][target]["command"] == Digest::MD5.hexdigest(command.inspect)
return false unless @cache["targets"][cache_key]["command"] == Digest::MD5.hexdigest(command.inspect)
cached_deps = @cache["targets"][target]["deps"] || []
cached_deps = @cache["targets"][cache_key]["deps"] || []
cached_deps_fnames = cached_deps.map { |dc| dc["fname"] }
if options[:strict_deps]
# depedencies passed in must exactly equal those in the cache
@@ -133,7 +154,7 @@ module Rscons
# set of user dependencies must match
user_deps = env.get_user_deps(target) || []
cached_user_deps = @cache["targets"][target]["user_deps"] || []
cached_user_deps = @cache["targets"][cache_key]["user_deps"] || []
cached_user_deps_fnames = cached_user_deps.map { |dc| dc["fname"] }
return false unless user_deps == cached_user_deps_fnames
@@ -148,17 +169,24 @@ 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 [Symbol, 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]
def register_build(targets, command, deps, env)
Array(targets).each do |target|
@cache["targets"][target] = {
target_checksum = Rscons.phony_target?(target) ? "" : calculate_checksum(target)
@cache["targets"][get_cache_key(target)] = {
"command" => Digest::MD5.hexdigest(command.inspect),
"checksum" => calculate_checksum(target),
"checksum" => target_checksum,
"deps" => deps.map do |dep|
{
"fname" => dep,
@@ -212,6 +240,21 @@ module Rscons
private
# Return a String key based on the target name to use in the on-disk cache.
#
# @param target_name [Symbol, String]
# Target name.
#
# @return [String]
# Key name.
def get_cache_key(target_name)
if Rscons.phony_target?(target_name)
PHONY_PREFIX + target_name.to_s
else
target_name
end
end
# Create a Cache object and load in the previous contents from the cache
# file.
def initialize!
+78
View File
@@ -0,0 +1,78 @@
require "rscons"
require "optparse"
module Rscons
# Command-Line Interface functionality.
module Cli
# Default files to look for to execute if none specified.
DEFAULT_RSCONSFILES = %w[Rsconsfile Rsconsfile.rb]
class << self
# Run the Rscons CLI.
#
# @param argv [Array]
# Command-line parameters.
#
# @return [void]
def run(argv)
argv = argv.dup
rsconsfile = nil
OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options]"
opts.separator ""
opts.separator "Options:"
opts.on("-c", "--clean", "Perform clean operation") do
Rscons.clean
exit 0
end
opts.on("-f FILE", "Execute FILE (default Rsconsfile)") do |f|
rsconsfile = f
end
opts.on_tail("--version", "Show version") do
puts "Rscons version #{Rscons::VERSION}"
exit 0
end
opts.on_tail("-h", "--help", "Show this help.") do
puts opts
exit 0
end
end.parse!(argv)
if rsconsfile
unless File.exists?(rsconsfile)
$stderr.puts "Cannot read #{rsconsfile}"
exit 1
end
else
rsconsfile = DEFAULT_RSCONSFILES.find do |f|
File.exists?(f)
end
unless rsconsfile
$stderr.puts "Could not find the Rsconsfile to execute."
$stderr.puts "Looked in: [#{DEFAULT_RSCONSFILES.join(", ")}]"
exit 1
end
end
begin
load rsconsfile
rescue Rscons::BuildError => e
$stderr.puts e.message
exit 1
end
exit 0
end
end
end
end
+82 -35
View File
@@ -10,7 +10,7 @@ module Rscons
# @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.
@@ -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
@@ -120,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
@@ -184,8 +206,13 @@ module Rscons
#
# Source files from src_dir will produce object files under obj_dir.
#
# @param src_dir [String, Regexp] 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)
@@ -223,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.
@@ -251,33 +280,35 @@ module Rscons
#
# @return [void]
def process
expand_paths!
while @targets.size > 0
expand_paths!
targets = @targets
@targets = {}
cache = Cache.instance
cache.clear_checksum_cache!
targets_processed = {}
targets_processed = Set.new
process_target = proc do |target|
targets_processed[target] ||= begin
targets[target][:sources].each do |src|
unless targets_processed.include?(target)
targets_processed << target
targets[target].each do |target_params|
target_params[: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(target_params[:builder],
target,
targets[target][:sources],
target_params[:sources],
cache,
targets[target][:vars] || {})
target_params[:vars] || {})
unless result
raise BuildError.new("Failed to build #{target}")
end
result
end
end
end
begin
targets.each do |target, target_params|
targets.each_key do |target|
process_target.call(target)
end
ensure
@@ -287,6 +318,8 @@ module Rscons
end
# Clear all targets registered for the Environment.
#
# @return [void]
def clear_targets
@targets = {}
end
@@ -373,7 +406,8 @@ module Rscons
#
# @return [void]
def add_target(target, builder, sources, vars, args)
@targets[target] = {
@targets[target] ||= []
@targets[target] << {
builder: builder,
sources: sources,
vars: vars,
@@ -410,8 +444,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.
#
@@ -440,7 +475,7 @@ 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.
#
@@ -506,10 +541,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
@@ -628,6 +663,15 @@ module Rscons
end
end
# Print the Environment's construction variables for debugging.
def dump
varset_hash = @varset.to_h
varset_hash.keys.sort_by(&:to_s).each do |var|
var_str = var.is_a?(Symbol) ? var.inspect : var
puts "#{var_str} => #{varset_hash[var].inspect}"
end
end
private
# Expand target and source paths before invoking builders.
@@ -638,14 +682,16 @@ module Rscons
#
# @return [void]
def expand_paths!
@targets = @targets.reduce({}) do |result, (target, target_params)|
@targets = @targets.reduce({}) do |result, (target, target_params_list)|
target = expand_path(target) if @build_root
target = expand_varref(target)
result[target] = target_params_list.map do |target_params|
sources = target_params[:sources].map do |source|
source = expand_path(source) if @build_root
expand_varref(source)
end.flatten
target_params.merge(sources: sources)
end
target = expand_path(target) if @build_root
target = expand_varref(target)
result[target] = target_params.merge(sources: sources)
result
end
end
@@ -655,7 +701,8 @@ module Rscons
# 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.
# @param target [String, nil]
# Name of the target to gather dependencies for, nil for any/all.
#
# @return [Array<String>] Paths of dependency files.
def self.parse_makefile_deps(mf_fname, target)
@@ -668,7 +715,7 @@ module Rscons
buildup += ' ' + line
if buildup =~ /^(.*): (.*)$/
mf_target, mf_deps = $1.strip, $2
if mf_target == target
if target.nil? or mf_target == target
deps += mf_deps.split(' ').map(&:strip)
end
end
+23 -1
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)
@@ -116,11 +116,33 @@ module Rscons
expand_varref(varref[*lambda_args], lambda_args)
elsif varref.nil?
nil
elsif varref.is_a?(Symbol)
varref
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
private
# Move all VarSet variables into the copy-on-access list.
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons
# gem version
VERSION = "1.7.0"
VERSION = "1.9.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
+263
View File
@@ -249,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|
@@ -700,4 +721,246 @@ EOF
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]
env[:foo] = :bar
end
env.dump
result = lines
expect(result.include?(%{:foo => :bar})).to be_truthy
expect(result.include?(%{CFLAGS => ["-O2", "-fomit-frame-pointer"]})).to be_truthy
expect(result.include?(%{CPPPATH => []})).to be_truthy
end
it "considers deep dependencies when deciding whether to rerun Preprocess builder" do
test_dir("preprocess")
env = Rscons::Environment.new do |env|
env.Preprocess("pp", "foo.h")
end
expect(File.read("pp")).to match(%r{xyz42abc}m)
expect(lines).to eq(["Preprocess pp"])
env.Preprocess("pp", "foo.h")
env.process
expect(lines).to eq([])
File.open("bar.h", "w") do |fh|
fh.puts "#define BAR abc88xyz"
end
$ttt = true
env.Preprocess("pp", "foo.h")
env.process
expect(lines).to eq(["Preprocess pp"])
expect(File.read("pp")).to match(%r{abc88xyz}m)
end
it "allows construction variable references which expand to arrays in sources of a build target" do
test_dir("simple")
Rscons::Environment.new do |env|
env["sources"] = Dir["*.c"]
env.Program("simple", "${sources}")
end
expect(File.exists?("simple.o")).to be_truthy
expect(`./simple`).to eq "This is a simple C program\n"
end
it "supports registering multiple build targets with the same target path" do
test_dir("build_dir")
Rscons::Environment.new do |env|
env["CPPPATH"] << "src/two"
env.Object("one.o", "src/one/one.c")
env.Object("one.o", "src/two/two.c")
end
expect(File.exists?("one.o")).to be_truthy
expect(lines).to eq([
"CC one.o",
"CC one.o",
])
end
it "expands target and source paths when builders are registered in build hooks" do
test_dir("build_dir")
Rscons::Environment.new do |env|
env["CPPPATH"] << "src/two"
env.Object("one.o", "src/one/one.c")
env.add_post_build_hook do |build_op|
if build_op[:target] == "one.o"
env["MODULE"] = "two"
env.Object("${MODULE}.o", "src/${MODULE}/${MODULE}.c")
end
end
end
expect(File.exists?("one.o")).to be_truthy
expect(File.exists?("two.o")).to be_truthy
expect(lines).to eq([
"CC one.o",
"CC two.o",
])
end
context "Directory builder" do
it "creates the requested directory" do
test_dir("simple")
Rscons::Environment.new do |env|
env.Directory("teh_dir")
end
expect(File.directory?("teh_dir")).to be_truthy
expect(lines).to eq(["Directory teh_dir"])
end
it "succeeds when the requested directory already exists" do
test_dir("simple")
FileUtils.mkdir("teh_dir")
Rscons::Environment.new do |env|
env.Directory("teh_dir")
end
expect(File.directory?("teh_dir")).to be_truthy
expect(lines).to eq([])
end
it "fails when the target path is a file" do
test_dir("simple")
FileUtils.touch("teh_dir")
expect do
Rscons::Environment.new do |env|
env.Directory("teh_dir")
end
end.to raise_error /Failed to build teh_dir/
expect(lines).to eq([
"Error: `teh_dir' already exists and is not a directory",
])
end
end
context "Install buildler" do
let(:base_env) do
test_dir("build_dir")
Rscons::Environment.new do |env|
env["CPPPATH"] = Dir["src/**/"]
env["sources"] = Dir["src/**/*.c"]
env.Program("simple.exe", "${sources}")
end
end
it "copies a file to the target file name" do
env = base_env.clone do |env|
lines
env.Install("inst.exe", "simple.exe")
end
expect(lines).to eq(["Install inst.exe"])
env.Install("inst.exe", "simple.exe")
env.process
expect(lines).to eq([])
expect(File.exists?("inst.exe")).to be_truthy
expect(File.read("inst.exe", mode: "rb")).to eq(File.read("simple.exe", mode: "rb"))
FileUtils.rm("inst.exe")
env.Install("inst.exe", "simple.exe")
env.process
expect(lines).to eq(["Install inst.exe"])
end
it "operates the same as a Copy builder" do
env = base_env.clone do |env|
lines
env.Copy("inst.exe", "simple.exe")
end
expect(lines).to eq(["Copy inst.exe"])
env.Copy("inst.exe", "simple.exe")
env.process
expect(lines).to eq([])
expect(File.exists?("inst.exe")).to be_truthy
expect(File.read("inst.exe", mode: "rb")).to eq(File.read("simple.exe", mode: "rb"))
FileUtils.rm("inst.exe")
env.Copy("inst.exe", "simple.exe")
env.process
expect(lines).to eq(["Copy inst.exe"])
end
it "copies a file to the target directory name" do
env = base_env.clone do |env|
lines
env.Directory("inst")
env.Install("inst", "simple.exe")
end
expect(lines).to eq([
"Directory inst",
"Install inst",
])
env.Install("inst", "simple.exe")
env.process
expect(lines).to eq([])
expect(File.exists?("inst/simple.exe")).to be_truthy
expect(File.read("inst/simple.exe", mode: "rb")).to eq(File.read("simple.exe", mode: "rb"))
end
it "copies a directory to the non-existent target directory name" do
env = base_env.clone do |env|
lines
env.Install("dist/src", "src")
end
expect(lines).to eq(["Install dist/src"])
env.Install("dist/src", "src")
env.process
expect(lines).to eq([])
%w[src/one/one.c src/two/two.c src/two/two.h].each do |f|
expect(File.exists?("dist/#{f}")).to be_truthy
expect(File.read("dist/#{f}", mode: "rb")).to eq(File.read(f, mode: "rb"))
end
end
it "copies a directory to the existent target directory name" do
env = base_env.clone do |env|
lines
env.Directory("dist/src")
env.Install("dist/src", "src")
end
expect(lines).to eq([
"Directory dist/src",
"Install dist/src",
])
env.Install("dist/src", "src")
env.process
expect(lines).to eq([])
%w[src/one/one.c src/two/two.c src/two/two.h].each do |f|
expect(File.exists?("dist/#{f}")).to be_truthy
expect(File.read("dist/#{f}", mode: "rb")).to eq(File.read(f, mode: "rb"))
end
FileUtils.rm("dist/src/two/two.c")
env.Install("dist/src", "src")
env.process
expect(lines).to eq(["Install dist/src"])
end
end
context "phony targets" do
it "allows specifying a Symbol as a target name and reruns the builder if the sources or command have changed" do
test_dir("simple")
env = Rscons::Environment.new do |env|
env.add_builder(:Checker) do |target, sources, cache, env, vars|
unless cache.up_to_date?(target, :Checker, sources, env)
puts "Checker #{sources.first}" if env.echo != :off
cache.register_build(target, :Checker, sources, env)
end
target
end
env.Program("simple.exe", "simple.c")
env.Checker(:checker, "simple.exe")
end
expect(lines).to eq(["CC simple.o", "LD simple.exe", "Checker simple.exe"])
env.clone do |env|
env.Checker(:checker, "simple.exe")
end
expect(lines).to eq([])
File.open("simple.exe", "w") do |fh|
fh.puts "Changed simple.exe"
end
env.clone do |env|
env.Checker(:checker, "simple.exe")
end
expect(lines).to eq(["Checker simple.exe"])
end
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
+18
View File
@@ -0,0 +1,18 @@
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("PANDOC foo.html", "foo.html", expected_cmd, ["foo.md"], env, :cache)
subject.run("foo.html", ["foo.md"], :cache, env,
"CMD" => command, "CMD_DESC" => "PANDOC")
end
end
end
end
+34 -4
View File
@@ -1,18 +1,48 @@
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")
cache = double(Cache)
command = %w[my_cpp -E -MMD -MF module.mf -o module.pp module.c]
expect(cache).to receive(:up_to_date?).with("module.pp", command, %w[module.c], env).and_return(false)
expect(cache).to receive(:mkdir_p).with(".")
expect(env).to receive(:execute).with("Preprocess module.pp", command).and_return(true)
expect(File).to receive(:exists?).with("module.mf").and_return(true)
expect(Environment).to receive(:parse_makefile_deps).with("module.mf", nil).and_return(%w[module.c one.h two.h])
expect(FileUtils).to receive(:rm_f).with("module.mf")
expect(cache).to receive(:register_build).with("module.pp", command, %w[module.c one.h two.h], env)
expect(subject.run("module.pp", ["module.c"], cache, env, "CC" => "my_cpp")).to eq("module.pp")
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}"])
cache = double(Cache)
command = %w[my_cpp module.c]
expect(cache).to receive(:up_to_date?).with("module.pp", command, %w[module.c], env).and_return(false)
expect(cache).to receive(:mkdir_p).with(".")
expect(env).to receive(:execute).with("Preprocess module.pp", command).and_return(true)
expect(File).to receive(:exists?).with("module.mf").and_return(true)
expect(Environment).to receive(:parse_makefile_deps).with("module.mf", nil).and_return(%w[module.c one.h two.h])
expect(FileUtils).to receive(:rm_f).with("module.mf")
expect(cache).to receive(:register_build).with("module.pp", command, %w[module.c one.h two.h], env)
expect(subject.run("module.pp", ["module.c"], cache, env, "CPP_CMD" => ["my_cpp", "${_SOURCES}"])).to eq("module.pp")
end
it "returns false if executing the preprocessor fails" do
cache = double(Cache)
command = %w[gcc -E -MMD -MF module.mf -o module.pp module.c]
expect(cache).to receive(:up_to_date?).with("module.pp", command, %w[module.c], env).and_return(false)
expect(cache).to receive(:mkdir_p).with(".")
expect(env).to receive(:execute).with("Preprocess module.pp", command).and_return(false)
expect(subject.run("module.pp", ["module.c"], cache, env, {})).to eq(false)
end
end
end
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
+11 -4
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
@@ -298,10 +305,10 @@ module Rscons
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 []
expect(target[0][:builder].is_a?(Builder)).to be_truthy
expect(target[0][:sources]).to eq ["src1.c", "src2.c"]
expect(target[0][:vars]).to eq({var: "val"})
expect(target[0][:args]).to eq []
end
it "raises an error when vars is not a Hash" do
+31
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})
@@ -168,5 +169,35 @@ module Rscons
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
+17 -7
View File
@@ -24,13 +24,23 @@ describe Rscons do
end
end
describe ".set_suffix" do
it "changes the suffix to the new one" do
expect(Rscons.set_suffix("foo.c", ".h")).to eq("foo.h")
end
it "appends a suffix if the given file name does not have one" do
expect(Rscons.set_suffix("bazz", ".d")).to eq("bazz.d")
end
end
describe ".get_system_shell" do
before(:each) do
Rscons.class_variable_set(:@@shell, nil)
Rscons.instance_variable_set(:@shell, nil)
end
after(:each) do
Rscons.class_variable_set(:@@shell, nil)
Rscons.instance_variable_set(:@shell, nil)
end
it "uses the SHELL environment variable if it tests successfully" do
@@ -70,11 +80,11 @@ describe Rscons do
context "command executer" do
describe ".command_executer" do
before(:each) do
Rscons.class_variable_set(:@@command_executer, nil)
Rscons.instance_variable_set(:@command_executer, nil)
end
after(:each) do
Rscons.class_variable_set(:@@command_executer, nil)
Rscons.instance_variable_set(:@command_executer, nil)
end
it "returns ['env'] if mingw platform in MSYS and 'env' works" do
@@ -105,10 +115,10 @@ describe Rscons do
end
describe ".command_executer=" do
it "overrides the value of @@command_executer" do
Rscons.class_variable_set(:@@command_executer, ["env"])
it "overrides the value of @command_executer" do
Rscons.instance_variable_set(:@command_executer, ["env"])
Rscons.command_executer = []
expect(Rscons.class_variable_get(:@@command_executer)).to eq([])
expect(Rscons.instance_variable_get(:@command_executer)).to eq([])
end
end
end