Compare commits

..
54 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
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
31 changed files with 1506 additions and 278 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.9.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
+146 -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
@@ -173,6 +200,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,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
@@ -239,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
@@ -317,6 +372,28 @@ 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.
### 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
@@ -329,6 +406,65 @@ 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
- 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
+9 -9
View File
@@ -8,19 +8,19 @@ end
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "yard"
require "rake/clean"
RSpec::Core::RakeTask.new(:spec)
CLEAN.include %w[build_test_run .yardoc doc coverage]
CLOBBER.include %w[pkg]
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']
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
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
+108 -82
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,97 +38,114 @@ module Rscons
# Class to represent a fatal error while building a target.
class BuildError < RuntimeError; end
# Remove all generated files.
def self.clean
cache = Cache.instance
# remove all built files
cache.targets.each do |target|
FileUtils.rm_f(target)
end
# remove all created directories if they are empty
cache.directories.sort {|a, b| b.size <=> a.size}.each do |directory|
next unless File.directory?(directory)
if (Dir.entries(directory) - ['.', '..']).empty?
Dir.rmdir(directory) rescue nil
class << self
# Remove all generated files.
#
# @return [void]
def clean
cache = Cache.instance
# remove all built files
cache.targets.each do |target|
FileUtils.rm_f(target)
end
end
cache.clear
end
# Return whether the given path is an absolute filesystem path.
#
# @param path [String] the path to examine.
#
# @return [Boolean] Whether the given path is an absolute filesystem path.
def self.absolute_path?(path)
path =~ %r{^(/|\w:[\\/])}
end
# Return a new path by changing the suffix in path to suffix.
#
# @param path [String] The path to alter.
# @param suffix [String] The new filename suffix, e.g. ".exe".
#
# @return [String] New path.
def self.set_suffix(path, suffix)
path.sub(/\.[^.]*$/, suffix)
end
# Return the system shell and arguments for executing a shell command.
#
# @return [Array<String>] The shell and flag.
def self.get_system_shell
@@shell ||=
begin
test_shell = lambda do |*args|
begin
"success" == IO.popen([*args, "echo success"]) do |io|
io.read.strip
end
rescue
false
end
# remove all created directories if they are empty
cache.directories.sort {|a, b| b.size <=> a.size}.each do |directory|
next unless File.directory?(directory)
if (Dir.entries(directory) - ['.', '..']).empty?
Dir.rmdir(directory) rescue nil
end
if ENV["SHELL"] and ENV["SHELL"] != "" and test_shell[ENV["SHELL"], "-c"]
[ENV["SHELL"], "-c"]
elsif Object.const_get("RUBY_PLATFORM") =~ /mingw/
if test_shell["sh", "-c"]
# Using Rscons from MSYS should use MSYS's shell.
["sh", "-c"]
end
cache.clear
end
# Return whether the given path is an absolute filesystem path.
#
# @param path [String] the path to examine.
#
# @return [Boolean] Whether the given path is an absolute filesystem path.
def 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 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 get_system_shell
@shell ||=
begin
test_shell = lambda do |*args|
begin
"success" == IO.popen([*args, "echo success"]) do |io|
io.read.strip
end
rescue
false
end
end
if ENV["SHELL"] and ENV["SHELL"] != "" and test_shell[ENV["SHELL"], "-c"]
[ENV["SHELL"], "-c"]
elsif Object.const_get("RUBY_PLATFORM") =~ /mingw/
if test_shell["sh", "-c"]
# Using Rscons from MSYS should use MSYS's shell.
["sh", "-c"]
else
["cmd", "/c"]
end
else
["cmd", "/c"]
["sh", "-c"]
end
else
["sh", "-c"]
end
end
end
end
# Return an Array containing a command used to execute commands.
#
# This will normally be an empty Array, but on Windows if Rscons detects
# that it is running in MSYS then ["env"] will be returned.
#
# @return [Array<String>] Command used to execute commands.
def self.command_executer
@@command_executer ||=
if Object.const_get("RUBY_PLATFORM") =~ /mingw/
if ENV.keys.find {|key| key =~ /MSYS/}
begin
if IO.popen(["env", "echo", "success"]) {|io| io.read.strip} == "success"
["env"]
# Return an Array containing a command used to execute commands.
#
# This will normally be an empty Array, but on Windows if Rscons detects
# that it is running in MSYS then ["env"] will be returned.
#
# @return [Array<String>] Command used to execute commands.
def command_executer
@command_executer ||=
if Object.const_get("RUBY_PLATFORM") =~ /mingw/
if ENV.keys.find {|key| key =~ /MSYS/}
begin
if IO.popen(["env", "echo", "success"]) {|io| io.read.strip} == "success"
["env"]
end
rescue
end
rescue
end
end
end || []
end
end || []
end
# Set the command executer array.
#
# @param val [Array<String>] Command used to execute commands.
#
# @return [Array<String>] Command used to execute commands.
def command_executer=(val)
@command_executer = val
end
# Set the command executer array.
#
# @param val [Array<String>] Command used to execute commands.
def self.command_executer=(val)
@@command_executer = val
end
end
+21 -3
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)
cache.mkdir_p(File.dirname(target))
FileUtils.rm_f(target)
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
+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}}"))
+28 -8
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}"
else
"${CC}"
end
if sources.find {|s| s.end_with?(*env.expand_varref("${CXXSUFFIX}", vars))}
pp_cc = "${CXX}"
depgen = "${CXXDEPGEN}"
else
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
+59 -16
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|
# target file must exist on disk
return false unless File.exists?(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)
# target must have the same checksum as when it was built last
return false unless @cache["targets"][target]["checksum"] == lookup_checksum(target)
unless Rscons.phony_target?(target)
# target must have the same checksum as when it was built last
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
+157 -65
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,31 +280,35 @@ module Rscons
#
# @return [void]
def process
unless @targets.empty?
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|
if @targets.include?(src) and not targets_processed.include?(src)
process_target.call(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(target_params[:builder],
target,
target_params[:sources],
cache,
target_params[:vars] || {})
unless result
raise BuildError.new("Failed to build #{target}")
end
end
result = run_builder(@targets[target][:builder],
target,
@targets[target][:sources],
cache,
@targets[target][:vars] || {})
unless result
raise BuildError.new("Failed to build #{target}")
end
result
end
end
begin
@targets.each do |target, target_params|
targets.each_key do |target|
process_target.call(target)
end
ensure
@@ -250,6 +318,8 @@ module Rscons
end
# Clear all targets registered for the Environment.
#
# @return [void]
def clear_targets
@targets = {}
end
@@ -263,11 +333,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
@@ -334,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,
@@ -349,7 +422,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 +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.
#
@@ -400,24 +475,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 +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
@@ -583,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.
@@ -593,14 +682,16 @@ module Rscons
#
# @return [void]
def expand_paths!
@targets = @targets.reduce({}) do |result, (target, target_params)|
sources = target_params[:sources].map do |source|
source = expand_path(source) if @build_root
expand_varref(source)
end
@targets = @targets.reduce({}) do |result, (target, target_params_list)|
target = expand_path(target) if @build_root
target = expand_varref(target)
result[target] = target_params.merge(sources: sources)
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
result
end
end
@@ -610,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)
@@ -623,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
+45 -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,63 @@ 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
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
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons
# gem version
VERSION = "1.5.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
+434 -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,367 @@ 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]
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
+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
+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
+19 -5
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
@@ -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
@@ -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
+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