Compare commits

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

No files matched your search

+4 -1
View File
@@ -1,7 +1,7 @@
PATH PATH
remote: . remote: .
specs: specs:
rscons (1.6.1) rscons (1.8.0)
json (~> 1.0) json (~> 1.0)
GEM GEM
@@ -12,6 +12,8 @@ GEM
json (1.8.1) json (1.8.1)
multi_json (1.10.1) multi_json (1.10.1)
rake (10.3.2) rake (10.3.2)
rdoc (4.1.2)
json (~> 1.4)
redcarpet (3.1.2) redcarpet (3.1.2)
rspec (3.0.0) rspec (3.0.0)
rspec-core (~> 3.0.0) rspec-core (~> 3.0.0)
@@ -38,6 +40,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
rake rake
rdoc
redcarpet redcarpet
rscons! rscons!
rspec rspec
+69
View File
@@ -154,6 +154,31 @@ Rscons::Environment.new do |env|
end 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 ### Example: Using different compilation flags for some sources
```ruby ```ruby
@@ -173,6 +198,9 @@ Each build hook block will be invoked for every build operation, so the block
should test the target or sources if its action should only apply to some should test the target or sources if its action should only apply to some
subset of build targets or source files. 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 The `build_op` parameter to the build hook block is a Hash describing the
build operation with the following keys: build operation with the following keys:
* `:builder` - `Builder` instance in use * `:builder` - `Builder` instance in use
@@ -207,6 +235,7 @@ end
Rscons ships with a number of builders: Rscons ships with a number of builders:
* Command, which executes a user-defined command to produce the target
* CFile, which builds a C or C++ source file from a lex or yacc input file * 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 * Disassemble, which disassembles an object file to a disassembly listing
* Library, which collects object files into a static library archive file * Library, which collects object files into a static library archive file
@@ -217,6 +246,18 @@ Rscons ships with a number of builders:
If you want to create an Environment that does not contain any builders, If you want to create an Environment that does not contain any builders,
you can use the `exclude_builders` key to the Environment constructor. you can use the `exclude_builders` key to the Environment constructor.
#### Command
```ruby
env.Command(target, sources, 'CMD' => command)
# Example
env.Command("docs.html", "docs.md",
CMD => ['pandoc', '-fmarkdown', '-thtml', '-o${_TARGET}', '${_SOURCES}'])
```
The command builder executes a user-defined command in order to produce the
desired target file based on the provided source files.
#### CFile #### CFile
```ruby ```ruby
@@ -317,6 +358,19 @@ cloned_env["CPPPATH"] << "three"
`base_env["CPPPATH"]` will not include "three". `base_env["CPPPATH"]` will not include "three".
#### Build Hooks
Environments can have build hooks which are added with `env.add_build_hook()`.
Build hooks are invoked immediately before a builder executes.
Build hooks can modify the construction variables in use for the build
operation.
They can also register new build targets.
Environments can also have post-build hooks added with `env.add_post_build_hook()`.
Post-build hooks are only invoked if the build operation was a success.
Post-build hooks can invoke commands using the newly-built files, or register
new build targets.
### Construction Variable Naming ### Construction Variable Naming
* uppercase strings - the default construction variables that Rscons uses * uppercase strings - the default construction variables that Rscons uses
@@ -329,6 +383,21 @@ http://rubydoc.info/github/holtrop/rscons/frames.
## Release Notes ## Release Notes
### v1.8.0
- new Command builder to execute arbitrary user commands
- new SimpleBuilder class
- create new builders quickly by passing a block to Environment#add_builder
- improved YARD documentation
- add Environment#dump to debug Environment construction variables
### v1.7.0
- allow build hooks to register new build targets
- add post-build hooks (register with Environment#add_post_build_hook)
- clear all build targets after processing an Environment
- allow trailing slashes in arguments to Environment#build_dir
### v1.6.1 ### v1.6.1
- add DEPFILESUFFIX construction variable to override dependency file suffix - add DEPFILESUFFIX construction variable to override dependency file suffix
+4 -8
View File
@@ -8,6 +8,10 @@ end
require "bundler/gem_tasks" require "bundler/gem_tasks"
require "rspec/core/rake_task" require "rspec/core/rake_task"
require "yard" require "yard"
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)
@@ -16,11 +20,3 @@ YARD::Rake::YardocTask.new do |yard|
end end
task :default => :spec task :default => :spec
task :clean do
FileUtils.rm_rf(["build_test_run", "doc", "coverage"])
end
task :clobber => :clean do
FileUtils.rm_rf(["pkg"])
end
+1
View File
@@ -0,0 +1 @@
{ "key": "value" }
+7
View File
@@ -6,18 +6,21 @@ require_relative "rscons/varset"
require_relative "rscons/version" require_relative "rscons/version"
# default builders # default builders
require_relative "rscons/builders/command"
require_relative "rscons/builders/cfile" require_relative "rscons/builders/cfile"
require_relative "rscons/builders/disassemble" require_relative "rscons/builders/disassemble"
require_relative "rscons/builders/library" require_relative "rscons/builders/library"
require_relative "rscons/builders/object" require_relative "rscons/builders/object"
require_relative "rscons/builders/preprocess" require_relative "rscons/builders/preprocess"
require_relative "rscons/builders/program" require_relative "rscons/builders/program"
require_relative "rscons/builders/simple_builder"
# Namespace module for rscons classes # Namespace module for rscons classes
module Rscons module Rscons
# Names of the default builders which will be added to all newly created # Names of the default builders which will be added to all newly created
# {Environment} objects. # {Environment} objects.
DEFAULT_BUILDERS = [ DEFAULT_BUILDERS = [
:Command,
:CFile, :CFile,
:Disassemble, :Disassemble,
:Library, :Library,
@@ -30,6 +33,8 @@ module Rscons
class BuildError < RuntimeError; end class BuildError < RuntimeError; end
# Remove all generated files. # Remove all generated files.
#
# @return [void]
def self.clean def self.clean
cache = Cache.instance cache = Cache.instance
# remove all built files # remove all built files
@@ -118,6 +123,8 @@ module Rscons
# Set the command executer array. # Set the command executer array.
# #
# @param val [Array<String>] Command used to execute commands. # @param val [Array<String>] Command used to execute commands.
#
# @return [Array<String>] Command used to execute commands.
def self.command_executer=(val) def self.command_executer=(val)
@@command_executer = val @@command_executer = val
end end
+17 -1
View File
@@ -9,6 +9,8 @@ module Rscons
# Return the name of the builder. # Return the name of the builder.
# #
# If not overridden this defaults to the last component of the class name. # If not overridden this defaults to the last component of the class name.
#
# @return [String] The name of the builder.
def name def name
self.class.name.split(":").last self.class.name.split(":").last
end end
@@ -44,7 +46,7 @@ module Rscons
# file name from a given source file name. # file name from a given source file name.
# #
# @param target [String] The target file name. # @param target [String] The target file name.
# @param source [String, Array] The source file name(s). # @param source [String] The source file name.
# @param env [Environment] The Environment. # @param env [Environment] The Environment.
# #
# @return [Boolean] # @return [Boolean]
@@ -54,6 +56,20 @@ module Rscons
false false
end end
# Run the builder to produce a build target.
#
# @param target [String] Target file name.
# @param sources [Array<String>] Source file name(s).
# @param cache [Cache] The Cache object.
# @param env [Environment] The Environment executing the builder.
# @param vars [Hash,VarSet] Extra construction variables.
#
# @return [String,false]
# Name of the target file on success or false on failure.
def run(target, sources, cache, env, vars)
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 # Check if the cache is up to date for the target and if not execute the
# build command. # build command.
# #
+32
View File
@@ -0,0 +1,32 @@
module Rscons
module Builders
# Execute a command that will produce the given target based on the given
# sources.
#
# @since 1.8.0
#
# Example:
# env.Command("docs.html", "docs.md",
# CMD => %w[pandoc -fmarkdown -thtml -o${_TARGET} ${_SOURCES}])
class Command < Builder
# Run the builder to produce a build target.
#
# @param target [String] Target file name.
# @param sources [Array<String>] Source file name(s).
# @param cache [Cache] The Cache object.
# @param env [Environment] The Environment executing the builder.
# @param vars [Hash,VarSet] Extra construction variables.
#
# @return [String,false]
# Name of the target file on success or false on failure.
def run(target, sources, cache, env, vars)
vars = vars.merge({
"_TARGET" => target,
"_SOURCES" => sources,
})
command = env.build_command("${CMD}", vars)
standard_build("CMD #{target}", target, command, sources, env, cache)
end
end
end
end
+1 -1
View File
@@ -62,7 +62,7 @@ module Rscons
# file name from a given source file name. # file name from a given source file name.
# #
# @param target [String] The target file name. # @param target [String] The target file name.
# @param source [String, Array] The source file name(s). # @param source [String] The source file name.
# @param env [Environment] The Environment. # @param env [Environment] The Environment.
# #
# @return [Boolean] # @return [Boolean]
+39
View File
@@ -0,0 +1,39 @@
module Rscons
module Builders
# A Generic builder class whose name and operation is defined at
# instantiation.
#
# @since 1.8.0
class SimpleBuilder < Builder
# @return [String] The name of this builder.
attr_reader :name
# Create a new builder with the given name and action.
#
# @param name [String,Symbol] The name of the builder when registered.
# @param block [Block]
# The action to perform when the builder is processed. The provided
# block must return the target file on success or false on failure.
# The provided block should have the same signature as {Builder#run}.
def initialize(name, &block)
@name = name.to_s
@block = block
end
# Run the builder to produce a build target.
#
# @param target [String] Target file name.
# @param sources [Array<String>] Source file name(s).
# @param cache [Cache] The Cache object.
# @param env [Environment] The Environment executing the builder.
# @param vars [Hash,VarSet] Extra construction variables.
#
# @return [String,false]
# Name of the target file on success or false on failure.
def run(target, sources, cache, env, vars)
@block.call(target, sources, cache, env, vars)
end
end
end
end
+22 -6
View File
@@ -63,17 +63,23 @@ module Rscons
end end
# Remove the cache file. # Remove the cache file.
#
# @return [void]
def clear def clear
FileUtils.rm_f(CACHE_FILE) FileUtils.rm_f(CACHE_FILE)
initialize! initialize!
end end
# Clear the cached file checksums. # Clear the cached file checksums.
#
# @return [void]
def clear_checksum_cache! def clear_checksum_cache!
@lookup_checksums = {} @lookup_checksums = {}
end end
# Write the cache to disk to be loaded next time. # Write the cache to disk to be loaded next time.
#
# @return [void]
def write def write
if @dirty || (@cache["version"] != VERSION) if @dirty || (@cache["version"] != VERSION)
@cache["version"] = VERSION @cache["version"] = VERSION
@@ -86,9 +92,14 @@ module Rscons
# Check if target(s) are up to date. # Check if target(s) are up to date.
# #
# @param targets [String, Array] The name(s) of the target file(s). # @param targets [String, Array<String>] The name(s) of the target file(s).
# @param command [String, Array] The command used to build the target. # @param command [String, Array, Hash]
# @param deps [Array] List of the target's dependency files. # 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 env [Environment] The Rscons::Environment.
# @param options [Hash] Optional options. # @param options [Hash] Optional options.
# @option options [Boolean] :strict_deps # @option options [Boolean] :strict_deps
@@ -148,9 +159,14 @@ module Rscons
# Store cache information about target(s) built by a builder. # Store cache information about target(s) built by a builder.
# #
# @param targets [String, Array] The name of the target(s) built. # @param targets [String, Array<String>] The name of the target(s) built.
# @param command [String, Array] The command used to build the target. # @param command [String, Array, Hash]
# @param deps [Array] List of dependencies for the target. # 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}. # @param env [Environment] The {Rscons::Environment}.
# #
# @return [void] # @return [void]
+114 -33
View File
@@ -7,10 +7,10 @@ module Rscons
# contains a collection of construction variables, options, builders, and # contains a collection of construction variables, options, builders, and
# rules for building targets. # rules for building targets.
class Environment class Environment
# @return [Hash] Set of \{"builder_name" => builder_object} pairs. # @return [Hash] Set of !{"builder_name" => builder_object} pairs.
attr_reader :builders attr_reader :builders
# :command, :short, or :off # @return [Symbol] :command, :short, or :off
attr_accessor :echo attr_accessor :echo
# @return [String, nil] The build root. # @return [String, nil] The build root.
@@ -42,7 +42,7 @@ module Rscons
@user_deps = {} @user_deps = {}
@builders = {} @builders = {}
@build_dirs = [] @build_dirs = []
@build_hooks = [] @build_hooks = {pre: [], post: []}
unless options[:exclude_builders] unless options[:exclude_builders]
DEFAULT_BUILDERS.each do |builder_class_name| DEFAULT_BUILDERS.each do |builder_class_name|
builder_class = Builders.const_get(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. # 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 = {}) def clone(options = {})
clone = options[:clone] || Set[:variables, :builders] clone = options[:clone] || Set[:variables, :builders]
clone = Set[:variables, :builders, :build_root, :build_dirs, :build_hooks] if clone == :all clone = Set[:variables, :builders, :build_root, :build_dirs, :build_hooks] if clone == :all
@@ -103,9 +103,12 @@ module Rscons
end end
end end
if clone.include?(:build_hooks) 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) env.add_build_hook(&build_hook_block)
end end
@build_hooks[:post].each do |build_hook_block|
env.add_post_build_hook(&build_hook_block)
end
end end
if block_given? if block_given?
@@ -117,10 +120,32 @@ module Rscons
# Add a {Builder} object to the Environment. # 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] # @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 @builders[builder.name] = builder
var_defs = builder.default_variables(self) var_defs = builder.default_variables(self)
if var_defs if var_defs
@@ -132,6 +157,12 @@ module Rscons
# Add a build hook to the Environment. # 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] # @yield [build_op]
# Invoke the given block with the current build operation. # Invoke the given block with the current build operation.
# @yieldparam build_op [Hash] # @yieldparam build_op [Hash]
@@ -144,19 +175,50 @@ module Rscons
# #
# @return [void] # @return [void]
def add_build_hook(&block) 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 end
# Specify a build directory for this Environment. # Specify a build directory for this Environment.
# #
# Source files from src_dir will produce object files under obj_dir. # Source files from src_dir will produce object files under obj_dir.
# #
# @param src_dir [String] Path to the source directory. # @param src_dir [String, Regexp]
# @param obj_dir [String] Path to the object directory. # 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] # @return [void]
def build_dir(src_dir, obj_dir) def build_dir(src_dir, obj_dir)
src_dir = src_dir.gsub('\\', '/') if src_dir.is_a?(String) if src_dir.is_a?(String)
src_dir = src_dir.gsub("\\", "/").sub(%r{/*$}, "")
end
@build_dirs << [src_dir, obj_dir] @build_dirs << [src_dir, obj_dir]
end end
@@ -188,25 +250,27 @@ module Rscons
build_fname build_fname
end end
# Access a construction variable or environment option. # Get a construction variable's value.
# #
# @see VarSet#[] # @see VarSet#[]
def [](*args) def [](*args)
@varset.send(:[], *args) @varset.send(:[], *args)
end end
# Set a construction variable or environment option. # Set a construction variable's value.
# #
# @see VarSet#[]= # @see VarSet#[]=
def []=(*args) def []=(*args)
@varset.send(:[]=, *args) @varset.send(:[]=, *args)
end end
# Add a set of construction variables or environment options. # Add a set of construction variables to the Environment.
# #
# @see VarSet#append # @param values [VarSet, Hash] New set of variables.
def append(*args) #
@varset.append(*args) # @return [void]
def append(values)
@varset.append(values)
end end
# Build all build targets specified in the Environment. # Build all build targets specified in the Environment.
@@ -216,23 +280,25 @@ module Rscons
# #
# @return [void] # @return [void]
def process def process
unless @targets.empty?
expand_paths! expand_paths!
while @targets.size > 0
targets = @targets
@targets = {}
cache = Cache.instance cache = Cache.instance
cache.clear_checksum_cache! cache.clear_checksum_cache!
targets_processed = {} targets_processed = {}
process_target = proc do |target| process_target = proc do |target|
targets_processed[target] ||= begin targets_processed[target] ||= begin
@targets[target][:sources].each do |src| targets[target][:sources].each do |src|
if @targets.include?(src) and not targets_processed.include?(src) if targets.include?(src) and not targets_processed.include?(src)
process_target.call(src) process_target.call(src)
end end
end end
result = run_builder(@targets[target][:builder], result = run_builder(targets[target][:builder],
target, target,
@targets[target][:sources], targets[target][:sources],
cache, cache,
@targets[target][:vars] || {}) targets[target][:vars] || {})
unless result unless result
raise BuildError.new("Failed to build #{target}") raise BuildError.new("Failed to build #{target}")
end end
@@ -240,7 +306,7 @@ module Rscons
end end
end end
begin begin
@targets.each do |target, target_params| targets.each do |target, target_params|
process_target.call(target) process_target.call(target)
end end
ensure ensure
@@ -250,6 +316,8 @@ module Rscons
end end
# Clear all targets registered for the Environment. # Clear all targets registered for the Environment.
#
# @return [void]
def clear_targets def clear_targets
@targets = {} @targets = {}
end end
@@ -373,8 +441,9 @@ module Rscons
# #
# This method is used internally by Rscons builders. # This method is used internally by Rscons builders.
# #
# @param sources [Array] List of source files to build. # @param sources [Array<String>] List of source files to build.
# @param suffixes [Array] List of suffixes to try to convert source files into. # @param suffixes [Array<String>]
# List of suffixes to try to convert source files into.
# @param cache [Cache] The Cache. # @param cache [Cache] The Cache.
# @param vars [Hash] Extra variables to pass to the builder. # @param vars [Hash] Extra variables to pass to the builder.
# #
@@ -403,14 +472,15 @@ module Rscons
# #
# @param builder [Builder] The Builder to use. # @param builder [Builder] The Builder to use.
# @param target [String] The target output file. # @param target [String] The target output file.
# @param sources [Array] List of source files. # @param sources [Array<String>] List of source files.
# @param cache [Cache] The Cache. # @param cache [Cache] The Cache.
# @param vars [Hash] Extra variables to pass to the builder. # @param vars [Hash] Extra variables to pass to the builder.
# #
# @return [String,false] Return value from the {Builder}'s +run+ method. # @return [String,false] Return value from the {Builder}'s +run+ method.
def run_builder(builder, target, sources, cache, vars) def run_builder(builder, target, sources, cache, vars)
vars = @varset.merge(vars) vars = @varset.merge(vars)
@build_hooks.each do |build_hook_block| call_build_hooks = lambda do |sec|
@build_hooks[sec].each do |build_hook_block|
build_operation = { build_operation = {
builder: builder, builder: builder,
target: target, target: target,
@@ -420,7 +490,11 @@ module Rscons
} }
build_hook_block.call(build_operation) build_hook_block.call(build_operation)
end end
builder.run(target, sources, cache, self, vars) end
call_build_hooks[:pre]
rv = builder.run(target, sources, cache, self, vars)
call_build_hooks[:post] if rv
rv
end end
# Expand a path to be relative to the Environment's build root. # Expand a path to be relative to the Environment's build root.
@@ -464,10 +538,10 @@ module Rscons
# Parse command-line flags for compilation/linking options into separate # Parse command-line flags for compilation/linking options into separate
# construction variables. # construction variables.
# #
# The parsed construction variables are returned in a Hash instead of # For {#parse_flags}, the parsed construction variables are returned in a
# merging them directly to the Environment. They can be merged with # Hash instead of merging them directly to the Environment. They can be
# {#merge_flags}. The {#parse_flags!} version immediately merges the parsed # merged with {#merge_flags}. The {#parse_flags!} version immediately
# flags as well. # merges the parsed flags as well.
# #
# Example: # Example:
# # Import FreeType build options # # Import FreeType build options
@@ -586,6 +660,13 @@ module Rscons
end end
end end
# Print the Environment's construction variables for debugging.
def dump
@varset.to_h.sort.each do |var, val|
puts "#{var} => #{val.inspect}"
end
end
private private
# Expand target and source paths before invoking builders. # Expand target and source paths before invoking builders.
+23 -3
View File
@@ -58,7 +58,7 @@ module Rscons
# #
# @param values [VarSet, Hash] New set of variables. # @param values [VarSet, Hash] New set of variables.
# #
# @return [self] # @return [VarSet] Returns self.
def append(values) def append(values)
coa! coa!
if values.is_a?(VarSet) if values.is_a?(VarSet)
@@ -83,7 +83,7 @@ module Rscons
end end
alias_method :clone, :merge 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. # variables' values, recursively.
# #
# @param varref [nil, String, Array, Proc] # @param varref [nil, String, Array, Proc]
@@ -92,7 +92,7 @@ module Rscons
# Arguments to pass to any lambda variable values to be expanded. # Arguments to pass to any lambda variable values to be expanded.
# #
# @return [nil, String, Array] # @return [nil, String, Array]
# Expanded value with "$\{var}" variable references replaced. # Expanded value with "$!{var}" variable references replaced.
def expand_varref(varref, lambda_args) def expand_varref(varref, lambda_args)
if varref.is_a?(String) if varref.is_a?(String)
if varref =~ /^(.*)\$\{([^}]+)\}(.*)$/ if varref =~ /^(.*)\$\{([^}]+)\}(.*)$/
@@ -121,6 +121,26 @@ module Rscons
end end
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 private
# Move all VarSet variables into the copy-on-access list. # Move all VarSet variables into the copy-on-access list.
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons module Rscons
# gem version # gem version
VERSION = "1.6.1" VERSION = "1.8.0"
end end
+1
View File
@@ -24,5 +24,6 @@ Gem::Specification.new do |gem|
gem.add_development_dependency "rake" gem.add_development_dependency "rake"
gem.add_development_dependency "simplecov" gem.add_development_dependency "simplecov"
gem.add_development_dependency "yard" gem.add_development_dependency "yard"
gem.add_development_dependency "rdoc"
gem.add_development_dependency "redcarpet" gem.add_development_dependency "redcarpet"
end end
+179 -9
View File
@@ -10,6 +10,7 @@ class Dir
end end
describe Rscons do describe Rscons do
BUILD_TEST_RUN_DIR = "build_test_run" BUILD_TEST_RUN_DIR = "build_test_run"
def rm_rf(dir) def rm_rf(dir)
@@ -120,34 +121,43 @@ describe Rscons do
it 'rebuilds a C module when a header it depends on changes' do it 'rebuilds a C module when a header it depends on changes' do
test_dir('header') test_dir('header')
env = Rscons::Environment.new do |env| make_env = lambda do
Rscons::Environment.new do |env|
env.Program('header', Dir['*.c']) env.Program('header', Dir['*.c'])
end end
end
make_env[]
expect(`./header`).to eq "The value is 2\n" expect(`./header`).to eq "The value is 2\n"
file_sub('header.h') {|line| line.sub(/2/, '5')} file_sub('header.h') {|line| line.sub(/2/, '5')}
env.process make_env[]
expect(`./header`).to eq "The value is 5\n" expect(`./header`).to eq "The value is 5\n"
end end
it 'does not rebuild a C module when its dependencies have not changed' do it 'does not rebuild a C module when its dependencies have not changed' do
test_dir('header') test_dir('header')
env = Rscons::Environment.new do |env| make_env = lambda do
Rscons::Environment.new do |env|
env.Program('header', Dir['*.c']) env.Program('header', Dir['*.c'])
end end
end
env = make_env[]
expect(`./header`).to eq "The value is 2\n" expect(`./header`).to eq "The value is 2\n"
expect(lines).to eq [ expect(lines).to eq [
'CC header.o', 'CC header.o',
"LD header#{env["PROGSUFFIX"]}", "LD header#{env["PROGSUFFIX"]}",
] ]
env.process make_env[]
expect(lines).to eq [] expect(lines).to eq []
end end
it "does not rebuild a C module when only the file's timestamp has changed" do it "does not rebuild a C module when only the file's timestamp has changed" do
test_dir('header') test_dir('header')
env = Rscons::Environment.new do |env| make_env = lambda do
Rscons::Environment.new do |env|
env.Program('header', Dir['*.c']) env.Program('header', Dir['*.c'])
end end
end
env = make_env[]
expect(`./header`).to eq "The value is 2\n" expect(`./header`).to eq "The value is 2\n"
expect(lines).to eq [ expect(lines).to eq [
'CC header.o', 'CC header.o',
@@ -155,7 +165,7 @@ describe Rscons do
] ]
sleep 0.05 sleep 0.05
file_sub('header.c') {|line| line} file_sub('header.c') {|line| line}
env.process make_env[]
expect(lines).to eq [] expect(lines).to eq []
end end
@@ -187,6 +197,19 @@ describe Rscons do
expect(File.exists?('build_two/two.o')).to be_truthy expect(File.exists?('build_two/two.o')).to be_truthy
end 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 it 'uses build directories before build root' do
test_dir('build_dir') test_dir('build_dir')
env = Rscons::Environment.new do |env| env = Rscons::Environment.new do |env|
@@ -226,6 +249,27 @@ describe Rscons do
] ]
end 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 it 'cleans built files' do
test_dir('build_dir') test_dir('build_dir')
Rscons::Environment.new do |env| Rscons::Environment.new do |env|
@@ -303,11 +347,14 @@ EOF
end end
end end
env = Rscons::Environment.new do |env| make_env = lambda do
Rscons::Environment.new do |env|
env.add_builder(CHGen.new) env.add_builder(CHGen.new)
env.CHGen("inc.c", ["program.c"]) env.CHGen("inc.c", ["program.c"])
env.Program("program", Dir["*.c"] + ["inc.c"]) env.Program("program", %w[program.c inc.c])
end end
end
env = make_env[]
expect(lines).to eq ["CHGen inc.c", "CC program.o", "CC inc.o", "LD program#{env["PROGSUFFIX"]}"] expect(lines).to eq ["CHGen inc.c", "CC program.o", "CC inc.o", "LD program#{env["PROGSUFFIX"]}"]
expect(File.exists?("inc.c")).to be_truthy expect(File.exists?("inc.c")).to be_truthy
@@ -315,7 +362,7 @@ EOF
expect(`./program`).to eq "The value is 42\n" expect(`./program`).to eq "The value is 42\n"
File.open("inc.c", "w") {|fh| fh.puts "int THE_VALUE = 33;"} File.open("inc.c", "w") {|fh| fh.puts "int THE_VALUE = 33;"}
env.process make_env[]
expect(lines).to eq ["CHGen inc.c"] expect(lines).to eq ["CHGen inc.c"]
expect(`./program`).to eq "The value is 42\n" expect(`./program`).to eq "The value is 42\n"
end end
@@ -347,12 +394,16 @@ EOF
it 'allows cloning all attributes of an Environment object' do it 'allows cloning all attributes of an Environment object' do
test_dir('clone_env') test_dir('clone_env')
built_targets = []
env1 = Rscons::Environment.new(echo: :command) do |env| env1 = Rscons::Environment.new(echo: :command) do |env|
env.build_dir('src', 'build') env.build_dir('src', 'build')
env['CFLAGS'] = '-O2' env['CFLAGS'] = '-O2'
env.add_build_hook do |build_op| env.add_build_hook do |build_op|
build_op[:vars]['CPPFLAGS'] = '-DSTRING="Hello"' build_op[:vars]['CPPFLAGS'] = '-DSTRING="Hello"'
end end
env.add_post_build_hook do |build_op|
built_targets << build_op[:target]
end
env.Program('program', Dir['src/*.c']) env.Program('program', Dir['src/*.c'])
end end
@@ -365,6 +416,12 @@ EOF
%Q{gcc -o program#{env1["PROGSUFFIX"]} build/program.o}, %Q{gcc -o program#{env1["PROGSUFFIX"]} build/program.o},
%Q{gcc -o program2#{env2["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 end
it 'builds a C++ program with one source file' do it 'builds a C++ program with one source file' do
@@ -562,4 +619,117 @@ EOF
expect(e2.expand_varref("${computed}")).to eq("-H38xyz") expect(e2.expand_varref("${computed}")).to eq("-H38xyz")
expect(env.expand_varref("${lambda_recurse}")).to eq("-Hello") expect(env.expand_varref("${lambda_recurse}")).to eq("-Hello")
end end
it "supports registering build targets from within a build hook" do
test_dir("simple")
Rscons::Environment.new do |env|
env.Program("simple", Dir["*.c"])
env.add_build_hook do |build_op|
if build_op[:target].end_with?(".o")
env.Disassemble("#{build_op[:target]}.txt", build_op[:target])
end
end
end
expect(File.exists?("simple.o")).to be_truthy
expect(File.exists?("simple.o.txt")).to be_truthy
expect(`./simple`).to eq "This is a simple C program\n"
end
it "supports post-build hooks" do
test_dir("simple")
built_targets = []
env = Rscons::Environment.new do |env|
env.Program("simple", Dir["*.c"])
env.add_post_build_hook do |build_op|
built_targets << build_op[:target]
expect(File.exists?(build_op[:target])).to be_truthy
end
end
expect(File.exists?("simple.o")).to be_truthy
expect(`./simple`).to eq "This is a simple C program\n"
expect(built_targets).to eq ["simple.o", "simple#{env["PROGSUFFIX"]}"]
end
it "supports multiple values for CXXSUFFIX" do
test_dir("simple_cc")
File.open("other.cccc", "w") {|fh| fh.puts}
Rscons::Environment.new do |env|
env["CXXSUFFIX"] = %w[.cccc .cc]
env["CXXFLAGS"] += %w[-x c++]
env.Program("simple", Dir["*.cc"] + ["other.cccc"])
end
expect(File.exists?("simple.o")).to be_truthy
expect(File.exists?("other.o")).to be_truthy
expect(`./simple`).to eq "This is a simple C++ program\n"
end
it "supports multiple values for CSUFFIX" do
test_dir("build_dir")
FileUtils.mv("src/one/one.c", "src/one/one.yargh")
Rscons::Environment.new do |env|
env["CSUFFIX"] = %w[.yargh .c]
env["CFLAGS"] += %w[-x c]
env["CPPPATH"] += Dir["src/**/"]
env.Program("build_dir", Dir["src/**/*.{c,yargh}"])
end
expect(File.exists?("src/one/one.o")).to be_truthy
expect(File.exists?("src/two/two.o")).to be_truthy
expect(`./build_dir`).to eq "Hello from two()\n"
end
it "supports multiple values for OBJSUFFIX" do
test_dir("two_sources")
env = Rscons::Environment.new() do |env|
env["OBJSUFFIX"] = %w[.oooo .ooo]
env.Object("one.oooo", "one.c", "CPPFLAGS" => ["-DONE"])
env.Object("two.ooo", "two.c")
env.Program("two_sources", %w[one.oooo two.ooo])
end
expect(File.exists?("two_sources#{env["PROGSUFFIX"]}")).to be_truthy
expect(`./two_sources`).to eq "This is a C program with two sources.\n"
end
it "supports multiple values for LIBSUFFIX" do
test_dir("two_sources")
env = Rscons::Environment.new() do |env|
env["LIBSUFFIX"] = %w[.aaaa .aaa]
env.Library("one.aaaa", "one.c", "CPPFLAGS" => ["-DONE"])
env.Library("two.aaa", "two.c")
env.Program("two_sources", %w[one.aaaa two.aaa])
end
expect(File.exists?("two_sources#{env["PROGSUFFIX"]}")).to be_truthy
expect(`./two_sources`).to eq "This is a C program with two sources.\n"
end
it "supports multiple values for ASSUFFIX" do
test_dir("two_sources")
env = Rscons::Environment.new() do |env|
env["ASSUFFIX"] = %w[.ssss .sss]
env["CFLAGS"] += %w[-S]
env.Object("one.ssss", "one.c", "CPPFLAGS" => ["-DONE"])
env.Object("two.sss", "two.c")
env.Program("two_sources", %w[one.ssss two.sss], "ASFLAGS" => env["ASFLAGS"] + %w[-x assembler])
end
expect(lines).to eq([
"CC one.ssss",
"CC two.sss",
"AS one.o",
"AS two.o",
"LD two_sources#{env["PROGSUFFIX"]}",
])
expect(File.exists?("two_sources#{env["PROGSUFFIX"]}")).to be_truthy
expect(`./two_sources`).to eq "This is a C program with two sources.\n"
end
it "supports dumping an Environment's construction variables" do
test_dir("simple")
env = Rscons::Environment.new do |env|
env["CFLAGS"] += %w[-O2 -fomit-frame-pointer]
end
env.dump
result = lines
expect(result.include?(%{CFLAGS => ["-O2", "-fomit-frame-pointer"]})).to be_truthy
expect(result.include?(%{CPPPATH => []})).to be_truthy
end
end end
+9
View File
@@ -0,0 +1,9 @@
module Rscons
describe Builder do
describe "#run" do
it "raises an error if called directly and not through a subclass" do
expect{subject.run(:target, :sources, :cache, :env, :vars)}.to raise_error /This method must be overridden in a subclass/
end
end
end
end
+16
View File
@@ -0,0 +1,16 @@
module Rscons
module Builders
describe Command do
let(:command) { ['pandoc', '-fmarkdown', '-thtml', '-o${_TARGET}', '${_SOURCES}'] }
let(:env) {Environment.new}
subject {Command.new}
it "invokes the command to build the target" do
expected_cmd = ['pandoc', '-fmarkdown', '-thtml', '-ofoo.html', 'foo.md']
expect(subject).to receive(:standard_build).with("CMD foo.html", "foo.html", expected_cmd, ["foo.md"], env, :cache)
subject.run("foo.html", ["foo.md"], :cache, env, {'CMD' => command})
end
end
end
end
@@ -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
+7
View File
@@ -72,6 +72,13 @@ module Rscons
env.add_builder(Rscons::Builders::Object.new) env.add_builder(Rscons::Builders::Object.new)
expect(env.builders.keys).to eq ["Object"] expect(env.builders.keys).to eq ["Object"]
end 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 end
describe "#get_build_fname" do describe "#get_build_fname" do
+31
View File
@@ -1,5 +1,6 @@
module Rscons module Rscons
describe VarSet do describe VarSet do
describe '#initialize' do describe '#initialize' do
it "initializes variables from a Hash" do it "initializes variables from a Hash" do
v = VarSet.new({"one" => 1, "two" => :two}) v = VarSet.new({"one" => 1, "two" => :two})
@@ -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/ expect { v.expand_varref("${bad}", :lambda_args) }.to raise_error /I do not know how to expand a variable reference to a Hash/
end end
end end
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
end end