Compare commits

..

9 Commits

22 changed files with 567 additions and 154 deletions

4
build_tests/sh/sh.rb Normal file
View File

@ -0,0 +1,4 @@
build do
sh "echo", "hi there"
sh(["echo 1 2"])
end

View File

@ -0,0 +1,4 @@
build do
sh "foobar42"
sh "echo", "continued"
end

View File

@ -0,0 +1,4 @@
build do
sh "foobar42", continue: true
sh "echo", "continued"
end

View File

@ -0,0 +1,6 @@
build do
Environment.new do |env|
env.Program("simple.exe", glob("*.c"))
env.Size("simple.size", "simple.exe")
end
end

View File

@ -0,0 +1,6 @@
build do
Environment.new(name: "typical") do |env|
env["CPPPATH"] += glob("src/**")
env.Program("^/typical.exe", glob("src/**/*.c"))
end
end

View File

@ -1,9 +1,10 @@
build do
Environment.new(echo: :command) do |env|
env.append('CPPPATH' => glob('src/**').sort)
env.append("CPPPATH" => glob("src/**"))
FileUtils.mkdir_p(env.build_root)
FileUtils.mv("src/one/one.c", env.build_root)
FileUtils.mv("src/two/two.c", Rscons.application.build_dir)
env.Object("^/one.o", "^/one.c")
env.Program("program.exe", glob('src/**/*.c') + ["^/one.o"])
env.Program("^^/program.exe", ["^/one.o", "^^/two.c"])
end
end

View File

@ -0,0 +1,9 @@
build do
base_env = Environment.new do |env|
env["CPPPATH"] += glob("src/**")
end
base_env.clone(name: "typical") do |env|
env.Program("^/typical.exe", glob("src/**/*.c"))
end
end

View File

@ -0,0 +1,11 @@
build do
mkdir "foo"
cd "foo" do
mkdir_p ["bar/baz", "bar/booz"]
end
mv "foo/bar", "foobar"
rmdir "foo"
touch "foobar/booz/a.txt"
cp "foobar/booz/a.txt", "foobar/baz/b.txt"
rm_rf "foobar/booz"
end

View File

