Compare commits

...

11 Commits

12 changed files with 681 additions and 142 deletions

View File

@ -1,3 +1,25 @@
## v3.0.0
- #136 - Move rsconscache into build directory
- #140 - Support naming environments
- #143 - Add Size builder
- #142 - Add 'sh' script DSL method
- #144 - Add FileUtils class methods to script DSL
- #145 - Support environment variable to set rscons build directory
- #146 - Add ^^/ shortcut to top-level build directory
- #139 - Add tasks
- #147 - Add task options
- #148 - Add license/copyright to distributable script
- #150 - Add env.expand() shortcut method to expand paths and construction variables
- #152 - Add download script method
- #153 - Allow passing spawn options to sh
- #154 - Record build directory absolute path
- #149 - Add shortcut method for creating environments
- #131 - Only configure if necessary
- #151 - Store configure task parameters in configuration cache data
- #137 - Add variants
- #155 - Add build_dir script method
## v2.3.0 ## v2.3.0
### New Features ### New Features

View File

@ -4,7 +4,39 @@
Rscons (https://github.com/holtrop/rscons) is an open-source build system Rscons (https://github.com/holtrop/rscons) is an open-source build system
for developers. for developers.
It supports the following features:
Rscons is written in Ruby, and is inspired by [SCons](https://scons.org/) and [waf](https://waf.io/). * multi-threaded job execution
* auto-configuration
* built-in builders for several common operations
* out-of-the-box support for C, C++, and D languages
* extensibility for other languages or custom builders
* compatible with Windows, Linux, OS X, and FreeBSD
* colorized output with build progress
* build hooks
* user-defined tasks with dependencies and custom parameters
* build variants
At its core, Rscons is mainly an engine to:
* determine the proper order to perform build steps,
* determine whether each build target is up to date or in need of rebuild, and
* schedule those build steps across multiple threads as efficiently as possible.
Along the way, Rscons provides a concise syntax for specifying common types of
build steps, but also provides an extensible framework for performing
custom build operations as well.
Rscons takes inspiration from:
* [SCons](https://scons.org/)
* [waf](https://waf.io/)
* [rake](https://github.com/ruby/rake)
* [CMake](https://cmake.org/)
* [Autoconf](https://www.gnu.org/software/autoconf/)
Rscons is written in Ruby.
The only requirement to run Rscons is that the system has a Ruby interpreter
installed.
See [https://holtrop.github.io/rscons/index.html](https://holtrop.github.io/rscons/index.html) for User Guide and Installation instructions. See [https://holtrop.github.io/rscons/index.html](https://holtrop.github.io/rscons/index.html) for User Guide and Installation instructions.

View File

@ -0,0 +1,3 @@
default do
touch "#{build_dir}/a.file"
end

View File

@ -0,0 +1,8 @@
variant "debug", default: false
variant "release"
with_variants do
env "prog" do |env|
env.Program("^/prog.exe", "prog.c")
end
end

View File

@ -1,5 +0,0 @@
variant_group
variant_group
variant "foo"
with_variants do
end

View File

@ -0,0 +1,14 @@
variant "one"
variant "two", default: false
configure do
if variant_enabled?("one")
puts "one enabled"
end
if variant_enabled?("two")
puts "two enabled"
end
if variant_enabled?("three")
puts "three enabled"
end
end

View File

@ -12,6 +12,7 @@ It supports the following features:
* colorized output with build progress * colorized output with build progress
* build hooks * build hooks
* user-defined tasks with dependencies and custom parameters * user-defined tasks with dependencies and custom parameters
* build variants
At its core, Rscons is mainly an engine to: At its core, Rscons is mainly an engine to:
@ -67,8 +68,8 @@ incorrect decision being made to not rebuild when a rebuild is necessary.
### Build Flexibility ### Build Flexibility
Rscons supports multiple configurations of compilation flags or build options Rscons supports multiple configurations of compilation flags or build options
across multiple environments to build output files in different ways according across multiple environments or build variants to build output files in
to the user's desire. different ways according to the user's desire.
For example, the same source files can be built into a release executable, but For example, the same source files can be built into a release executable, but
also compiled with different compilation flags or build options into a test also compiled with different compilation flags or build options into a test
executable. executable.
@ -143,17 +144,52 @@ different version control systems):
Rscons is typically invoked from the command-line as `./rscons`. Rscons is typically invoked from the command-line as `./rscons`.
./rscons [global options] [[task] [task options] ...] Usage: ./rscons [global options] [[task] [task options] ...]
Global options: Global options:
-b BUILD, --build=BUILD Set build directory (default: build) -A, --all
-f FILE Use FILE as Rsconscript Show all tasks (even those without descriptions) in task list. Use in
-F, --show-failure Show failed command log from previous build and exit conjunction with the -T argument.
-h, --help Show rscons help and exit
-j N, --nthreads=N Set number of threads -b BUILD, --build=BUILD
-r COLOR, --color=COLOR Set color mode (off, auto, force) Set build directory (default: build).
-v, --verbose Run verbosely
--version Show rscons version and exit -e VS, --variants=VS
Enable or disable variants. VS is a comma-separated list of variant
entries. If the entry begins with "-" the variant is disabled instead of
enabled. If the full list begins with "+" or "-" then it modifies the
variants that are enabled by default by only enabling or disabling the
listed variants. Otherwise, the enabled set of variants is as given and
any variants not listed are disabled. The set of enabled variants is
remembered from when the project is configured.
-f FILE
Use FILE as Rsconscript.
-F, --show-failure
Show failed command log from previous build and exit (does not load build
script).
-h, --help
Show rscons help and exit (does not load build script).
-j N, --nthreads=N
Set number of threads (local default: 16).
-r COLOR, --color=COLOR
Set color mode (off, auto, force).
-T, --tasks
Show task list and parameters and exit (loads build script). By default
only tasks with a description are listed. Use -AT to show all tasks whether
they have a description or not.
-v, --verbose
Run verbosely. This causes Rscons to print the full build command used by
each builder.
--version
Show rscons version and exit (does not load build script).
The user can list any number of tasks on the command line. The user can list any number of tasks on the command line.
Any parameters beginning with a "-" that follow a task are interpreted as task Any parameters beginning with a "-" that follow a task are interpreted as task
@ -181,6 +217,19 @@ Rscons execution.
The user can also invoke Rscons with the `-v` global command-line option which The user can also invoke Rscons with the `-v` global command-line option which
will cause Rscons to print each command it is executing. will cause Rscons to print each command it is executing.
## Rscons Operation Phases
When Rscons executes, it performs the following phases:
- Parse the command line.
- This is the last step if --help, --version, or --show-failure is specified.
- Load the build script.
- This is the last step if --tasks is specified.
- Configure the project by running the `configure` task if necessary (the
project has not yet been configured, autoconf is set to true, and the user
is requesting to execute a task that is marked with autoconf set to true)
- Execute user-requested tasks.
#> The Build Script #> The Build Script
Rscons looks for instructions for what to build by reading a build script file Rscons looks for instructions for what to build by reading a build script file
@ -188,10 +237,8 @@ called `Rsconscript` (or `Rsconscript.rb`).
Here is a simple example `Rsconscript` file: Here is a simple example `Rsconscript` file:
```ruby ```ruby
default do env do |env|
Environment.new do |env| env.Program("myprog.exe", glob("src/**/*.c"))
env.Program("myprog.exe", glob("src/**/*.c"))
end
end end
``` ```
@ -203,7 +250,7 @@ The `Rsconscript` file is a Ruby script.
##> Tasks ##> Tasks
Tasks are the high-level user interface for performing functionality in a build Tasks are a high-level user interface for performing functionality in a build
script. script.
Tasks can create Environments that perform compilation/linking steps. Tasks can create Environments that perform compilation/linking steps.
Tasks can also execute arbitrary commands or perform any miscellaneous logic. Tasks can also execute arbitrary commands or perform any miscellaneous logic.
@ -219,7 +266,7 @@ Example:
```ruby ```ruby
task "build" do task "build" do
Environment.new do |env| env do |env|
env.Program("^^/proj.elf", glob("src/**/*.c")) env.Program("^^/proj.elf", glob("src/**/*.c"))
end end
end end
@ -240,9 +287,13 @@ Any newly specified dependencies are added to the current dependencies.
Any action block is appended to the task's list of action blocks to execute Any action block is appended to the task's list of action blocks to execute
when the task is executed. when the task is executed.
Note that for a simple project, the build script may not need to define any
tasks at all and could just make use of the Rscons built-in default task (see
${#Default Task}).
###> Task Parameters ###> Task Parameters
Tasks can also take parameters. Tasks can accept parameters.
Parameters are defined by the build script author, and have default values. Parameters are defined by the build script author, and have default values.
The user can override parameter values on the command line. The user can override parameter values on the command line.
@ -261,7 +312,7 @@ task "build", params: [
param("myparam", "defaultvalue", true, "My special parameter"), param("myparam", "defaultvalue", true, "My special parameter"),
param("xyz", nil, false, "Enable the xyz feature"), param("xyz", nil, false, "Enable the xyz feature"),
] do |task, params| ] do |task, params|
Environment.new do |env| env do |env|
env["CPPDEFINES"] << "SOMEMACRO=#{params["myparam"]}" env["CPPDEFINES"] << "SOMEMACRO=#{params["myparam"]}"
if params["flag"] if params["flag"]
env["CPPDEFINES"] << "ENABLE_FEATURE_XYZ" env["CPPDEFINES"] << "ENABLE_FEATURE_XYZ"
@ -399,8 +450,6 @@ configuration functionality that Rscons provides.
The `default` task is special in that Rscons will execute it if no other task The `default` task is special in that Rscons will execute it if no other task
has been requested by the user on the command line. has been requested by the user on the command line.
It is entirely feasible for the default task to be the only task defined for a
project, and simple projects may wish to do just that.
The default task can also be used to declare a dependency on another task that The default task can also be used to declare a dependency on another task that
would effectively become the default. would effectively become the default.
@ -432,7 +481,8 @@ It will not remove the cached configuration options.
The `distclean` task is built-in to Rscons. The `distclean` task is built-in to Rscons.
It removes all built target files and all cached configuration options. It removes all built target files and all cached configuration options.
Generally it will get the project directory back to the state it was in when Generally it will get the project directory back to the state it was in when
unpacked before any configuration or build operations took place. unpacked or checked out, before any configuration or build operations took
place.
It will not remove items installed by an Install builder. It will not remove items installed by an Install builder.
####> Install Task ####> Install Task
@ -734,17 +784,16 @@ If set, a build define of the specified String will be added to the
##> Building Targets ##> Building Targets
Building target files is accomplished by using Environments. Building target files is accomplished by using Environments.
Environments are typically created within the default task or any user-defined Environments can be created at the top level of the build script, or from
tasks. within a task action block.
Here is an example `default` task block demonstrating how to create an Environments are created with the `env` build script method.
Environment and register a build target: Here is an example build script that creates an Environment and registers a
Program build target:
```ruby ```ruby
default do env do |env|
Environment.new do |env| env.Program("myprog.exe", glob("src/**/*.c"))
env.Program("myprog.exe", glob("src/**/*.c"))
end
end end
``` ```
@ -759,24 +808,21 @@ An Environment includes:
- a collection of construction variables - a collection of construction variables
- a collection of build hooks - a collection of build hooks
- a collection of user-registered build targets - a collection of user-registered build targets
- a build root - a build root directory
All build targets must be registered within an `Environment`. All build targets must be registered within an `Environment`.
If the user does not specify a name for the environment, a name will be If the user does not specify a name for the environment, a name will be
automatically generated based on the Environment's internal ID, for example automatically generated based on the Environment's internal ID, for example
"e.1". "e.1".
The Environment's build root is a directory created within the top-level The Environment's build root is a directory with the same name as the
Rscons build directory. Environment, created within the top-level Rscons build directory.
It is based on the Environment name.
By default it holds all intermediate files generated by Rscons that are needed By default it holds all intermediate files generated by Rscons that are needed
to produce a user-specified build target. to produce a user-specified build target.
For example, for the `Rsconscript`: For example, for the `Rsconscript`:
```ruby ```ruby
default do env "myproj" do |env|
Environment.new(name: "myproj") do |env| env.Program("myprog.exe", glob("src/**/*.c"))
env.Program("myprog.exe", glob("src/**/*.c"))
end
end end
``` ```
@ -786,6 +832,12 @@ Assuming a top-level build directory of "build", the Environment's build root
would be "build/myproj". would be "build/myproj".
This keeps the intermediate generated build artifacts separate from the source This keeps the intermediate generated build artifacts separate from the source
files. files.
Source and target paths passed to a Builder (e.g. Program) can begin with "^/"
to indicate that Rscons should expand those paths to be relative to the
Environment's build root.
If a source or target path passed to a Builder begins with "^^/", it is
expanded to be relative to the Rscons top-level build directory (but outside
the Environment's build root).
###> Construction Variables ###> Construction Variables
@ -797,11 +849,9 @@ construction variables.
Example: Example:
```ruby ```ruby
default do env do |env|
Environment.new do |env| env["CCFLAGS"] += %w[-O2 -Wall]
env["CCFLAGS"] += %w[-O2 -Wall] env["LIBS"] += %w[m]
env["LIBS"] += %w[m]
end
end end
``` ```
@ -813,6 +863,7 @@ It also instructs the linker to link against the `m` library.
* uppercase strings - the default construction variables that Rscons uses * uppercase strings - the default construction variables that Rscons uses
* strings beginning with "_" - set and used internally by builders * strings beginning with "_" - set and used internally by builders
* strings with a ":" as "#{task_name}:#{parameter_name}" - set to task parameter values
* symbols, lowercase strings - reserved as user-defined construction variables * symbols, lowercase strings - reserved as user-defined construction variables
###> Builders ###> Builders
@ -834,7 +885,8 @@ The `target` parameter is the path to the output file or directory.
The `sources` parameter is the path or paths to the input file(s) to be used The `sources` parameter is the path or paths to the input file(s) to be used
by the builder. by the builder.
In the `target` and `sources` parameters, the user can explicitly refer to a In the `target` and `sources` parameters, the user can explicitly refer to a
path within the Environment's build root by beginning the path with "^/". path within the Environment's build root by beginning the path with "^/", or to
a path within the Rscons top-level built directory by beginning with "^^/".
The `vars` parameter is an optional Hash which can include construction The `vars` parameter is an optional Hash which can include construction
variables to be used for this build target. variables to be used for this build target.
Any construction variable values specified in this parameter will override Any construction variable values specified in this parameter will override
@ -1099,7 +1151,7 @@ and flags can be specified with `SIZEFLAGS`.
###> Phony Targets ###> Phony Targets
rscons supports phony build targets. Rscons supports phony build targets.
Normally, a builder produces an output file, and executes whenever the input Normally, a builder produces an output file, and executes whenever the input
files or command have changed. files or command have changed.
A phony build target can be used to register a builder that does not produce A phony build target can be used to register a builder that does not produce
@ -1142,17 +1194,15 @@ build target or source file names.
Example: Example:
```ruby ```ruby
default do env do |env|
Environment.new do |env| env["CFLAGS"] << "-Wall"
env["CFLAGS"] << "-Wall" env.add_build_hook do |builder|
env.add_build_hook do |builder| # Compile sources from under src/tests without the -Wall flag.
# Compile sources from under src/tests without the -Wall flag. if builder.sources.first =~ %r{src/tests/}
if builder.sources.first =~ %r{src/tests/} builder.vars["CFLAGS"] -= %w[-Wall]
builder.vars["CFLAGS"] -= %w[-Wall]
end
end end
env.Program("program.exe", glob("src/**/*.c"))
end end
env.Program("program.exe", glob("src/**/*.c"))
end end
``` ```
@ -1166,7 +1216,7 @@ Build hooks and post-build hooks can register new build targets.
###> Barriers ###> Barriers
Normally Rscons will parallelize all builders. Normally Rscons will parallelize all builders executed within an Environment.
A barrier can be used to separate sets of build targets. A barrier can be used to separate sets of build targets.
All build targets registered before the barrier is created will be built before All build targets registered before the barrier is created will be built before
Rscons will schedule any build targets after the barrier. Rscons will schedule any build targets after the barrier.
@ -1176,14 +1226,117 @@ In other words, build targets are not parallelized across a barrier.
env.barrier env.barrier
``` ```
##> Variants
Rscons supports build variants.
Variants can be used to built multiple variations of the same item with a
specific change.
For example, a desktop application with the same sources could be built to
target KDE or GNOME using build variants.
It is up to the build script author to define the variants and what effect they
have on the build.
This build script defines "kde" and "gnome" variants:
```ruby
variant "kde"
variant "gnome"
with_variants do
env "prog" do |env|
if variant "kde"
env["CPPDEFINES"] << "KDE"
end
if variant "gnome"
env["CPPDEFINES"] << "GNOME"
end
env.Program("^/prog.exe", "src/**/*.cpp")
end
end
```
The `variant` build script method has two uses:
* At the top of the build script, it defines a variant.
* Within a `with_variants` block, it queries for whether the given variant is
active.
The `with_variants` build script method allows the power of variants to be
harnessed.
It iterates through each enabled variant and calls the given block.
In this example, the block would be called twice, once with the "kde" variant
active, and the second time with the "gnome" variant active.
Each `env()` call creates an Environment, so two environments are created.
When an Environment is created within a `with_variants` block, the
Environment's name has the active variant(s) appended to the given Environment
name (if any), and separated by a "-".
In this example, a "prog-kde" Environment would be created with build root
build/prog-kde and -DKDE would be passed to the compiler when compiling each
source.
Next a "prog-gnome" Environment would be created with build root
build/prog-gnome and -DGNOME would be passed to the compiler when compiling
the sources.
Variants are enabled by default, but can be disabled by passing a `false` value
to the `:default` option of the `variant` method.
For example:
```ruby
${include build_tests/variants/default.rb}
```
The `rscons` command line interface provides a `-e`/`--variants` argument which
allows the user to enable a different set of variants from those enabled by
default according to the build script author.
This argument accepts a comma-separated list of variants to enable.
Each entry in the list can begin with "-" to disable the variant instead of
enable it.
If the list begins with "+" or "-", then the entire given list modifies the
defaults given in the build script.
Otherwise, it exactly specifies which variants should be enabled, and any
variant not listed is disabled.
When the project is configured, the set of enabled variants is recorded and
remembered for later Rscons invocations.
This way, a user working on a single variant of a project does not need to
specify the `-e`/`--variants` option on each build operation.
The `variant_enabled?` build script method can be called to query whether the
given variant is enabled.
###> Variant Groups
Variants may be grouped, which allows the build script author to define
multiple combinations of desired variations to build with.
For example:
```ruby
${include build_tests/variants/multiple_groups.rb}
```
This build script executes the block given to `with_variants` four times and
results in four Environments being created:
* prog-kde-debug
* prog-kde-release
* prog-gnome-debug
* prog-gnome-release
The command `./rscons -e-debug` would build just "prog-kde-release" and "prog-gnome-release".
The command `./rscons --variants kde,release` would build just "prog-kde-release".
##> Build Script Methods ##> Build Script Methods
`rscons` provides several methods that a build script can use. `rscons` provides several methods that a build script can use.
* `autoconf` (see ${#Configure Task}) * `autoconf` (see ${#Configure Task})
* `build_dir` which returns the path to the top-level Rscons build directory
* `clean` (see ${#Clean Task}) * `clean` (see ${#Clean Task})
* `configure` (see ${#Configure Task}) * `configure` (see ${#Configure Task})
* `default` (see ${#Default Task}) * `default` (see ${#Default Task})
* `download` (see ${#Downloading Files: The download Method})
* `distclean` (see ${#Distclean Task}) * `distclean` (see ${#Distclean Task})
* `glob` (see ${#Finding Files: The glob Method}) * `glob` (see ${#Finding Files: The glob Method})
* `install` (see ${#Install Task}) * `install` (see ${#Install Task})
@ -1197,6 +1350,10 @@ env.barrier
* `sh` (see (${#Executing Commands: The sh Method}) * `sh` (see (${#Executing Commands: The sh Method})
* `task` (see ${#Tasks}) * `task` (see ${#Tasks})
* `uninstall` (see ${#Uninstall Task}) * `uninstall` (see ${#Uninstall Task})
* `variant` (see ${#Variants})
* `variant_enabled?` (see ${#Variant Groups})
* `variant_group` (see ${#Variant Groups})
* `with_variants` (see ${#Variant Groups})
Additionally, the following methods from the Ruby Additionally, the following methods from the Ruby
[FileUtils](https://ruby-doc.org/stdlib-3.1.0/libdoc/fileutils/rdoc/FileUtils.html) [FileUtils](https://ruby-doc.org/stdlib-3.1.0/libdoc/fileutils/rdoc/FileUtils.html)
@ -1237,16 +1394,26 @@ rather than file system directory ordering).
Example use: Example use:
```ruby ```ruby
default do env do |env|
Environment.new do |env| env.Program("mytests", glob("src/**/*.cc", "test/**/*.cc"))
env.Program("mytests", glob("src/**/*.cc", "test/**/*.cc"))
end
end end
``` ```
This example would build the `mytests` executable from all `.cc` source files This example would build the `mytests` executable from all `.cc` source files
found recursively under the `src` or `test` directory. found recursively under the `src` or `test` directory.
###> Downloading Files: The download Method
The [`download`](../yard/Rscons/Script/GlobalDsl.html#download-instance_method)
method can be used to download a file from a given URL.
Example use:
```ruby
default do
end
```
###> PATH Management ###> PATH Management
`rscons` provides methods for management of the `PATH` environment variable. `rscons` provides methods for management of the `PATH` environment variable.
@ -1354,6 +1521,18 @@ default do
end end
``` ```
Options that Ruby's `spawn` method accepts can also be passed in to the `sh`
method.
For example, to execute the given command with a different working directory,
the `:chdir` option can be specified:
```ruby
default do
# Execute 'ls' from within the 'src' directory:
sh "ls", chdir: "src"
end
```
##> Extending Rscons ##> Extending Rscons
### Adding New Languages ### Adding New Languages
@ -1406,7 +1585,7 @@ class Rscons::Builders::Mine < Rscons::Builder
end end
default do default do
Environment.new do |env| env do |env|
env.add_builder(Rscons::Builders::Mine) env.add_builder(Rscons::Builders::Mine)
end end
end end
@ -1429,7 +1608,7 @@ Rscons::DEFAULT_BUILDERS << :Special
load "SpecialBuilder.rb" load "SpecialBuilder.rb"
default do default do
Environment.new do |env| env do |env|
# A build target using the "Special" builder can be registered. # A build target using the "Special" builder can be registered.
env.Special("target", "source") env.Special("target", "source")
end end
@ -1656,40 +1835,36 @@ ${include lib/rscons/default_construction_variables.rb}
### Example: Building a C Program ### Example: Building a C Program
```ruby ```ruby
default do env do |env|
Environment.new do |env| env["CFLAGS"] << "-Wall"
env["CFLAGS"] << "-Wall" env.Program("program", glob("src/**/*.c"))
env.Program("program", glob("src/**/*.c"))
end
end end
``` ```
### Example: Building a D Program ### Example: Building a D Program
```ruby ```ruby
default do env do |env|
Environment.new do |env| env["DFLAGS"] << "-Wall"
env["DFLAGS"] << "-Wall" env.Program("program", glob("src/**/*.d"))
env.Program("program", glob("src/**/*.d"))
end
end end
``` ```
### Example: Cloning an Environment ### Example: Cloning an Environment
```ruby ```ruby
default do main_env = env do |env|
main_env = Environment.new do |env| env["CFLAGS"] = ["-fshort-enums", "-O3"]
env["CFLAGS"] = ["-DSOME_DEFINE", "-O3"] env["CPPDEFINES"] << "SOME_DEFINE"
env["LIBS"] = ["SDL"] env["LIBS"] = ["SDL"]
env.Program("program", glob("src/**/*.cc")) env.Program("program", glob("src/**/*.cc"))
end end
debug_env = main_env.clone do |env| test_env = main_env.clone do |env|
env["CFLAGS"] -= ["-O3"] env["CFLAGS"] -= ["-O3"]
env["CFLAGS"] += ["-g", "-O0"] env["CFLAGS"] += ["-g", "-O0"]
env.Program("program-debug", glob("src/**/*.cc")) env["CPPDEFINES"] = "ENABLE_TESTS"
end env.Program("program-test", glob("src/**/*.cc"))
end end
``` ```
@ -1709,48 +1884,40 @@ EOF
end end
end end
default do env do |env|
Environment.new do |env| env.add_builder(GenerateFoo)
env.add_builder(GenerateFoo) env.GenerateFoo("foo.h", [])
env.GenerateFoo("foo.h", []) env.Program("a.out", glob("*.c"))
env.Program("a.out", glob("*.c"))
end
end end
``` ```
### Example: Using different compilation flags for some sources ### Example: Using different compilation flags for some sources
```ruby ```ruby
default do env do |env|
Environment.new do |env| env["CFLAGS"] = ["-O3", "-Wall"]
env["CFLAGS"] = ["-O3", "-Wall"] env.add_build_hook do |builder|
env.add_build_hook do |builder| if builder.sources.first =~ %r{src/third-party/}
if builder.sources.first =~ %r{src/third-party/} build_op[:vars]["CFLAGS"] -= ["-Wall"]
build_op[:vars]["CFLAGS"] -= ["-Wall"]
end
end end
env.Program("program", glob("**/*.cc"))
end end
env.Program("program", glob("**/*.cc"))
end end
``` ```
### Example: Creating a static library ### Example: Creating a static library
```ruby ```ruby
default do env do |env|
Environment.new do |env| env.Library("mylib.a", glob("src/**/*.c"))
env.Library("mylib.a", glob("src/**/*.c"))
end
end end
``` ```
### Example: Creating a C++ parser source from a Yacc/Bison input file ### Example: Creating a C++ parser source from a Yacc/Bison input file
```ruby ```ruby
default do env do |env|
Environment.new do |env| env.CFile("^/parser.tab.cc", "parser.yy")
env.CFile("^/parser.tab.cc", "parser.yy")
end
end end
``` ```

View File

@ -5,8 +5,8 @@ module Rscons
# Functionality for an instance of the rscons application invocation. # Functionality for an instance of the rscons application invocation.
class Application class Application
# @return [Array<String>] # @return [Array<Hash>]
# Active variant names. # Active variants.
attr_reader :active_variants attr_reader :active_variants
# @return [String] # @return [String]
@ -54,15 +54,26 @@ module Rscons
# List of task(s) to execute. # List of task(s) to execute.
# @param show_tasks [Boolean] # @param show_tasks [Boolean]
# Flag to show tasks and exit. # Flag to show tasks and exit.
# @param all_tasks [Boolean]
# Flag to show all tasks (not just those with a description).
# @param enabled_variants [String]
# User-specified variants list.
# #
# @return [Integer] # @return [Integer]
# Process exit code (0 on success). # Process exit code (0 on success).
def run(rsconscript, tasks_and_params, show_tasks) def run(rsconscript, tasks_and_params, show_tasks, all_tasks, enabled_variants)
Cache.instance["failed_commands"] = [] Cache.instance["failed_commands"] = []
@enabled_variants = enabled_variants
if enabled_variants == "" && !tasks_and_params.include?("configure")
if cache_enabled_variants = Cache.instance["configuration_data"]["enabled_variants"]
@enabled_variants = cache_enabled_variants
end
end
@script = Script.new @script = Script.new
@script.load(rsconscript) @script.load(rsconscript)
enable_variants
if show_tasks if show_tasks
show_script_tasks show_script_tasks(all_tasks)
return 0 return 0
end end
apply_task_params(tasks_and_params) apply_task_params(tasks_and_params)
@ -79,6 +90,32 @@ module Rscons
0 0
end end
# Apply user-specified variant enables and complain if they don't make
# sense given the build script variant configuration.
def enable_variants
unless @_variants_enabled
if @enabled_variants != ""
exact = !(@enabled_variants =~ /^(\+|-)/)
enabled_variants = @enabled_variants.split(",")
specified_variants = {}
enabled_variants.each do |enable_variant|
enable_variant =~ /^(\+|-)?(.*)$/
enable_disable, variant_name = $1, $2
specified_variants[variant_name] = enable_disable != "-"
end
each_variant do |variant|
if specified_variants.include?(variant[:name])
variant[:enabled] = specified_variants[variant[:name]]
elsif exact
variant[:enabled] = false
end
end
end
@_variants_enabled = true
end
check_enabled_variants
end
# Show the last failures. # Show the last failures.
# #
# @return [void] # @return [void]
@ -131,6 +168,7 @@ module Rscons
# #
# @return [void] # @return [void]
def check_configure def check_configure
enable_variants
unless Cache.instance["configuration_data"]["configured"] unless Cache.instance["configuration_data"]["configured"]
if @script.autoconf if @script.autoconf
configure configure
@ -168,6 +206,7 @@ module Rscons
co.close(false) co.close(false)
raise e raise e
end end
Cache.instance["configuration_data"]["enabled_variants"] = @enabled_variants
co.close(true) co.close(true)
end end
@ -213,24 +252,48 @@ module Rscons
end end
options = options.dup options = options.dup
options[:name] = name options[:name] = name
options[:active] = options.fetch(:default, true) options[:enabled] = options.fetch(:default, true)
options[:key] = options.fetch(:key, name) options[:key] = options.fetch(:key, name)
@variant_groups.last[:variants] << options @variant_groups.last[:variants] << options
end end
end end
# Check if a variant is enabled.
#
# This can be used, for example, in a configuration block to omit or
# include configuration checks based on which variants have been
# configured.
#
# @param variant_name [String]
# Variant name.
#
# @return [Boolean]
# Whether the requested variant is enabled.
def variant_enabled?(variant_name)
each_variant do |variant|
if variant[:name] == variant_name
return variant[:enabled]
end
end
false
end
# Create a variant group. # Create a variant group.
def variant_group(*args, &block) def variant_group(*args, &block)
if args.first.is_a?(String) if args.first.is_a?(String)
name = args.slice!(0) name = args.slice!(0)
end end
@variant_groups << {name: name, variants: []} options = args.first || {}
@variant_groups << options.merge(name: name, variants: [])
if block if block
block[] block[]
end end
end end
# Iterate through variants. # Iterate through enabled variants.
#
# The given block is called for each combination of enabled variants
# across the defined variant groups.
def with_variants(&block) def with_variants(&block)
if @active_variants if @active_variants
raise "with_variants cannot be called within another with_variants block" raise "with_variants cannot be called within another with_variants block"
@ -238,18 +301,15 @@ module Rscons
if @variant_groups.empty? if @variant_groups.empty?
raise "with_variants cannot be called with no variants defined" raise "with_variants cannot be called with no variants defined"
end end
if @variant_groups.any? {|variant_group| variant_group[:variants].empty?} iter_vgs = lambda do |iter_variants|
raise "Error: empty variant group found" if iter_variants.size == @variant_groups.size
end @active_variants = iter_variants.compact
iter_vgs = lambda do |variants|
if variants.size == @variant_groups.size
@active_variants = variants
block[] block[]
@active_variants = nil @active_variants = nil
else else
@variant_groups[variants.size][:variants].each do |variant| @variant_groups[iter_variants.size][:variants].each do |variant|
if variant[:active] if variant[:enabled]
iter_vgs[variants + [variant]] iter_vgs[iter_variants + [variant]]
end end
end end
end end
@ -259,10 +319,33 @@ module Rscons
private private
def show_script_tasks def check_enabled_variants
@variant_groups.each do |variant_group|
enabled_count = variant_group[:variants].count do |variant|
variant[:enabled]
end
if enabled_count == 0
message = "No variants enabled for variant group"
if variant_group[:name]
message += " #{variant_group[:name].inspect}"
end
raise RsconsError.new(message)
end
end
end
def each_variant
@variant_groups.each do |variant_group|
variant_group[:variants].each do |variant|
yield variant
end
end
end
def show_script_tasks(all_tasks)
puts "Tasks:" puts "Tasks:"
Task[].sort.each do |task_name, task| Task[].sort.each do |task_name, task|
if task.description if task.description || all_tasks
puts %[ #{sprintf("%-27s", task_name)} #{task.description}] puts %[ #{sprintf("%-27s", task_name)} #{task.description}]
task.params.each do |param_name, param| task.params.each do |param_name, param|
arg_text = "--#{param_name}" arg_text = "--#{param_name}"
@ -273,6 +356,15 @@ module Rscons
end end
end end
end end
unless @variant_groups.empty?
@variant_groups.each do |variant_group|
puts "\nVariant group#{variant_group[:name] ? " '#{variant_group[:name]}'" : ""}:"
variant_group[:variants].each do |variant|
puts " #{variant[:name]}#{variant[:enabled] ? " (enabled)" : ""}"
end
end
end
end end
def apply_task_params(tasks_and_params) def apply_task_params(tasks_and_params)

View File

@ -54,13 +54,23 @@ module Rscons
def run_toplevel(argv) def run_toplevel(argv)
rsconscript = nil rsconscript = nil
show_tasks = false show_tasks = false
all_tasks = false
enabled_variants = ""
OptionParser.new do |opts| OptionParser.new do |opts|
opts.on("-A", "--all") do
all_tasks = true
end
opts.on("-b", "--build DIR") do |build_dir| opts.on("-b", "--build DIR") do |build_dir|
Rscons.application.build_dir = build_dir Rscons.application.build_dir = build_dir
end end
opts.on("-e", "--variants VS") do |variants|
enabled_variants = variants
end
opts.on("-f FILE") do |f| opts.on("-f FILE") do |f|
rsconscript = f rsconscript = f
end end
@ -127,7 +137,7 @@ module Rscons
end end
begin begin
Rscons.application.run(rsconscript, tasks_and_params, show_tasks) Rscons.application.run(rsconscript, tasks_and_params, show_tasks, all_tasks, enabled_variants)
rescue RsconsError => e rescue RsconsError => e
Ansi.write($stderr, :red, e.message, :reset, "\n") Ansi.write($stderr, :red, e.message, :reset, "\n")
1 1
@ -139,15 +149,49 @@ module Rscons
Usage: #{$0} [global options] [[task] [task options] ...] Usage: #{$0} [global options] [[task] [task options] ...]
Global options: Global options:
-b BUILD, --build=BUILD Set build directory (default: build) -A, --all
-f FILE Use FILE as Rsconscript Show all tasks (even those without descriptions) in task list. Use in
-F, --show-failure Show failed command log from previous build and exit conjunction with the -T argument.
-h, --help Show rscons help and exit
-j N, --nthreads=N Set number of threads (local default: #{Rscons.application.n_threads}) -b BUILD, --build=BUILD
-r COLOR, --color=COLOR Set color mode (off, auto, force) Set build directory (default: build).
-T, --tasks Show task list and parameters and exit
-v, --verbose Run verbosely -e VS, --variants=VS
--version Show rscons version and exit Enable or disable variants. VS is a comma-separated list of variant
entries. If the entry begins with "-" the variant is disabled instead of
enabled. If the full list begins with "+" or "-" then it modifies the
variants that are enabled by default by only enabling or disabling the
listed variants. Otherwise, the enabled set of variants is as given and
any variants not listed are disabled. The set of enabled variants is
remembered from when the project is configured.
-f FILE
Use FILE as Rsconscript.
-F, --show-failure
Show failed command log from previous build and exit (does not load build
script).
-h, --help
Show rscons help and exit (does not load build script).
-j N, --nthreads=N
Set number of threads (local default: #{Rscons.application.n_threads}).
-r COLOR, --color=COLOR
Set color mode (off, auto, force).
-T, --tasks
Show task list and parameters and exit (loads build script). By default
only tasks with a description are listed. Use -AT to show all tasks whether
they have a description or not.
-v, --verbose
Run verbosely. This causes Rscons to print the full build command used by
each builder.
--version
Show rscons version and exit (does not load build script).
EOF EOF
end end

View File

@ -102,7 +102,6 @@ module Rscons
end.compact end.compact
@name = [base_name, *variant_keys].join("-") @name = [base_name, *variant_keys].join("-")
options = args.first || {} options = args.first || {}
Rscons.application.check_configure
unless Cache.instance["configuration_data"]["configured"] unless Cache.instance["configuration_data"]["configured"]
raise "Project must be configured before creating an Environment" raise "Project must be configured before creating an Environment"
end end

View File

@ -10,6 +10,14 @@ module Rscons
@script = script @script = script
end end
# Get the Rscons build directory path.
#
# @return [String]
# Rscons build directory path.
def build_dir
Rscons.application.build_dir
end
# Return a list of paths matching the specified pattern(s). # Return a list of paths matching the specified pattern(s).
# #
# A pattern can contain a "/**" component to recurse through directories. # A pattern can contain a "/**" component to recurse through directories.
@ -95,6 +103,7 @@ module Rscons
# Create an environment. # Create an environment.
def env(*args, &block) def env(*args, &block)
Rscons.application.check_configure
Environment.new(*args, &block) Environment.new(*args, &block)
end end
@ -250,6 +259,11 @@ module Rscons
Rscons.application.variant(*args) Rscons.application.variant(*args)
end end
# Check if a variant is enabled.
def variant_enabled?(*args)
Rscons.application.variant_enabled?(*args)
end
# Create a variant group. # Create a variant group.
def variant_group(*args, &block) def variant_group(*args, &block)
Rscons.application.variant_group(*args, &block) Rscons.application.variant_group(*args, &block)
@ -257,6 +271,7 @@ module Rscons
# Iterate through variants. # Iterate through variants.
def with_variants(&block) def with_variants(&block)
Rscons.application.enable_variants
Rscons.application.with_variants(&block) Rscons.application.with_variants(&block)
end end

View File

@ -2956,6 +2956,26 @@ EOF
expect(result.stdout).to_not match /^\s*one\b/ expect(result.stdout).to_not match /^\s*one\b/
expect(result.stdout).to_not match /^\s*two\b/ expect(result.stdout).to_not match /^\s*two\b/
end end
context "with -A flag" do
it "displays all tasks and their parameters" do
test_dir "tasks"
result = run_rscons(args: %w[-f tasks.rb -AT])
expect(result.stderr).to eq ""
expect(result.status).to eq 0
verify_lines(lines(result.stdout), [
"Tasks:",
/\bone\b/,
/\btwo\b/,
/\bthree\b\s+Task three/,
/\bfour\b\s+Task four/,
/--myparam=MYPARAM\s+My special parameter/,
/--myp2\s+My parameter 2/,
/\bfive\b/,
/\bsix\b/,
])
end
end
end end
context "download script method" do context "download script method" do
@ -3100,12 +3120,140 @@ EOF
expect(result.status).to_not eq 0 expect(result.status).to_not eq 0
end end
it "raises an error when with_variants is called with an empty variant group" do it "allows specifying the exact enabled variants on the command line 1" do
test_dir "variants" test_dir "variants"
result = run_rscons(args: %w[-f error_with_variants_with_empty_variant_group.rb]) result = run_rscons(args: %w[-v -f multiple_groups.rb -e kde,debug])
expect(result.stderr).to match %r{Error: empty variant group found} expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(File.exist?("build/prog-kde-debug/prog.exe")).to be_truthy
expect(File.exist?("build/prog-kde-release/prog.exe")).to be_falsey
expect(File.exist?("build/prog-gnome-debug/prog.exe")).to be_falsey
expect(File.exist?("build/prog-gnome-release/prog.exe")).to be_falsey
end
it "allows specifying the exact enabled variants on the command line 2" do
test_dir "variants"
result = run_rscons(args: %w[-v -f multiple_groups.rb -e kde,gnome,release])
expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(File.exist?("build/prog-kde-debug/prog.exe")).to be_falsey
expect(File.exist?("build/prog-kde-release/prog.exe")).to be_truthy
expect(File.exist?("build/prog-gnome-debug/prog.exe")).to be_falsey
expect(File.exist?("build/prog-gnome-release/prog.exe")).to be_truthy
end
it "allows disabling a single variant on the command line" do
test_dir "variants"
result = run_rscons(args: %w[-v -f multiple_groups.rb --variants=-kde])
expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(File.exist?("build/prog-kde-debug/prog.exe")).to be_falsey
expect(File.exist?("build/prog-kde-release/prog.exe")).to be_falsey
expect(File.exist?("build/prog-gnome-debug/prog.exe")).to be_truthy
expect(File.exist?("build/prog-gnome-release/prog.exe")).to be_truthy
end
it "allows turning off variants by default" do
test_dir "variants"
result = run_rscons(args: %w[-v -f default.rb])
expect(File.exist?("build/prog-debug/prog.exe")).to be_falsey
expect(File.exist?("build/prog-release/prog.exe")).to be_truthy
end
it "allows turning on an off-by-default-variant from the command line" do
test_dir "variants"
result = run_rscons(args: %w[-v -f default.rb -e +debug])
expect(File.exist?("build/prog-debug/prog.exe")).to be_truthy
expect(File.exist?("build/prog-release/prog.exe")).to be_truthy
end
it "allows only turning on an off-by-default-variant from the command line" do
test_dir "variants"
result = run_rscons(args: %w[-v -f default.rb -e debug])
expect(File.exist?("build/prog-debug/prog.exe")).to be_truthy
expect(File.exist?("build/prog-release/prog.exe")).to be_falsey
end
it "exits with an error if no variant in a variant group is activated" do
test_dir "variants"
result = run_rscons(args: %w[-v -f multiple_groups.rb --variants=kde])
expect(result.stderr).to match %r{No variants enabled for variant group}
expect(result.status).to_not eq 0 expect(result.status).to_not eq 0
end end
it "allows querying if a variant is enabled" do
test_dir "variants"
result = run_rscons(args: %w[-f variant_enabled.rb configure])
expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(result.stdout).to match %r{one enabled}
expect(result.stdout).to_not match %r{two enabled}
expect(result.stdout).to_not match %r{three enabled}
result = run_rscons(args: %w[-f variant_enabled.rb --variants=+two configure])
expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(result.stdout).to match %r{one enabled}
expect(result.stdout).to match %r{two enabled}
expect(result.stdout).to_not match %r{three enabled}
result = run_rscons(args: %w[-f variant_enabled.rb --variants=two configure])
expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(result.stdout).to_not match %r{one enabled}
expect(result.stdout).to match %r{two enabled}
expect(result.stdout).to_not match %r{three enabled}
end
it "shows available variants with -T" do
test_dir "variants"
result = run_rscons(args: %w[-f multiple_groups.rb -T])
expect(result.stderr).to eq ""
expect(result.status).to eq 0
verify_lines(lines(result.stdout), [
"Variant group 'desktop-environment':",
" kde (enabled)",
" gnome (enabled)",
"Variant group 'debug':",
" debug (enabled)",
" release (enabled)",
])
result = run_rscons(args: %w[-f multiple_groups.rb -e gnome,release configure])
expect(result.stderr).to eq ""
expect(result.status).to eq 0
result = run_rscons(args: %w[-f multiple_groups.rb -T])
expect(result.stderr).to eq ""
expect(result.status).to eq 0
verify_lines(lines(result.stdout), [
"Variant group 'desktop-environment':",
" kde",
" gnome (enabled)",
"Variant group 'debug':",
" debug",
" release (enabled)",
])
end
end
context "build_dir method" do
it "returns the top-level build directory path 1" do
test_dir "typical"
result = run_rscons(args: %w[-f build_dir.rb])
expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(File.exist?("build/a.file")).to be_truthy
end
it "returns the top-level build directory path 2" do
test_dir "typical"
result = run_rscons(args: %w[-f build_dir.rb -b bb])
expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(File.exist?("bb/a.file")).to be_truthy
end
end end
end end