Compare commits

...
9 Commits
19 changed files with 364 additions and 30 deletions

No files matched your search

+4 -1
View File
@@ -1,7 +1,7 @@
PATH PATH
remote: . remote: .
specs: specs:
rscons (1.7.0) 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
+46
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
@@ -210,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
@@ -220,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
@@ -345,6 +383,14 @@ 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 ### v1.7.0
- allow build hooks to register new build targets - allow build hooks to register new build targets
+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]
+58 -19
View File
@@ -10,7 +10,7 @@ module Rscons
# @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.
@@ -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
@@ -120,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
@@ -184,8 +206,13 @@ module Rscons
# #
# 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, Regexp] 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)
@@ -223,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.
@@ -287,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
@@ -410,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.
# #
@@ -440,7 +472,7 @@ 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.
# #
@@ -506,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
@@ -628,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.
+21 -1
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)
@@ -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.7.0" 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
+32
View File
@@ -249,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|
@@ -700,4 +721,15 @@ EOF
expect(`./two_sources`).to eq "This is a C program with two sources.\n" expect(`./two_sources`).to eq "This is a C program with two sources.\n"
end 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