@ -78,10 +78,22 @@ cache file in order to avoid rebuilding a target when it is already up to date.
### Build Directory
Rscons was designed to store temporary build artifacts (for example, object
files, dependency files, etc...) in a `build` directory.
files, dependency files, etc...) and build system metadata in a
"build directory".
This keeps files generated by the build cleanly separated from user-controlled
source files.
In contrast to other build systems or build system generators, rscons executes
from the project base directory (up to the user) rather than executing from
*within* the build directory.
This keeps any file paths printed by compilers (such as in warning or error
messages) accurate relative to the project directory, so that the user does not
need to translate any paths to the correct path within a terminal or editor
application, for example.
By default a build directory named "build" is used, but this can be overridden
by the user by using the `-b`/`--build` command-line option.
## Getting Started
To use Rscons on your project, you must:
@ -94,7 +106,7 @@ To use Rscons on your project, you must:
Rscons is designed to be distributed as a stand-alone single file script that
can be copied into and versioned in a project's source tree.
The only dependency required to run Rscons is to have a Ruby interpreter
The only requirement to run Rscons is that the system has a Ruby interpreter
installed.
The latest release can be downloaded from [https://github.com/holtrop/rscons/releases](https://github.com/holtrop/rscons/releases).
Simply copy the `rscons` executable script into the desired location within
@ -209,6 +221,171 @@ called `myprog.exe` which is to be built from all C source files found
The `Rsconscript` file is a Ruby script.
##> Build Script Methods
`rscons` provides several methods that a build script can use.
* `glob` (see ${#Finding Files: The glob Method})
* `path_append` (see ${#PATH Management})
* `path_components` (see ${#PATH Management})
* `path_prepend` (see ${#PATH Management})
* `path_set` (see ${#PATH Management})
* `rscons` (see ${#Using Subsidiary Build Scripts: The rscons Method})
* `sh` (see (${#Executing Commands: The sh Method})
Additionally, the following methods from the Ruby
[FileUtils](https://ruby-doc.org/stdlib-3.1.0/libdoc/fileutils/rdoc/FileUtils.html)
module are made available for the build script to call directly:
* `cd`
* `chmod`
* `chmod_R`
* `chown`
* `chown_R`
* `cp`
* `cp_lr`
* `cp_r`
* `install`
* `ln`
* `ln_s`
* `ln_sf`
* `mkdir`
* `mkdir_p`
* `mv`
* `pwd`
* `rm`
* `rm_f`
* `rm_r`
* `rm_rf`
* `rmdir`
* `touch`
###> Finding Files: The glob Method
The [`glob`](../yard/Rscons/Script/GlobalDsl.html#glob-instance_method) method can be
used to find files matching the patterns specified.
It supports a syntax similar to the Ruby [Dir.glob method](https://ruby-doc.org/core-3.1.0/Dir.html#method-c-glob) but operates more deterministically.
Example use:
```ruby
build do
Environment.new do |env|
env.Program("mytests", glob("src/**/*.cc", "test/**/*.cc"))
end
end
```
This example would build the `mytests` executable from all `.cc` source files
found recursively under the `src` or `test` directory.
###> PATH Management
`rscons` provides methods for management of the `PATH` environment variable.
The
[`path_append`](../yard/Rscons/Script/GlobalDsl.html#path_append-instance_method)
and
[`path_prepend`](../yard/Rscons/Script/GlobalDsl.html#path_prepend-instance_method)
methods can be used to append or prepend a path to the `PATH` environment
variable.
```ruby
path_prepend "i686-elf-gcc/bin"
```
The
[`path_set`](../yard/Rscons/Script/GlobalDsl.html#path_set-instance_method)
method sets the `PATH` environment variable to the given Array or String.
The
[`path_components`](../yard/Rscons/Script/GlobalDsl.html#path_components-instance_method)
method returns an Array of the components in the `PATH`
environment variable.
###> Using Subsidiary Build Scripts: The rscons Method
The
[`rscons`](../yard/Rscons/Script/GlobalDsl.html#rscons-instance_method)
build script method can be used to invoke an rscons subprocess to
perform an operation using a subsidiary rscons build script.
This can be used, for example, when a subproject is imported and a top-level
`configure` or `build` operation should also perform the same operation in the
subproject directory.
The first argument to the `rscons` method specifies either a directory name, or
the path to the subsidiary Rsconscript file to execute.
Any additional arguments are passed to `rscons` when it executes the subsidiary
script.
`rscons` will change working directories to the directory containing the
subsidiary script when executing it.
For example:
```ruby
configure do
rscons "subproject", "configure"
end
build do
rscons "subproject/Rsconscript", "build"
end
```
It is also perfectly valid to perform a different operation in the subsidiary
script from the one being performed in the top-level script.
For example, in a project that requires a particular cross compiler, the
top-level `configure` script could build the necessary cross compiler using a
subsidiary build script.
This could look something like:
```ruby
configure do
rscons "cross/Rsconscript"
check_c_compiler "i686-elf-gcc"
end
```
This would build, and if necessary first configure, using the cross/Rsconscript
subsidiary build script.
Subsidiary build scripts are executed from within the directory containing the
build script.
###> Executing Commands: The sh Method
The
[`sh`](../yard/Rscons/Script/GlobalDsl.html#sh-instance_method)
build script method can be used to directly execute commands.
The `sh` method accepts either a single String argument or an Array of Strings.
When an Array is given, if the array length is greater than 1, then the command
will not be executed and interpreted by the system shell.
Otherwise, it will be executed and interpreted by the system shell.
For example:
```ruby
build do
# Run "make" in imported "subcomponent" directory.
sh "cd subcomponent; make"
# Move a file around.
sh "mv", "subcomponent/file with spaces.txt", "new_name.txt"
end
```
If the command fails, rscons will normally print the error and terminate
execution.
If the `:continue` option is set, then rscons will not terminate execution.
For example:
```ruby
build do
# This command will fail and a message will be printed.
sh "false", continue: true
# However, due to the :continue option being set, execution will continue.
sh "echo hi"
end
```
##> Configuration Operations
A `configure` block is optional.
@ -492,21 +669,26 @@ source files found recursively under the `src` directory.
An Environment includes:
- a name
- a collection of construction variables
- a collection of build hooks
- a collection of user-registered build targets
- a build root
All build targets must be registered within an `Environment`.
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
"e.1".
The Environment's build root is a directory 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
to produce a user-specified build target.
For example, for the `Rsconscript`:
```ruby
build do
Environment.new do |env|
Environment.new(name: "myproj") do |env|
env.Program("myprog.exe", glob("src/**/*.c"))
end
end
@ -514,28 +696,11 @@ end
Rscons will place an object file and dependency file corresponding to each C
source file under the Environment's build root.
Assuming a top-level build directory of "build", the Environment's build root
would be "build/myproj".
This keeps the intermediate generated build artifacts separate from the source
files.
###> Specifying Source Files: The glob Method
The [`glob`](../yard/Rscons/Script/Dsl.html#glob-instance_method) method can be
used to find files matching the patterns specified.
It supports a syntax similar to the Ruby [Dir.glob method](https://ruby-doc.org/core-2.5.1/Dir.html#method-c-glob) but operates more deterministically.
Example use:
```ruby
build do
Environment.new do |env|
env.Program("mytests", glob("src/**/*.cc", "test/**/*.cc"))
end
end
```
This example would build the `mytests` executable from all `.cc` source files
found recursively under the `src` or `test` directory.
###> Construction Variables
Construction variables are values assigned to keys within an Environment.
@ -606,6 +771,7 @@ There are several default builders that are built-in to Rscons:
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.
* `Size`, which runs the 'size' utility on an executable file.
####> The Command Builder
@ -821,6 +987,20 @@ 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.
####> The Size Builder
```ruby
env.Size(target, sources)
# Example
env.Program("program.exe", glob("*.c"))
env.Size("program.size", "program.exe")
```
The `Size` builder runs the "size" executable on the given source file and
stores its output in the target file.
The size executable can be specified with the `SIZE` construction variable,
and flags can be specified with `SIZEFLAGS`.
###> Phony Targets
rscons supports phony build targets.
@ -900,70 +1080,6 @@ In other words, build targets are not parallelized across a barrier.
env.barrier
```
##> Global Build Script Functionality
###> Using Subsidiary Build Scripts
The `rscons` build script method can be used to invoke an rscons subprocess to
perform an operation using a subsidiary rscons build script.
This can be used, for example, when a subproject is imported and a top-level
`configure` or `build` operation should also perform the same operation in the
subproject directory.
The first argument to the `rscons` method specifies either a directory name, or
the path to the subsidiary Rsconscript file to execute.
Any additional arguments are passed to `rscons` when it executes the subsidiary
script.
`rscons` will change working directories to the directory containing the
subsidiary script when executing it.
For example:
```ruby
configure do
rscons "subproject", "configure"
end
build do
rscons "subproject/Rsconscript", "build"
end
```
It is also perfectly valid to perform a different operation in the subsidiary
script from the one being performed in the top-level script.
For example, in a project that requires a particular cross compiler, the
top-level `configure` script could build the necessary cross compiler using a
subsidiary build script.
This could look something like:
```ruby
configure do
rscons "cross/Rsconscript"
check_c_compiler "i686-elf-gcc"
end
```
This would build, and if necessary first configure, using the cross/Rsconscript
subsidiary build script.
Subsidiary build scripts are executed from within the directory containing the
build script.
###> PATH Management
`rscons` provides methods for management of the `PATH` environment variable.
The `path_append` and `path_prepend` methods can be used to append or prepend
a path to the `PATH` environment variable.
```ruby
path_prepend "i686-elf-gcc/bin"
```
The `path_set` method sets the `PATH` environment variable to the given
Array or String.
The `path_components` method returns an Array of the components in the `PATH`
environment variable.
##> Extending Rscons
### Adding New Languages

View File

@ -33,6 +33,7 @@ module Rscons
:Program,
:SharedLibrary,
:SharedObject,
:Size,
]
# Class to represent a fatal error during an Rscons operation.
@ -146,6 +147,7 @@ require_relative "rscons/builders/program"
require_relative "rscons/builders/shared_library"
require_relative "rscons/builders/shared_object"
require_relative "rscons/builders/simple_builder"
require_relative "rscons/builders/size"
# language support
require_relative "rscons/builders/lang/asm"

View File

@ -5,6 +5,10 @@ module Rscons
# Functionality for an instance of the rscons application invocation.
class Application
# @return [String]
# Top-level build directory.
attr_accessor :build_dir
# @return [Boolean]
# Whether to output ANSI color escape sequences.
attr_accessor :do_ansi_color
@ -23,6 +27,8 @@ module Rscons
# Create Application instance.
def initialize
@build_dir = ENV["RSCONS_BUILD_DIR"] || "build"
ENV.delete("RSCONS_BUILD_DIR")
@n_threads = Util.determine_n_threads
@vars = VarSet.new
@operations = Set.new
@ -171,11 +177,8 @@ module Rscons
# Exit code.
def distclean
cache = Cache.instance
build_dir = cache["configuration_data"]["build_dir"]
clean
if build_dir
FileUtils.rm_rf(build_dir)
end
FileUtils.rm_rf(@build_dir)
cache.clear
0
end

View File

@ -0,0 +1,24 @@
module Rscons
module Builders
# Run the "size" utility on an executable and store its results in the
# target file.
# input file.
#
# Examples::
# env.Size("^/project.size", "^/project.elf")
class Size < Builder
# Run the builder to produce a build target.
def run(options)
if @command
finalize_command
else
@vars["_SOURCES"] = @sources
command = @env.build_command("${SIZECMD}", @vars)
standard_command("Size <source>#{Util.short_format_paths(@sources)}<reset> => <target>#{@target}<reset>", command, stdout: @target)
end
end
end
end
end

View File

@ -51,9 +51,6 @@ module Rscons
# }
class Cache
# Name of the file to store cache information in
CACHE_FILE = ".rsconscache"
# Prefix for phony cache entries.
PHONY_PREFIX = ":PHONY:"
@ -70,6 +67,11 @@ module Rscons
initialize!
end
# Get the path to the cache file.
def cache_file
File.join(Rscons.application.build_dir, ".rsconscache")
end
# Access cache value.
def [](key)
@cache[key]
@ -84,7 +86,7 @@ module Rscons
#
# @return [void]
def clear
FileUtils.rm_f(CACHE_FILE)
FileUtils.rm_f(cache_file)
initialize!
end
@ -100,7 +102,7 @@ module Rscons
# @return [void]
def write
@cache["version"] = VERSION
File.open(CACHE_FILE, "w") do |fh|
File.open(cache_file, "w") do |fh|
fh.puts(JSON.dump(@cache))
end
end
@ -360,9 +362,9 @@ module Rscons
# Create a Cache object and load in the previous contents from the cache
# file.
def initialize!
@cache = JSON.load(File.read(CACHE_FILE)) rescue {}
@cache = JSON.load(File.read(cache_file)) rescue {}
unless @cache.is_a?(Hash)
$stderr.puts "Warning: #{CACHE_FILE} was corrupt. Contents:\n#{@cache.inspect}"
$stderr.puts "Warning: #{cache_file} was corrupt. Contents:\n#{@cache.inspect}"
@cache = {}
end
@cache["targets"] ||= {}

View File

@ -6,6 +6,7 @@ USAGE = <<EOF
Usage: #{$0} [global options] [operation] [operation options]
Global options:
-b BUILD, --build=BUILD Set build directory (default: build)
-f FILE Use FILE as Rsconscript
-F, --show-failure Show failed command log from previous build and exit
-h, --help Show rscons help and exit
@ -23,7 +24,6 @@ Operations:
uninstall Uninstall project from installation destination
Configure options:
-b BUILD, --build=BUILD Set build directory (default: build)
--prefix=PREFIX Set installation prefix (default: /usr/local)
EOF
@ -56,6 +56,10 @@ module Rscons
private
def add_global_options(opts)
opts.on("-b", "--build DIR") do |build_dir|
Rscons.application.build_dir = build_dir
end
opts.on("-j NTHREADS") do |n_threads|
Rscons.application.n_threads = n_threads.to_i
end
@ -156,10 +160,6 @@ module Rscons
end
def parse_configure_args(opts, argv, options)
opts.on("-b", "--build DIR") do |build_dir|
options[:build_dir] = build_dir
end
opts.on("--prefix PREFIX") do |prefix|
options[:prefix] = prefix
end

View File

@ -9,30 +9,25 @@ module Rscons
#
# @param options [Hash]
# Optional parameters.
# @option options [String] :build_dir
# Build directory.
# @option options [String] :prefix
# Install prefix.
# @option options [String] :project_name
# Project name.
def initialize(options)
# Default options.
options[:build_dir] ||= "build"
options[:prefix] ||= "/usr/local"
@work_dir = "#{options[:build_dir]}/configure"
@work_dir = "#{Rscons.application.build_dir}/_configure"
FileUtils.mkdir_p(@work_dir)
@log_fh = File.open("#{@work_dir}/config.log", "wb")
cache = Cache.instance
cache["failed_commands"] = []
cache["configuration_data"] = {}
cache["configuration_data"]["build_dir"] = options[:build_dir]
cache["configuration_data"]["prefix"] = options[:prefix]
if project_name = options[:project_name]
Ansi.write($stdout, "Configuring ", :cyan, project_name, :reset, "...\n")
else
$stdout.puts "Configuring project..."
end
Ansi.write($stdout, "Setting build directory... ", :green, options[:build_dir], :reset, "\n")
Ansi.write($stdout, "Setting prefix... ", :green, options[:prefix], :reset, "\n")
store_merge("prefix" => options[:prefix])
end

View File

@ -80,6 +80,9 @@ module Rscons
"SHLIBLINKPREFIX" => "-l",
"SHLIBPREFIX" => on_windows ? "" : "lib",
"SHLIBSUFFIX" => on_windows ? ".dll" : ".so",
"SIZE" => "size",
"SIZECMD" => %w[${SIZE} ${SIZEFLAGS} ${_SOURCES}],
"SIZEFLAGS" => [],
"YACC" => "bison",
"YACCSUFFIX" => %w[.y .yy],
"YACC_CMD" => %w[${YACC} ${YACC_FLAGS} -o ${_TARGET} ${_SOURCES}],

View File

@ -51,6 +51,10 @@ module Rscons
# global Rscons.application.n_threads value.
attr_accessor :n_threads
# @return [String]
# Environment name.
attr_reader :name
# Create an Environment object.
#
# @param options [Hash]
@ -58,6 +62,9 @@ module Rscons
# :command, :short, or :off (default :short)
# @option options [Boolean] :exclude_builders
# Whether to omit adding default builders (default false)
# @option options [String, nil] :name
# Environment name. This determines the folder name used to store all
# environment build files under the top-level build directory.
# @option options [String, Array<String>] :use
# Use flag(s). If specified, any configuration flags which were saved
# with a corresponding `:use` value will be applied to this Environment.
@ -97,7 +104,8 @@ module Rscons
else
:short
end
@build_root = "#{Cache.instance["configuration_data"]["build_dir"]}/e.#{@id}"
@name = options[:name] || "e.#{@id}"
@build_root = "#{Rscons.application.build_dir}/#{@name}"
@n_threads = Rscons.application.n_threads
if block_given?
@ -125,14 +133,14 @@ module Rscons
#
# @return [Environment] The newly created {Environment} object.
def clone(options = {})
options = options.dup
clone = options[:clone] || :all
clone = Set[:variables, :builders, :build_hooks] if clone == :all
clone = Set[] if clone == :none
clone = Set.new(clone) if clone.is_a?(Array)
clone.delete(:builders) if options[:exclude_builders]
env = self.class.new(
echo: options[:echo] || @echo,
exclude_builders: true)
options[:echo] ||= @echo
env = self.class.new(options.merge(exclude_builders: true))
if clone.include?(:builders)
@builders.each do |builder_name, builder|
env.add_builder(builder)
@ -467,10 +475,12 @@ module Rscons
output_fname
end
# Expand a path to be relative to the Environment's build root.
# Expand paths.
#
# Paths beginning with "^/" are expanded by replacing "^" with the
# Environment's build root.
# Environment's build root (e.g. "build/envname").
# Paths beginning with "^^/" are expanded by replacing "^^" with the
# top-level build directory (e.g. "build").
#
# @param path [String, Array<String>]
# The path(s) to expand.
@ -485,7 +495,7 @@ module Rscons
expand_path(path)
end
else
path.sub(%r{^\^(?=[\\/])}, @build_root).gsub("\\", "/")
path.sub(%r{^\^\^(?=[\\/])}, Rscons.application.build_dir).sub(%r{^\^(?=[\\/])}, @build_root).gsub("\\", "/")
end
end

View File

@ -6,6 +6,33 @@ module Rscons
# Global DSL methods.
class GlobalDsl
# Return a list of paths matching the specified pattern(s).
#
# A pattern can contain a "/**" component to recurse through directories.
# If the pattern ends with "/**" then only the recursive list of
# directories will be returned.
#
# Examples:
# - "src/**": return all directories under "src", recursively (including
# "src" itself).
# - "src/**/*": return all files and directories recursively under the src
# directory.
# - "src/**/*.c": return all .c files recursively under the src directory.
# - "dir/*/": return all directories in dir, but no files.
#
# @return [Array<String>] Paths matching the specified pattern(s).
def glob(*patterns)
require "pathname"
patterns.reduce([]) do |result, pattern|
if pattern.end_with?("/**")
pattern += "/"
end
result += Dir.glob(pattern).map do |path|
Pathname.new(path.gsub("\\", "/")).cleanpath.to_s
end
end.sort
end
# Return path components from the PATH variable.
#
# @return [Array<String>]
@ -80,6 +107,87 @@ module Rscons
end
end
# Execute a shell command, exiting on failure.
# The behavior to exit on failure is suppressed if the +:continue+
# option is given.
#
# @overload sh(command, options = {})
# @param command [String, Array<String>]
# Command to execute. The command is executed and interpreted by the
# system shell when given as a single string. It is not passed to the
# system shell if the array size is greater than 1.
# @param options [Hash]
# Options.
# @option options [Boolean] :continue
# If set to +true+, rscons will continue executing afterward, even if
# the command fails.
#
# @overload sh(*command, options = {})
# @param command [String, Array<String>]
# Command to execute. The command is executed and interpreted by the
# system shell when given as a single string. It is not passed to the
# system shell if the array size is greater than 1.
# @param options [Hash]
# Options.
# @option options [Boolean] :continue
# If set to +true+, rscons will continue executing afterward, even if
# the command fails.
def sh(*command)
options = {}
if command.last.is_a?(Hash)
options = command.slice!(-1)
end
if command.size == 1 && command[0].is_a?(Array)
command = command[0]
end
if Rscons.application.verbose
if command.size > 1
puts Util.command_to_s(command)
else
puts command[0]
end
end
begin
system(*command, exception: true)
rescue StandardError => e
message = "#{e.backtrace[2]}: #{e.message}"
if options[:continue]
Ansi.write($stderr, :red, message, :reset, "\n")
else
raise RsconsError.new(message)
end
end
end
[
:cd,
:chmod,
:chmod_R,
:chown,
:chown_R,
:cp,
:cp_lr,
:cp_r,
:install,
:ln,
:ln_s,
:ln_sf,
:mkdir,
:mkdir_p,
:mv,
:pwd,
:rm,
:rm_f,
:rm_r,
:rm_rf,
:rmdir,
:touch,
].each do |method|
define_method(method) do |*args, **kwargs, &block|
FileUtils.__send__(method, *args, **kwargs, &block)
end
end
end
# Top-level DSL available to the Rsconscript.
@ -108,33 +216,6 @@ module Rscons
def configure(&block)
@script.operations["configure"] = block
end
# Return a list of paths matching the specified pattern(s).
#
# A pattern can contain a "/**" component to recurse through directories.
# If the pattern ends with "/**" then only the recursive list of
# directories will be returned.
#
# Examples:
# - "src/**": return all directories under "src", recursively (including
# "src" itself).
# - "src/**/*": return all files and directories recursively under the src
# directory.
# - "src/**/*.c": return all .c files recursively under the src directory.
# - "dir/*/": return all directories in dir, but no files.
#
# @return [Array<String>] Paths matching the specified pattern(s).
def glob(*patterns)
require "pathname"
patterns.reduce([]) do |result, pattern|
if pattern.end_with?("/**")
pattern += "/"
end
result += Dir.glob(pattern).map do |path|
Pathname.new(path.gsub("\\", "/")).cleanpath.to_s
end
end.sort
end
end
# DSL available to the 'configure' block.

View File

@ -48,7 +48,7 @@ module Rscons
# @return [String]
# The string representation of the command.
def command_to_s(command)
command.map { |c| c =~ /\s/ ? "'#{c}'" : c }.join(' ')
command.map { |c| c[" "] ? "'#{c.gsub("'", "'\\\\''")}'" : c }.join(" ")
end
# Determine the number of threads to use by default.

View File

@ -1,4 +1,4 @@
module Rscons
# Project version.
VERSION = "2.2.0"
VERSION = "2.3.0"
end

View File

@ -66,6 +66,7 @@ compressed_script = Zlib::Deflate.deflate(stripped.join)
encoded_compressed_script = Base64.encode64(compressed_script).gsub("\n", "")
hash = Digest::MD5.hexdigest(encoded_compressed_script)
FileUtils.rm_rf(DIST)
FileUtils.mkdir_p(DIST)
File.open("#{DIST}/#{PROG_NAME}", "wb", 0755) do |fh|
fh.write(<<EOF)

View File

@ -208,6 +208,23 @@ EOF
expect(nr(`./simple.exe`)).to eq "This is a simple C program\n"
end
it "uses the build directory specified with -b" do
test_dir("simple")
result = run_rscons(rscons_args: %w[-b b])
expect(result.stderr).to eq ""
expect(Dir.exist?("build")).to be_falsey
expect(File.exists?("b/e.1/simple.c.o")).to be_truthy
end
it "uses the build directory specified by an environment variable" do
test_dir("simple")
passenv["RSCONS_BUILD_DIR"] = "b2"
result = run_rscons
expect(result.stderr).to eq ""
expect(Dir.exist?("build")).to be_falsey
expect(File.exists?("b2/e.1/simple.c.o")).to be_truthy
end
it "allows specifying a Builder object as the source to another build target" do
test_dir("simple")
result = run_rscons(rsconscript: "builder_as_source.rb")
@ -321,14 +338,14 @@ EOF
]
end
it "expands target and source paths starting with ^/ to be relative to the build root" do
it "expands target and source paths starting with ^/ and ^^/" do
test_dir("typical")
result = run_rscons(rsconscript: "carat.rb")
result = run_rscons(rsconscript: "carat.rb", rscons_args: %w[-b bld])
expect(result.stderr).to eq ""
verify_lines(lines(result.stdout), [
%r{gcc -c -o build/e.1/one.o -MMD -MF build/e.1/one.o.mf -Isrc -Isrc/one -Isrc/two build/e.1/one.c},
%r{gcc -c -o build/e.1/src/two/two.c.o -MMD -MF build/e.1/src/two/two.c.o.mf -Isrc -Isrc/one -Isrc/two src/two/two.c},
%r{gcc -o program.exe build/e.1/src/two/two.c.o build/e.1/one.o},
%r{gcc -c -o bld/e.1/one.o -MMD -MF bld/e.1/one.o.mf -Isrc -Isrc/one -Isrc/two bld/e.1/one.c},
%r{gcc -c -o bld/e.1/bld/two.c.o -MMD -MF bld/e.1/bld/two.c.o.mf -Isrc -Isrc/one -Isrc/two bld/two.c},
%r{gcc -o bld/program.exe bld/e.1/one.o bld/e.1/bld/two.c.o},
])
end
@ -1076,6 +1093,23 @@ EOF
expect(result.status).to eq 0
end
it "stores build artifacts in a directory according to Environment name" do
test_dir "typical"
result = run_rscons
expect(File.exist?("build/typical/typical.exe")).to be_truthy
expect(File.exist?("build/typical/src/one/one.c.o")).to be_truthy
end
it "names Environment during clone" do
test_dir "typical"
result = run_rscons(rsconscript: "clone_and_name.rb")
expect(File.exist?("build/typical/typical.exe")).to be_truthy
expect(File.exist?("build/typical/src/one/one.c.o")).to be_truthy
expect(Dir.exist?("build/e.1")).to be_falsey
end
context "colored output" do
it "does not output in color with --color=off" do
test_dir("simple")
@ -1296,7 +1330,8 @@ EOF
context "Cache management" do
it "prints a warning when the cache is corrupt" do
test_dir("simple")
File.open(Rscons::Cache::CACHE_FILE, "w") do |fh|
FileUtils.mkdir("build")
File.open("build/.rsconscache", "w") do |fh|
fh.puts("[1]")
end
result = run_rscons
@ -1645,6 +1680,21 @@ EOF
end
end
context "Size builder" do
it "generates a size file" do
test_dir "simple"
result = run_rscons(rsconscript: "size.rb")
verify_lines(lines(result.stdout), [
/Linking .*simple\.exe/,
/Size .*simple\.exe .*simple\.size/,
])
expect(File.exist?("simple.exe")).to be_truthy
expect(File.exist?("simple.size")).to be_truthy
expect(File.read("simple.size")).to match /text.*data.*bss/
end
end
context "multi-threading" do
it "waits for subcommands in threads for builders that support threaded commands" do
test_dir("simple")
@ -1748,6 +1798,22 @@ EOF
expect(result.status).to_not eq 0
end
it "automatically runs the configure operation if the project is not yet configured in the given build directory" do
test_dir "configure"
result = run_rscons(rsconscript: "check_c_compiler.rb")
expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(result.stdout).to match /Checking for C compiler\.\.\./
expect(Dir.exist?("build/_configure")).to be_truthy
result = run_rscons(rsconscript: "check_c_compiler.rb", rscons_args: %w[--build=bb])
expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(result.stdout).to match /Checking for C compiler\.\.\./
expect(Dir.exist?("bb/_configure")).to be_truthy
end
context "check_c_compiler" do
{"check_c_compiler.rb" => "when no arguments are given",
"check_c_compiler_find_first.rb" => "when arguments are given"}.each_pair do |rsconscript, desc|
@ -2284,7 +2350,6 @@ EOF
expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(result.stdout).to match /Configuring configure test\.\.\./
expect(result.stdout).to match /Setting build directory\.\.\. bb/
expect(result.stdout).to match %r{Setting prefix\.\.\. /my/prefix}
expect(result.stdout).to match /Checking for C compiler\.\.\. gcc/
expect(result.stdout).to match /Checking for C\+\+ compiler\.\.\. g\+\+/
@ -2295,6 +2360,8 @@ EOF
expect(result.stdout).to match /Checking for D import 'std.stdio'\.\.\. found/
expect(result.stdout).to match /Checking for library 'm'\.\.\. found/
expect(result.stdout).to match /Checking for program 'ls'\.\.\. .*ls/
expect(Dir.exist?("build")).to be_falsey
expect(Dir.exist?("bb/_configure")).to be_truthy
end
it "aggregates multiple set_define's" do
@ -2357,7 +2424,6 @@ EOF
expect(result.status).to eq 0
expect(File.exists?("simple.o")).to be_falsey
expect(File.exists?("build")).to be_falsey
expect(File.exists?(Rscons::Cache::CACHE_FILE)).to be_falsey
end
end
@ -2742,6 +2808,71 @@ EOF
expect(result.status).to_not eq 0
expect(result.stdout).to_not match /top configure/
end
it "does not pass RSCONS_BUILD_DIR to subsidiary scripts" do
test_dir "subsidiary"
passenv["RSCONS_BUILD_DIR"] = "buildit"
result = run_rscons(op: %W[configure])
expect(result.stderr).to eq ""
expect(Dir.exist?("build")).to be_falsey
expect(Dir.exist?("buildit")).to be_truthy
expect(Dir.exist?("sub/build")).to be_truthy
expect(Dir.exist?("sub/buildit")).to be_falsey
end
end
context "sh method" do
it "executes the command given" do
test_dir "sh"
result = run_rscons(rsconscript: "sh.rb")
expect(result.stderr).to eq ""
expect(result.status).to eq 0
verify_lines(lines(result.stdout), [
"hi there",
"1 2",
])
end
it "prints the command when executing verbosely" do
test_dir "sh"
result = run_rscons(rsconscript: "sh.rb", rscons_args: %w[-v])
expect(result.stderr).to eq ""
expect(result.status).to eq 0
verify_lines(lines(result.stdout), [
%r{echo 'hi there'},
"hi there",
%r{echo 1 2},
"1 2",
])
end
it "terminates execution on failure" do
test_dir "sh"
result = run_rscons(rsconscript: "sh_fail.rb")
expect(result.stderr).to match /sh_fail\.rb:2:.*foobar42/
expect(result.status).to_not eq 0
expect(result.stdout).to_not match /continued/
end
it "continues execution on failure when :continue option is set" do
test_dir "sh"
result = run_rscons(rsconscript: "sh_fail_continue.rb")
expect(result.stderr).to match /sh_fail_continue\.rb:2:.*foobar42/
expect(result.status).to eq 0
expect(result.stdout).to match /continued/
end
end
context "FileUtils methods" do
it "defines FileUtils methods to be available in the build script" do
test_dir "typical"
result = run_rscons(rsconscript: "fileutils_methods.rb")
expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(Dir.exist?("foobar")).to be_truthy
expect(Dir.exist?("foo")).to be_falsey
expect(File.exist?("foobar/baz/b.txt")).to be_truthy
end
end
end