add "prefix" construction variable - close #99 add InstallDirectory builder - close #100
Rscons
Rscons (https://github.com/holtrop/rscons) is a software construction framework inspired by SCons and waf but implemented in Ruby.
Installation
Rscons is distributed as a standalone Ruby script named rscons
. This file
(containing the complete rscons build system) can be checked into your project
and versioned along with your project.
Usage
Standalone
Rscons provides a standalone executable ("rscons") with a command-line interface. The rscons executable will read a build script (by default named Rsconsfile or Rsconsfile.rb) and execute its contents.
With Rake
Rscons can be used with rake as well. The same content that would be written in Rsconsfile can be placed in a Rakefile. It could be placed within a rake task block or split among multiple tasks.
Example Build Scripts
Example: Building a C Program
Rscons::Environment.new do |env|
env["CFLAGS"] << "-Wall"
env.Program("program", Rscons.glob("src/**/*.c"))
end
Example: Building a D Program
Rscons::Environment.new do |env|
env["DFLAGS"] << "-Wall"
env.Program("program", Rscons.glob("src/**/*.d"))
end
Example: Cloning an Environment
main_env = Rscons::Environment.new do |env|
# Store object files from sources under "src" in "build/main"
env.build_dir("src", "build/main")
env["CFLAGS"] = ["-DSOME_DEFINE", "-O3"]
env["LIBS"] = ["SDL"]
env.Program("program", Rscons.glob("src/**/*.cc"))
end
debug_env = main_env.clone do |env|
# Store object files from sources under "src" in "build/debug"
env.build_dir("src", "build/debug")
env["CFLAGS"] -= ["-O3"]
env["CFLAGS"] += ["-g", "-O0"]
env.Program("program-debug", Rscons.glob("src/**/*.cc"))
end
Example: Custom Builder
Custom builders are implemented as classes which extend from Rscons::Builder
.
The builder must have a run
method which is called to invoke the builder.
The run
method should return the name of the target built on success, and
false
on failure.
class GenerateFoo < Rscons::Builder
def run(target, sources, cache, env, vars)
cache.mkdir_p(File.dirname(target))
File.open(target, "w") do |fh|
fh.puts <<EOF
#define GENERATED 42
EOF
end
target
end
end
Rscons::Environment.new do |env|
env.add_builder(GenerateFoo.new)
env.GenerateFoo("foo.h", [])
env.Program("a.out", Rscons.glob("*.c"))
end
Example: Custom Builder That Only Regenerates When Necessary
class CmdBuilder < Rscons::Builder
def run(target, sources, cache, env, vars)
cmd = ["cmd", "-i", sources.first, "-o", target]
unless cache.up_to_date?(target, cmd, sources, env)
cache.mkdir_p(File.dirname(target))
system(cmd)
cache.register_build(target, cmd, sources, env)
end
target
end
end
Rscons::Environment.new do |env|
env.add_builder(CmdBuilder.new)
env.CmdBuilder("foo.gen", "foo_gen.cfg")
end
The Cache#up_to_date?
method accepts an optional 5th parameter which is an
options Hash. The :debug
option can be specified in this Hash with a value
of true
to aid in debugging builders while developing them. For example:
unless cache.up_to_date?(target, cmd, sources, env, debug: true)
end
Example: Custom Builder That Generates Multiple Output Files
class CModuleGenerator < Rscons::Builder
def run(target, sources, cache, env, vars)
c_fname = target
h_fname = target.sub(/\.c$/, ".h")
cmd = ["generate_c_and_h", sources.first, c_fname, h_fname]
unless cache.up_to_date?([c_fname, h_fname], cmd, sources, env)
cache.mkdir_p(File.dirname(target))
system(cmd)
cache.register_build([c_fname, h_fname], cmd, sources, env)
end
target
end
end
Rscons::Environment.new do |env|
env.add_builder(CModuleGenerator.new)
env.CModuleGenerator("build/foo.c", "foo_gen.cfg")
end
Example: Custom Builder Using Builder#standard_build()
The standard_build
method from the Rscons::Builder
base class can be used
when the builder needs to execute a system command to produce the target file.
The standard_build
method will return the correct value so its return value
can be used as the return value from the run
method.
class CmdBuilder < Rscons::Builder
def run(target, sources, cache, env, vars)
cmd = ["cmd", "-i", sources.first, "-o", target]
standard_build("CmdBld #{target}", target, cmd, sources, env, cache)
end
end
Rscons::Environment.new do |env|
env.add_builder(CmdBuilder.new)
env.CmdBuilder("foo.gen", "foo_gen.cfg")
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.
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
Rscons::Environment.new do |env|
env["CFLAGS"] = ["-O3", "-Wall", "-DDEFINE"]
env.add_build_hook do |build_op|
if build_op[:target] =~ %r{build/third-party}
build_op[:vars]["CFLAGS"] -= ["-Wall"]
end
end
env.build_dir("src", "build")
env.Program("program", Rscons.glob("**/*.cc"))
end
Example: Creating a static library
Rscons::Environment.new do |env|
env.Library("mylib.a", Rscons.glob("src/**/*.c"))
end
Example: Creating a C++ parser source from a Yacc/Bison input file
Rscons::Environment.new do |env|
env.CFile("#{env.build_root}/parser.tab.cc", "parser.yy")
end
Details
Environments
The Environment is the main top-level object that Rscons operates with. An Environment must be created by the user in order to build anything. All build targets are registered within an Environment. In many cases only a single Environment will be needed, but more than one can be created (either from scratch or by cloning another existing Environment) if needed.
An Environment consists of:
- a collection of builders
- a collection of construction variables used by those builders
- a mapping of build directories from source directories
- a default build root to apply if no specific build directories are matched
- a collection of user-defined build targets
- a collection of user-defined build hooks
When cloning an environment, by default the construction variables, builders, build hooks, build directories, and build root are cloned, but the new environment does not inherit any of the registered build targets.
The set of environment attributes that are cloned is controllable via the
:clone
option to the #clone
method.
For example, env.clone(clone: [:variables, :builders])
will include
construction variables, and builders but not build hooks, build directories, or
the build root.
The set of pending targets is never cloned.
Cloned environments contain "deep copies" of construction variables. For example, in:
base_env = Rscons::Environment.new
base_env["CPPPATH"] = ["one", "two"]
cloned_env = base_env.clone
cloned_env["CPPPATH"] << "three"
base_env["CPPPATH"]
will not include "three".
Builders
Builders are the workhorses that Rscons uses to execute build operations. Each builder is specialized to perform a particular operation.
Rscons ships with a number of builders:
- 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.
- SharedLibrary, which links object files to produce a dynamically loadable library.
- SharedObject, which compiles source files to produce an object file, in a way that is able to be used to create a shared library.
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
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
env.CFile(target, source)
# Example
env.CFile("parser.c", "parser.y")
The CFile builder will generate a C or C++ source file from a lex (.l, .ll) or yacc (.y, .yy) input file.
Disassemble
env.Disassemble(target, source)
# Example
env.Disassemble("module.dis", "module.o")
The Disassemble builder generates a disassembly listing using objdump from and object file.
Install
env.Install(destination, sources)
# Example
env.Install("dist/bin", "app.exe")
env.Install("dist/share", "share")
Library
env.Library(target, sources)
# Example
env.Library("lib.a", Rscons.glob("src/**/*.c"))
The Library builder creates a static library archive from the given source files.
Object
env.Object(target, sources)
# Example
env.Object("module.o", "module.c")
The Object builder compiles the given sources to an object file. Although it can be called explicitly, it is more commonly implicitly called by the Program builder.
Preprocess
env.Preprocess(target, source)
# Example
env.Preprocess("module-preprocessed.cc", "module.cc")
The Preprocess builder invokes either ${CC}
or ${CXX}
(depending on if the
source contains an extension in ${CXXSUFFIX}
or not) and writes the
preprocessed output to the target file.
Program
env.Program(target, sources)
# Example
env.Program("myprog", Rscons.glob("src/**/*.cc"))
The Program builder compiles and links the given sources to an executable file.
Object files or source files can be given as sources
. A platform-dependent
program suffix will be appended to the target name if one is not specified.
This can be controlled with the PROGSUFFIX
construction variable.
SharedLibrary
env.SharedLibrary(target, sources)
# Example
env.SharedLibrary("mydll", Rscons.glob("src/**/*.cc"))
The SharedLibrary builder compiles and links the given sources to a dynamically
loadable library. Object files or source files can be given as sources
.
A platform-dependent prefix and suffix will be appended to the target name if
they are not specified by the user. These values can be controlled by
overriding the SHLIBPREFIX
and SHLIBSUFFIX
construction variables.
SharedObject
env.SharedObject(target, sources)
# Example
env.SharedObject("lib_module.o", "lib_module.c")
The SharedObject builder compiles the given sources to an object file. Any compilation flags necessary to build the object file in a manner that allows it to be used to create a shared library are added. Although it can be called explicitly, it is more commonly implicitly called by the SharedLibrary builder.
Construction Variables
Construction variables are used to define the toolset and any build options that Rscons will use to build a project. The default construction variable values are configured to build applications using gcc. However, all construction variables can be overridden by the user.
Name | Type | Description | Default |
---|---|---|---|
AR | String | Static library archiver executable | "ar" |
ARCMD | Array | Static library archiver command line | ["${AR}", "${ARFLAGS}", "${_TARGET}", "${_SOURCES}"] |
ARFLAGS | Array | Static library archiver flags | ["rcs"] |
AS | String | Assembler executable | "${CC}" |
ASCMD | Array | Assembler command line | ["${AS}", "-c", "-o", "${_TARGET}", "${ASDEPGEN}", "${INCPREFIX}${ASPPPATH}", "${ASPPFLAGS}", "${ASFLAGS}", "${_SOURCES}"] |
ASDEPGEN | Array | Assembly dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}", "-MT", "TARGET"] |
ASFLAGS | Array | Assembler flags | [] |
ASPPFLAGS | Array | Assembler preprocessor flags | ["${CPPFLAGS}"] |
ASPPPATH | Array | Assembler preprocessor path | ["${CPPPATH}"] |
ASSUFFIX | Array | Assembly file suffixes | [".S"] |
CC | String | C compiler executable | "gcc" |
CCCMD | Array | C compiler command line | ["${CC}", "-c", "-o", "${_TARGET}", "${CCDEPGEN}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${CFLAGS}", "${CCFLAGS}", "${_SOURCES}"] |
CCDEPGEN | Array | C compiler dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}", "-MT", "TARGET"] |
CCFLAGS | Array | Common flags for both C and C++ compiler | [] |
CFLAGS | Array | C compiler flags | [] |
CPP_CMD | Array | Preprocess command line | ["${_PREPROCESS_CC}", "-E", "${_PREPROCESS_DEPGEN}", "-o", "${_TARGET}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${_SOURCES}"] |
CPP_TARGET_SUFFIX | String | Suffix used for crt:preprocess target filename. | ".c" |
CPPDEFINES | Array | C preprocessor defines | [] |
CPPDEFPREFIX | String | Prefix used for C preprocessor to introduce a define | "-D" |
CPPFLAGS | Array | C preprocessor flags | ["${CPPDEFPREFIX}${CPPDEFINES}"] |
CPPPATH | Array | C preprocessor path | [] |
CSUFFIX | Array | C source file suffixes | [".c"] |
CXX | String | C++ compiler executable | "g++" |
CXXCMD | Array | C++ compiler command line | ["${CXX}", "-c", "-o", "${_TARGET}", "${CXXDEPGEN}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${CXXFLAGS}", "${CCFLAGS}", "${_SOURCES}"] |
CXXDEPGEN | Array | C++ compiler dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}", "-MT", "TARGET"] |
CXXFLAGS | Array | C++ compiler flags | [] |
CXXSUFFIX | Array | C++ source file suffixes | [".cc", ".cpp", ".cxx", ".C"] |
D_IMPORT_PATH | Array | D compiler import path | [] |
DC | String | D compiler executable | "gdc" |
DCCMD | Array | D compiler command line | ["${DC}", "-c", "-o", "${_TARGET}", "${DDEPGEN}", "${INCPREFIX}${D_IMPORT_PATH}", "${DFLAGS}", "${_SOURCES}"] |
DDEPGEN | Array | D compiler dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}", "-MT", "TARGET"] |
DEPFILESUFFIX | String | Dependency file suffix for Makefile-style dependency rules emitted by the compiler (used internally for temporary dependency files used to determine a source file's dependencies) | ".mf" |
DFLAGS | Array | D compiler flags | [] |
DISASM_CMD | Array | Disassemble command line | ["${OBJDUMP}", "${DISASM_FLAGS}", "${_SOURCES}"] |
DISASM_FLAGS | Array | Disassemble flags | ["--disassemble", "--source"] |
DSUFFIX | String/Array | Default D source file suffix | ".d" |
INCPREFIX | String | Prefix used for C preprocessor to add an include path | "-I" |
LD | String | nil | Linker executable (automatically determined when nil) |
LDCMD | Array | Link command line | ["${LD}", "-o", "${_TARGET}", "${LDFLAGS}", "${_SOURCES}", "${LIBDIRPREFIX}${LIBPATH}", "${LIBLINKPREFIX}${LIBS}"] |
LDFLAGS | Array | Linker flags | [] |
LEX | String | Lex executable | "flex" |
LEX_CMD | Array | Lex command line | ["${LEX}", "${LEX_FLAGS}", "-o", "${_TARGET}", "${_SOURCES}"] |
LEX_FLAGS | Array | Lex flags | [] |
LEXSUFFIX | Array | Lex input file suffixes | [".l", ".ll"] |
LIBDIRPREFIX | String | Prefix given to linker to add a library search path | "-L" |
LIBLINKPREFIX | String | Prefix given to linker to add a library to link with | "-l" |
LIBPATH | Array | Library load path | [] |
LIBS | Array | Libraries to link with | [] |
LIBSUFFIX | String/Array | Default static library file suffix | ".a" |
OBJDUMP | String | Objdump executable | "objdump" |
OBJSUFFIX | String/Array | Default object file suffix | ".o" |
PROGSUFFIX | String | Default program suffix. | Windows: ".exe", POSIX: "" |
SHCC | String | Shared object C compiler | "${CC}" |
SHCCCMD | Array | Shared object C compiler command line | ["${SHCC}", "-c", "-o", "${_TARGET}", "${CCDEPGEN}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${SHCFLAGS}", "${SHCCFLAGS}", "${_SOURCES}"] |
SHCCFLAGS | Array | Shared object C and C++ compiler flags | Windows: ["${CCFLAGS}"], POSIX: ["${CCFLAGS}", -fPIC"] |
SHCFLAGS | Array | Shared object C compiler flags | ["${CFLAGS}"] |
SHCXX | String | Shared object C++ compiler | "${CXX}" |
SHCXXCMD | Array | Shared object C++ compiler command line | ["${SHCXX}", "-c", "-o", "${_TARGET}", "${CXXDEPGEN}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${SHCXXFLAGS}", "${SHCCFLAGS}", "${_SOURCES}"] |
SHCXXFLAGS | Array | Shared object C++ compiler flags | ["${CXXFLAGS}"] |
SHDC | String | Shared object D compiler | "gdc" |
SHDCCMD | Array | Shared object D compiler command line | ["${SHDC}", "-c", "-o", "${_TARGET}", "${INCPREFIX}${D_IMPORT_PATH}", "${SHDFLAGS}", "${_SOURCES}"] |
SHDFLAGS | Array | Shared object D compiler flags | Windows: ["${DFLAGS}"], POSIX: ["${DFLAGS}", "-fPIC"] |
SHLD | String | Shared library linker | nil (if nil, ${SHCC}, ${SHCXX}, or ${SHDC} is used depending on the sources being linked) |
SHLDCMD | Array | Shared library linker command line | ["${SHLD}", "-o", "${_TARGET}", "${SHLDFLAGS}", "${_SOURCES}", "${SHLIBDIRPREFIX}${LIBPATH}", "${SHLIBLINKPREFIX}${LIBS}"] |
SHLDFLAGS | Array | Shared library linker flags | ["${LDFLAGS}", "-shared"] |
SHLIBDIRPREFIX | String | Prefix given to shared library linker to add a library search path | "-L" |
SHLIBLINKPREFIX | String | Prefix given to shared library linker to add a library to link with | "-l" |
SHLIBPREFIX | String | Shared library file name prefix | Windows: "", POSIX: "lib" |
SHLIBSUFFIX | String | Shared library file name suffix | Windows: ".dll", POSIX: ".so" |
SIZE | String | Size executable. | "size" |
YACC | String | Yacc executable | "bison" |
YACC_CMD | Array | Yacc command line | ["${YACC}", "${YACC_FLAGS}", "-o", "${_TARGET}", "${_SOURCES}"] |
YACC_FLAGS | Array | Yacc flags | ["-d"] |
YACCSUFFIX | Array | Yacc input file suffixes | [".y", ".yy"] |
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.
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.
Example build hook:
Rscons::Environment.new do |env|
# Build third party sources without -Wall
env.add_build_hook do |build_op|
if build_op[:builder].name == "Object" and
build_op[:sources].first =~ %r{src/third-party}
build_op[:vars]["CFLAGS"] -= ["-Wall"]
end
end
end
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:env
-Environment
calling the build hook; note that this may be different from the Environment that the build hook was added to in the case that the original Environment was cloned with build hooks!:target
-String
name of the target file:sources
-Array
of the source files:vars
-Rscons::VarSet
containing the construction variables to use. The build hook can overwrite entries inbuild_op[:vars]
to alter the construction variables in use for this specific build operation.
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.
Explicit Dependencies
A target can be marked as depending on another file that Rscons would not
otherwise know about via the Environment#depends
function. For example,
to force the linker to re-link a Program output when a linker script changes:
Rscons::Environment.new do |env|
env.Program("a.out", "foo.c", "LDFLAGS" => %w[-T linker_script.ld])
env.depends("a.out", "linker_script.ld")
end
You can pass multiple dependency files to Environment#depends
:
env.depends("my_app", "config/link.ld", "README.txt", *Rscons.glob("assets/**/*"))
Command-Line Variables
Variables can be specified on the rscons command line. For example:
rscons VAR=val
These variables are accessible in a global VarSet called Rscons.vars
.
Construction Variable Naming
- uppercase strings - the default construction variables that Rscons uses
- strings beginning with "_" - set and used internally by builders
- symbols, lowercase strings - reserved as user-defined construction variables
API documentation
Documentation for the complete Rscons API can be found at http://www.rubydoc.info/github/holtrop/rscons/master.
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request