Compare commits

...
27 Commits
Author SHA1 Message Date
josh 663f4ac04c check_program: do not fail on Windows for non-existent PATH entries 2019-08-27 16:14:19 -04:00
josh 2c7cc61b30 build tests: use -m instead of -lc for mingw 2019-08-27 12:32:36 -04:00
josh 7c4abe9343 build tests: do ldc2 tests even if omit_gdc_tests set 2019-08-27 08:51:56 -04:00
josh 1a4f1a3608 update build test for gdc fix 2019-08-27 08:50:01 -04:00
josh 044ee343ca Use OBJSUFFIX of .obj for ldc2 on Windows. 2019-08-26 19:10:19 -04:00
josh eb54a2b64a fix specs create_exe() on Windows 2019-08-26 18:51:03 -04:00
josh 2dd1ce9f1d Use base64 encoding for compressed script to avoid encoding issues. 2019-08-26 18:33:42 -04:00
josh 1e157c79ec fix dependency parsing with ldc2 - close #110 2019-08-19 22:56:54 -04:00
josh 2385874f4a v2.0.0beta2 2019-08-17 17:07:59 -04:00
josh c5c42e6f4a user guide: document a couple missing configure flags and link to YARD some places 2019-08-17 17:00:36 -04:00
josh 9a627b2150 add YARD comments to get to 100% YARD coverage 2019-08-17 16:42:14 -04:00
josh 937c964c3e document :use better 2019-08-17 16:35:06 -04:00
josh a2a1d08014 user guide: document Custom Configuration Checks 2019-08-17 16:35:06 -04:00
josh 0ccf43cc96 custom config checks - close #108 2019-08-17 16:06:37 -04:00
josh d202e15a57 user guide: document direct mode for Program/SharedLibrary builders 2019-07-29 21:57:38 -04:00
josh 94f808b1fc user guide: add Barriers section 2019-07-29 21:32:04 -04:00
josh 51a9dd365f add :check_libpath option to check_lib configuration method - #107 2019-07-29 21:00:52 -04:00
josh 6e590b62a6 add :check_cpppath option to check_c_header and check_cxx_header
configuration methods
add :check_d_import_path option to check_d_import configuration method - #107
2019-07-09 23:53:17 -04:00
josh 3aac190ad6 configure checks: compile instead of link when looking for C/C++ header files and D imports 2019-07-09 23:41:51 -04:00
josh d6b9487e65 user guide: document -F command-line flag 2019-07-09 23:06:45 -04:00
josh 66d2700faa clarify -F command-line option usage more 2019-07-09 23:06:45 -04:00
josh ba98af1424 default configure checks :fail option to false if :set_define is specified 2019-07-09 22:00:50 -04:00
josh cf4f80c9ae user guide: document first compiler found is used 2019-07-09 21:47:45 -04:00
josh d74785d579 Add v2.0.0 changes to CHANGELOG.md 2019-07-07 22:15:44 -04:00
josh 4fd822fb95 user guide: add warning when viewing markdown source file directly 2019-07-07 21:38:17 -04:00
josh b39fd80d8c user guide: add ./configure && make Appendix section 2019-07-07 21:29:30 -04:00
josh ef00c9f377 add link to github.io site from README.md 2019-07-02 22:39:33 -04:00
31 changed files with 798 additions and 162 deletions

No files matched your search

+35
View File
@@ -1,3 +1,38 @@
## v2.0.0
- convert rscons from a Ruby gem to a standalone script
- compress rscons distributable script
- add configure operation to detect compilers, check for headers/libraries, etc... (invoked automatically if needed)
- Environments store builder classes instead of instances of builder classes
- use a separate Builder instance for each build operation
- load Rsconscript from Rsconscript/Rsconscript.rb instead of Rsconsfile
- drop support for builder run methods using the old 5 parameter signature
- remove Environment#build_dir
- set Environment build root in configure step
- remove Builder#finalize (now #run called repeatedly until builder completes)
- remove Builder#setup
- remove Builder#features and Builder#produces?
- add functionality to allow builders to wait on Ruby threads or other builders
- add install/uninstall/distclean command-line operations
- preserve makefile dependency files under build directory
- remove a few deprecated methods
- pass a Builder instance to build hooks instead of a build operation Hash
- support a basic markup syntax in builder run messages to colorize target/source files
- hide (but store) failed compilation command by default so the user doesn't have to scroll back as much to see compiler output
- refactor to remove some redundancy among built-in builders
- track object file source language (correctly determine linker when only passed object files previously built by particular toolchains)
- add barriers
- add InstallDirectory builder
- change Install builder to copy files on 'install' operation
- add "prefix" construction variable based on configured installation prefix
- allow passing builder objects as sources to build targets
- differentiate 'build' targets from 'install' targets in cache contents
- add verbose mode
- show build progress as a percentage in builder output messages
- various performance improvements
- wrote a new user guide
- added new website ([https://holtrop.github.io/rscons/](https://holtrop.github.io/rscons/))
## v1.17.0 ## v1.17.0
### New Features ### New Features
+2
View File
@@ -4,3 +4,5 @@ Rscons (https://github.com/holtrop/rscons) is an open-source build system
for developers. for developers.
Rscons is written in Ruby, and is inspired by [SCons](https://scons.org/) and [waf](https://waf.io/). Rscons is written in Ruby, and is inspired by [SCons](https://scons.org/) and [waf](https://waf.io/).
See [https://holtrop.github.io/rscons/index.html](https://holtrop.github.io/rscons/index.html) for User Guide and Installation instructions.
@@ -0,0 +1,10 @@
configure do
check_c_header "string.h", check_cpppath: ["./usr1"]
check_c_header "frobulous.h", check_cpppath: ["./usr2"]
end
build do
Environment.new do |env|
env.Object("test.o", "test.c")
end
end
@@ -1,5 +1,5 @@
configure do configure do
check_c_header "not___found.h", fail: false, set_define: "HAVE_NOT___FOUND_H" check_c_header "not___found.h", set_define: "HAVE_NOT___FOUND_H"
end end
build do build do
@@ -0,0 +1,10 @@
configure do
check_cxx_header "string.h", check_cpppath: ["./usr1"]
check_cxx_header "frobulous.h", check_cpppath: ["./usr2"]
end
build do
Environment.new do |env|
env.Object("test.o", "test.cc")
end
end
@@ -0,0 +1,10 @@
configure do
check_d_import "std.stdio", check_d_import_path: ["./usr1"]
check_d_import "frobulous", check_d_import_path: ["./usr2"]
end
build do
Environment.new do |env|
env.Object("test.o", "test.d")
end
end
@@ -0,0 +1,5 @@
build do
Environment.new(echo: :command) do |env|
env.Library("usr2/libfrobulous.a", "two.c")
end
end
@@ -0,0 +1,10 @@
configure do
check_lib "m", check_libpath: ["./usr1"]
check_lib "frobulous", check_libpath: ["./usr2"]
end
build do
Environment.new(echo: :command) do |env|
env.Program("simple.exe", "simple.c")
end
end
@@ -0,0 +1,26 @@
configure do
custom_check("Checking 'grep' version") do |op|
stdout, stderr, status = op.log_and_test_command(%w[grep --version])
should_fail = true
if status != 0
fail_message = "error executing grep"
elsif stdout =~ /^grep \(GNU grep\) 1\./
fail_message = "too old!"
status = 1
elsif stdout =~ /^grep \(GNU grep\) 2\./
fail_message = "we'll work with it but you should upgrade"
status = 1
should_fail = false
op.store_merge("CPPDEFINES" => "GREP_WORKAROUND")
else
op.store_append("CPPDEFINES" => "GREP_FULL")
end
op.complete(status, success_message: "good!", fail_message: fail_message, fail: should_fail)
end
end
build do
Environment.new do |env|
puts env["CPPDEFINES"]
end
end
+4
View File
@@ -0,0 +1,4 @@
int two(void)
{
return 42;
}
+4
View File
@@ -1,3 +1,7 @@
configure do
check_d_compiler "gdc"
end
build do build do
Environment.new(echo: :command) do |env| Environment.new(echo: :command) do |env|
env.Program("hello-d.exe", glob("*.d")) env.Program("hello-d.exe", glob("*.d"))
+9
View File
@@ -0,0 +1,9 @@
configure do
check_d_compiler "ldc2"
end
build do
Environment.new(echo: :command) do |env|
env.Program("hello-d.exe", glob("*.d"))
end
end
+1 -1
View File
@@ -1,6 +1,6 @@
build do build do
Environment.new do |env| Environment.new do |env|
env["LIBS"] += ["c"] env["LIBS"] += ["m"]
env.Program('simple.exe', Dir['*.c']) env.Program('simple.exe', Dir['*.c'])
end end
end end
+196 -5
View File
@@ -24,6 +24,15 @@ custom build operations as well.
Rscons is written in Ruby, and is inspired by [SCons](https://scons.org/) and [waf](https://waf.io/). Rscons is written in Ruby, and is inspired by [SCons](https://scons.org/) and [waf](https://waf.io/).
${remove}
WARNING: This user guide is meant to be preprocessed and rendered by a custom
script.
The markdown source file is not intended to be viewed directly and will not
include all intended content.
Visit [https://holtrop.github.io/rscons/index.html](https://holtrop.github.io/rscons/index.html)
to view the properly rendered version.
${/remove}
## Design Principles ## Design Principles
### Build Correctness ### Build Correctness
@@ -131,6 +140,7 @@ Such configuration checks can include:
* verifying presence of a D import * verifying presence of a D import
* verifying presence of a library * verifying presence of a library
* verifying presence of an executable * verifying presence of an executable
* any custom user-supplied configuration check
##> Build Operation ##> Build Operation
@@ -140,6 +150,17 @@ been performed, a `configure` operation will be automatically invoked.
The `build` operation will execute all builders registered to produce build The `build` operation will execute all builders registered to produce build
targets. targets.
If a `build` operation fails (e.g. due to a compilation failure), Rscons will
log the failed commands.
By default Rscons does not print the failed commands to the console so that it
is easier for the user to focus on the actual compiler failure messages rather
than the compilation command itself.
However, if the user wishes to see the compilation commands, rscons can be
invoked with the `-v` command-line option to show all complilation commands
while building, or, alternatively, following a compilation failure, the user
can invoke rscons with the -F option which will not rebuild but will show the
failed command log from the previous build operation.
##> Clean Operation ##> Clean Operation
A `clean` operation will remove all built target files. A `clean` operation will remove all built target files.
@@ -211,6 +232,7 @@ working compiler:
Each of these methods can take an optional list of compilers to check for. Each of these methods can take an optional list of compilers to check for.
If such a list is supplied, the compilers are tested in the order listed. If such a list is supplied, the compilers are tested in the order listed.
The first compiler option found which passes a compilation test is used.
Here are example calls which also show the default compiler list for each Here are example calls which also show the default compiler list for each
supported language: supported language:
@@ -237,7 +259,7 @@ Example calls:
```ruby ```ruby
configure do configure do
check_c_header "getopt.h", fail: false, set_define: "HAVE_GETOPT_H" check_c_header "getopt.h", set_define: "HAVE_GETOPT_H"
check_c_header "FreeType2.h" check_c_header "FreeType2.h"
check_cxx_header "memory" check_cxx_header "memory"
end end
@@ -245,10 +267,16 @@ end
#### Options #### Options
##### `:check_cpppath`
Optionally specifies an array of paths to look for the header file in.
##### `:fail` ##### `:fail`
If the `:fail` option is set to `false`, then the absence of the header file If the `:fail` option is set to `false`, then the absence of the header file
will not result in the configure option failing. will not result in the configure option failing.
The `:fail` option defaults to `true` if the `:set_define` option is not
defined, and defaults to `false` if the `:set_define` option is defined.
##### `:set_define` ##### `:set_define`
@@ -270,6 +298,12 @@ configure do
end end
``` ```
#### Options
##### `:check_d_import_path`
Optionally specifies an array of paths to look for the module in.
###> Checking for a Library ###> Checking for a Library
The `check_lib` method can be used to check for the presence of a library. The `check_lib` method can be used to check for the presence of a library.
@@ -288,16 +322,28 @@ end
#### Options #### Options
##### `:check_libpath`
Optionally specifies an array of paths to look for the library in.
##### `:fail` ##### `:fail`
If the `:fail` option is set to `false`, then the absence of the library If the `:fail` option is set to `false`, then the absence of the library
will not result in the configure option failing. will not result in the configure option failing.
The `:fail` option defaults to `true` if the `:set_define` option is not
defined, and defaults to `false` if the `:set_define` option is defined.
##### `:set_define` ##### `:set_define`
If set, a build define of the specified String will be added to the If set, a build define of the specified String will be added to the
`CPPDEFINES` construction variable array if the requested library is found. `CPPDEFINES` construction variable array if the requested library is found.
##### `:use`
If not set, the library will be used by default in all `Environment` objects.
If set, the library will only be used in `Environment` objects that have a
matching `:use` flag set.
###> Checking for a Program ###> Checking for a Program
The `check_program` method can check for the existence of an executable in the The `check_program` method can check for the existence of an executable in the
@@ -344,12 +390,72 @@ for configuration flags.
If the `:fail` option is set to `false`, then the absence of the package or If the `:fail` option is set to `false`, then the absence of the package or
program requested will not result in the configure option failing. program requested will not result in the configure option failing.
The `:fail` option defaults to `true` if the `:set_define` option is not
defined, and defaults to `false` if the `:set_define` option is defined.
##### `:set_define` ##### `:set_define`
If set, a build define of the specified String will be added to the If set, a build define of the specified String will be added to the
`CPPDEFINES` construction variable array if the requested package is found. `CPPDEFINES` construction variable array if the requested package is found.
##### `:use`
If not set, the library will be used by default in all `Environment` objects.
If set, the library will only be used in `Environment` objects that have a
matching `:use` flag set.
###> Custom Configuration Checks
The `Rsconscript` author can add custom configuration checks to be performed
during the rscons `configure` operation.
Here is an example from `build_tests/configure/custom_config_check.rb` showing
a custom configuration check:
```ruby
${include build_tests/configure/custom_config_check.rb}
```
A custom configuration check is created by calling the `custom_check` method
and passing a block.
The contents of the block should perform the custom configuration checking
logic.
This logic can include executing a test command or other arbitrary operations.
An argument `op` is passed to the block.
This object is an instance of the [`ConfigureOp` class](../yard/Rscons/ConfigureOp.html)
class and provides several methods that can be used to aid with the custom
configuration check.
The [`log_and_test_command`](../yard/Rscons/ConfigureOp.html#log_and_test_command-instance_method)
method can be used to execute a test command and retrieve its results.
The command and its output are also logged to the config.log file.
The [`store_merge`](../yard/Rscons/ConfigureOp.html#store_merge-instance_method),
[`store_append`](../yard/Rscons/ConfigureOp.html#store_append-instance_method),
and [`store_parse`](../yard/Rscons/ConfigureOp.html#store_parse-instance_method)
methods can be used to store construction variables for Environments created
during the `build` operation.
Finally, the [`complete`](../yard/Rscons/ConfigureOp.html#complete-instance_method)
method can be used to complete the configuration check and indicate a success
or failure.
While performing a custom configuration check, it can sometimes be useful to
be able to construct an Environment to use the set of default construction
variables as defined so far in the configuration block, for example to expand
construction variables to build a test command.
The normal `Environment` class cannot be used within the `configure` block,
however the [`BasicEnvironment`](../yard/Rscons/BasicEnvironment.html) class
can be used for such a purpose.
For example, to expand the current `${CCCMD}` value:
```ruby
configure do
custom_check("Checking something to do with CCCMD") do
command = BasicEnvironment.new.expand_varref("${CCCMD}")
# ...
end
end
```
##> Build Operations ##> Build Operations
The `build` block is used to create Environments and register build targets. The `build` block is used to create Environments and register build targets.
@@ -399,7 +505,8 @@ files.
###> Specifying Source Files: The glob Method ###> Specifying Source Files: The glob Method
The `glob` method can be used to find files matching the patterns specified. 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. 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: Example use:
@@ -622,6 +729,24 @@ A platform-dependent program suffix will be appended to the target name if one
is not specified. is not specified.
This can be controlled with the `PROGSUFFIX` construction variable. This can be controlled with the `PROGSUFFIX` construction variable.
##### Direct Mode
The Program builder supports a "direct" mode which is activated by specifying
the `:direct` option.
In the direct mode, all source files are passed directly to the compiler
together and compiled and linked in one step, rather than being individually
compiled to separate object files first.
This mode allows taking advantage of any multi-file compilation capabilities
of the compiler.
However, it also requires recompiling all source files when any one of them
has changed.
Example use:
```ruby
env.Program("myprog", Rscons.glob("src/**/*.c"), direct: true)
```
####> The SharedLibrary Builder ####> The SharedLibrary Builder
```ruby ```ruby
@@ -638,6 +763,24 @@ they are not specified by the user.
These values can be controlled by overriding the `SHLIBPREFIX` and These values can be controlled by overriding the `SHLIBPREFIX` and
`SHLIBSUFFIX` construction variables. `SHLIBSUFFIX` construction variables.
##### Direct Mode
The SharedLibrary builder supports a "direct" mode which is activated by
specifying the `:direct` option.
In the direct mode, all source files are passed directly to the compiler
together and compiled and linked in one step, rather than being individually
compiled to separate object files first.
This mode allows taking advantage of any multi-file compilation capabilities
of the compiler.
However, it also requires recompiling all source files when any one of them
has changed.
Example use:
```ruby
env.SharedLibrary("mydll", Rscons.glob("src/**/*.c"), direct: true)
```
####> The SharedObject Builder ####> The SharedObject Builder
```ruby ```ruby
@@ -703,6 +846,18 @@ Post-build hooks are only invoked if the build operation was a success.
Build hooks and post-build hooks can register new build targets. Build hooks and post-build hooks can register new build targets.
###> Barriers
Normally Rscons will parallelize all builders.
A barrier can be used to separate sets of build targets.
All build targets registered before the barrier is created will be built before
Rscons will schedule any build targets after the barrier.
In other words, build targets are not parallelized across a barrier.
```ruby
env.barrier
```
##> Extending Rscons ##> Extending Rscons
### Adding New Languages ### Adding New Languages
@@ -952,9 +1107,9 @@ the `Rscons::Command` class and then `wait_for` the Command object.
However, this is a common enough case that Rscons provides a few However, this is a common enough case that Rscons provides a few
convenience methods to handle this: convenience methods to handle this:
* `Rscons::Builder#register_command` * [`Rscons::Builder#register_command`](../yard/Rscons/Builder.html#register_command-instance_method)
* `Rscons::Builder#standard_command` * [`Rscons::Builder#standard_command`](../yard/Rscons/Builder.html#standard_command-instance_method)
* `Rscons::Builder#finalize_command` * [`Rscons::Builder#finalize_command`](../yard/Rscons/Builder.html#finalize_command-instance_method)
The `register_command` helper method can be used to create a Command object The `register_command` helper method can be used to create a Command object
and wait for it to complete. and wait for it to complete.
@@ -1104,6 +1259,42 @@ build do
end end
``` ```
##> ./configure && make
You can make your Rscons-based project more familiar to users of
autoconf-generated projects by creating a `configure` script and a `Makefile`
for the user.
Such users may be used to executing:
```
./configure
make
```
to build a project.
To do this, create a `configure` script with contents similar to the following:
```
#!/bin/sh
exec "$(dirname "$0")"/rscons "$@"
```
and make it executable with `chmod +x configure`.
If you want your users to be able to build/clean a project with `make` but
still make use of Rscons under the hood, you can create a `Makefile` with
contents something like this:
```
.PHONY: all
all:
./rscons build
.PHONY: clean
clean:
./rscons clean
```
##> YARD API Documentation ##> YARD API Documentation
See [here](../yard/index.html) for Rscons YARD API Documentation. See [here](../yard/index.html) for Rscons YARD API Documentation.
+1
View File
@@ -3,6 +3,7 @@ module Rscons
module Ansi module Ansi
class << self class << self
# ANSI terminal reset sequence.
RESET = "\e[0m" RESET = "\e[0m"
# Write a message to an IO with ANSI escape codes. # Write a message to an IO with ANSI escape codes.
+3 -2
View File
@@ -4,10 +4,11 @@ module Rscons
# Create a BasicEnvironment object. # Create a BasicEnvironment object.
# #
# @api private
#
# @param options [Hash] # @param options [Hash]
# Construction options. # Construction options.
# @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.
def initialize(options = {}) def initialize(options = {})
@varset = VarSet.new(Rscons::DEFAULT_CONSTRUCTION_VARIABLES) @varset = VarSet.new(Rscons::DEFAULT_CONSTRUCTION_VARIABLES)
load_configuration_data!(options) load_configuration_data!(options)
+1 -1
View File
@@ -9,7 +9,7 @@ module Rscons
def finalize_command_with_depfile def finalize_command_with_depfile
deps = @sources deps = @sources
if File.exists?(@vars["_DEPFILE"]) if File.exists?(@vars["_DEPFILE"])
deps += Util.parse_makefile_deps(@vars["_DEPFILE"]) deps += Util.parse_dependency_file(@vars["_DEPFILE"])
end end
@cache.register_build(@target, @command, deps.uniq, @env) @cache.register_build(@target, @command, deps.uniq, @env)
true true
+1
View File
@@ -8,6 +8,7 @@ module Rscons
module Builders module Builders
module Mixins module Mixins
# Mixin for builders needing to build object files.
module Object module Object
include Depfile include Depfile
+2
View File
@@ -8,6 +8,8 @@ module Rscons
include Mixins::Program include Mixins::Program
class << self class << self
# Custom new method which will delegate to the correct class depending
# on the options specified.
def new(options, *more) def new(options, *more)
unless File.basename(options[:target])["."] unless File.basename(options[:target])["."]
options[:target] += options[:env].expand_varref("${PROGSUFFIX}", options[:vars]) options[:target] += options[:env].expand_varref("${PROGSUFFIX}", options[:vars])
+2
View File
@@ -8,6 +8,8 @@ module Rscons
include Mixins::Program include Mixins::Program
class << self class << self
# Custom new method which will delegate to the correct class depending
# on the options specified.
def new(options, *more) def new(options, *more)
libprefix = options[:env].expand_varref("${SHLIBPREFIX}", options[:vars]) libprefix = options[:env].expand_varref("${SHLIBPREFIX}", options[:vars])
unless File.basename(options[:target]).start_with?(libprefix) unless File.basename(options[:target]).start_with?(libprefix)
+2
View File
@@ -7,6 +7,8 @@ module Rscons
include Mixins::Object include Mixins::Object
class << self class << self
# Content component to add to build path to separate objects built
# using this builder from others.
def extra_path def extra_path
"_shared" "_shared"
end end
+2 -1
View File
@@ -1,11 +1,12 @@
require "rscons" require "rscons"
require "optparse" require "optparse"
# CLI usage string.
USAGE = <<EOF USAGE = <<EOF
Usage: #{$0} [global options] [operation] [operation options] Usage: #{$0} [global options] [operation] [operation options]
Global options: Global options:
-F, --show-failure Show previous failed command(s) and exit -F, --show-failure Show failed command log from previous build and exit
--version Show rscons version and exit --version Show rscons version and exit
-h, --help Show rscons help and exit -h, --help Show rscons help and exit
-r COLOR, --color=COLOR Set color mode (off, auto, force) -r COLOR, --color=COLOR Set color mode (off, auto, force)
+195 -107
View File
@@ -69,7 +69,7 @@ module Rscons
cc = ccc.find do |cc| cc = ccc.find do |cc|
test_c_compiler(cc) test_c_compiler(cc)
end end
common_config_checks(cc ? 0 : 1, success_message: cc) complete(cc ? 0 : 1, success_message: cc)
end end
# Check for a working C++ compiler. # Check for a working C++ compiler.
@@ -87,7 +87,7 @@ module Rscons
cc = ccc.find do |cc| cc = ccc.find do |cc|
test_cxx_compiler(cc) test_cxx_compiler(cc)
end end
common_config_checks(cc ? 0 : 1, success_message: cc) complete(cc ? 0 : 1, success_message: cc)
end end
# Check for a working D compiler. # Check for a working D compiler.
@@ -105,7 +105,7 @@ module Rscons
dc = cdc.find do |dc| dc = cdc.find do |dc|
test_d_compiler(dc) test_d_compiler(dc)
end end
common_config_checks(dc ? 0 : 1, success_message: dc) complete(dc ? 0 : 1, success_message: dc)
end end
# Check for a package or configure program output. # Check for a package or configure program output.
@@ -122,11 +122,12 @@ module Rscons
if status == 0 if status == 0
store_parse(stdout, options) store_parse(stdout, options)
end end
common_config_checks(status, options) complete(status, options)
end end
# Check for a C header. # Check for a C header.
def check_c_header(header_name, options = {}) def check_c_header(header_name, options = {})
check_cpppath = [nil] + (options[:check_cpppath] || [])
Ansi.write($stdout, "Checking for C header '", :cyan, header_name, :reset, "'... ") Ansi.write($stdout, "Checking for C header '", :cyan, header_name, :reset, "'... ")
File.open("#{@work_dir}/cfgtest.c", "wb") do |fh| File.open("#{@work_dir}/cfgtest.c", "wb") do |fh|
fh.puts <<-EOF fh.puts <<-EOF
@@ -139,15 +140,30 @@ module Rscons
vars = { vars = {
"LD" => "${CC}", "LD" => "${CC}",
"_SOURCES" => "#{@work_dir}/cfgtest.c", "_SOURCES" => "#{@work_dir}/cfgtest.c",
"_TARGET" => "#{@work_dir}/cfgtest.exe", "_TARGET" => "#{@work_dir}/cfgtest.o",
"_DEPFILE" => "#{@work_dir}/cfgtest.mf",
} }
command = BasicEnvironment.new.build_command("${LDCMD}", vars) status = 1
_, _, status = log_and_test_command(command) check_cpppath.each do |cpppath|
common_config_checks(status, options) env = BasicEnvironment.new
if cpppath
env["CPPPATH"] += Array(cpppath)
end
command = env.build_command("${CCCMD}", vars)
_, _, status = log_and_test_command(command)
if status == 0
if cpppath
store_append({"CPPPATH" => Array(cpppath)}, options)
end
break
end
end
complete(status, options)
end end
# Check for a C++ header. # Check for a C++ header.
def check_cxx_header(header_name, options = {}) def check_cxx_header(header_name, options = {})
check_cpppath = [nil] + (options[:check_cpppath] || [])
Ansi.write($stdout, "Checking for C++ header '", :cyan, header_name, :reset, "'... ") Ansi.write($stdout, "Checking for C++ header '", :cyan, header_name, :reset, "'... ")
File.open("#{@work_dir}/cfgtest.cxx", "wb") do |fh| File.open("#{@work_dir}/cfgtest.cxx", "wb") do |fh|
fh.puts <<-EOF fh.puts <<-EOF
@@ -160,15 +176,30 @@ module Rscons
vars = { vars = {
"LD" => "${CXX}", "LD" => "${CXX}",
"_SOURCES" => "#{@work_dir}/cfgtest.cxx", "_SOURCES" => "#{@work_dir}/cfgtest.cxx",
"_TARGET" => "#{@work_dir}/cfgtest.exe", "_TARGET" => "#{@work_dir}/cfgtest.o",
"_DEPFILE" => "#{@work_dir}/cfgtest.mf",
} }
command = BasicEnvironment.new.build_command("${LDCMD}", vars) status = 1
_, _, status = log_and_test_command(command) check_cpppath.each do |cpppath|
common_config_checks(status, options) env = BasicEnvironment.new
if cpppath
env["CPPPATH"] += Array(cpppath)
end
command = env.build_command("${CXXCMD}", vars)
_, _, status = log_and_test_command(command)
if status == 0
if cpppath
store_append({"CPPPATH" => Array(cpppath)}, options)
end
break
end
end
complete(status, options)
end end
# Check for a D import. # Check for a D import.
def check_d_import(d_import, options = {}) def check_d_import(d_import, options = {})
check_d_import_path = [nil] + (options[:check_d_import_path] || [])
Ansi.write($stdout, "Checking for D import '", :cyan, d_import, :reset, "'... ") Ansi.write($stdout, "Checking for D import '", :cyan, d_import, :reset, "'... ")
File.open("#{@work_dir}/cfgtest.d", "wb") do |fh| File.open("#{@work_dir}/cfgtest.d", "wb") do |fh|
fh.puts <<-EOF fh.puts <<-EOF
@@ -181,15 +212,30 @@ module Rscons
vars = { vars = {
"LD" => "${DC}", "LD" => "${DC}",
"_SOURCES" => "#{@work_dir}/cfgtest.d", "_SOURCES" => "#{@work_dir}/cfgtest.d",
"_TARGET" => "#{@work_dir}/cfgtest.exe", "_TARGET" => "#{@work_dir}/cfgtest.o",
"_DEPFILE" => "#{@work_dir}/cfgtest.mf",
} }
command = BasicEnvironment.new.build_command("${LDCMD}", vars) status = 1
_, _, status = log_and_test_command(command) check_d_import_path.each do |d_import_path|
common_config_checks(status, options) env = BasicEnvironment.new
if d_import_path
env["D_IMPORT_PATH"] += Array(d_import_path)
end
command = env.build_command("${DCCMD}", vars)
_, _, status = log_and_test_command(command)
if status == 0
if d_import_path
store_append({"D_IMPORT_PATH" => Array(d_import_path)}, options)
end
break
end
end
complete(status, options)
end end
# Check for a library. # Check for a library.
def check_lib(lib, options = {}) def check_lib(lib, options = {})
check_libpath = [nil] + (options[:check_libpath] || [])
Ansi.write($stdout, "Checking for library '", :cyan, lib, :reset, "'... ") Ansi.write($stdout, "Checking for library '", :cyan, lib, :reset, "'... ")
File.open("#{@work_dir}/cfgtest.c", "wb") do |fh| File.open("#{@work_dir}/cfgtest.c", "wb") do |fh|
fh.puts <<-EOF fh.puts <<-EOF
@@ -204,19 +250,143 @@ module Rscons
"_SOURCES" => "#{@work_dir}/cfgtest.c", "_SOURCES" => "#{@work_dir}/cfgtest.c",
"_TARGET" => "#{@work_dir}/cfgtest.exe", "_TARGET" => "#{@work_dir}/cfgtest.exe",
} }
command = BasicEnvironment.new.build_command("${LDCMD}", vars) status = 1
_, _, status = log_and_test_command(command) check_libpath.each do |libpath|
env = BasicEnvironment.new
if libpath
env["LIBPATH"] += Array(libpath)
end
command = env.build_command("${LDCMD}", vars)
_, _, status = log_and_test_command(command)
if status == 0
if libpath
store_append({"LIBPATH" => Array(libpath)}, options)
end
break
end
end
if status == 0 if status == 0
store_append({"LIBS" => [lib]}, options) store_append({"LIBS" => [lib]}, options)
end end
common_config_checks(status, options) complete(status, options)
end end
# Check for a executable program. # Check for a executable program.
def check_program(program, options = {}) def check_program(program, options = {})
Ansi.write($stdout, "Checking for program '", :cyan, program, :reset, "'... ") Ansi.write($stdout, "Checking for program '", :cyan, program, :reset, "'... ")
path = Util.find_executable(program) path = Util.find_executable(program)
common_config_checks(path ? 0 : 1, options.merge(success_message: path)) complete(path ? 0 : 1, options.merge(success_message: path))
end
# Execute a test command and log the result.
#
# @return [String, String, Process::Status]
# stdout, stderr, status
def log_and_test_command(command)
begin
@log_fh.puts("Command: #{command.join(" ")}")
stdout, stderr, status = Open3.capture3(*command)
@log_fh.puts("Exit status: #{status.to_i}")
@log_fh.write(stdout)
@log_fh.write(stderr)
[stdout, stderr, status]
rescue Errno::ENOENT
["", "", 127]
end
end
# Store construction variables for merging into the Cache.
#
# @param vars [Hash]
# Hash containing the variables to merge.
# @param options [Hash]
# Options.
# @option options [String] :use
# A 'use' name. If specified, the construction variables are only applied
# to an Environment if the Environment is constructed with a matching
# `:use` value.
def store_merge(vars, options = {})
store_vars = store_common(options)
store_vars["merge"] ||= {}
vars.each_pair do |key, value|
store_vars["merge"][key] = value
end
end
# Store construction variables for appending into the Cache.
#
# @param vars [Hash]
# Hash containing the variables to append.
# @param options [Hash]
# Options.
# @option options [String] :use
# A 'use' name. If specified, the construction variables are only applied
# to an Environment if the Environment is constructed with a matching
# `:use` value.
def store_append(vars, options = {})
store_vars = store_common(options)
store_vars["append"] ||= {}
vars.each_pair do |key, value|
if store_vars["append"][key].is_a?(Array) and value.is_a?(Array)
store_vars["append"][key] += value
else
store_vars["append"][key] = value
end
end
end
# Store flags to be parsed into the Cache.
#
# @param flags [String]
# String containing the flags to parse.
# @param options [Hash]
# Options.
# @option options [String] :use
# A 'use' name. If specified, the construction variables are only applied
# to an Environment if the Environment is constructed with a matching
# `:use` value.
def store_parse(flags, options = {})
store_vars = store_common(options)
store_vars["parse"] ||= []
store_vars["parse"] << flags
end
# Perform processing common to several configure checks.
#
# @param status [Process::Status, Integer]
# Process exit code. 0 for success, non-zero for error.
# @param options [Hash]
# Common check options.
# @option options [Boolean] :fail
# Whether to fail configuration if the requested item is not found.
# This defaults to true if the :set_define option is not specified,
# otherwise defaults to false if :set_define option is specified.
# @option options [String] :set_define
# A define to set (in CPPDEFINES) if the requested item is found.
# @option options [String] :success_message
# Message to print on success (default "found").
def complete(status, options)
success_message = options[:success_message] || "found"
fail_message = options[:fail_message] || "not found"
if status == 0
Ansi.write($stdout, :green, "#{success_message}\n")
if options[:set_define]
store_append("CPPDEFINES" => [options[:set_define]])
end
else
should_fail =
if options.has_key?(:fail)
options[:fail]
else
!options[:set_define]
end
if should_fail
Ansi.write($stdout, :red, "#{fail_message}\n")
raise ConfigureFailure.new
else
Ansi.write($stdout, :yellow, "#{fail_message}\n")
end
end
end end
private private
@@ -309,7 +479,12 @@ module Rscons
"DC" => dc, "DC" => dc,
"DCCMD" => env["DCCMD"].map {|e| if e == "-o"; "-of"; else; e; end}, "DCCMD" => env["DCCMD"].map {|e| if e == "-o"; "-of"; else; e; end},
"LDCMD" => env["LDCMD"].map {|e| if e == "-o"; "-of"; else; e; end}, "LDCMD" => env["LDCMD"].map {|e| if e == "-o"; "-of"; else; e; end},
"DDEPGEN" => ["-deps=${_DEPFILE}"],
} }
if RUBY_PLATFORM =~ /mingw/
# ldc2 on Windows expect an object file suffix of .obj.
merge["OBJSUFFIX"] = %w[.obj]
end
end end
_, _, status = log_and_test_command(command) _, _, status = log_and_test_command(command)
if status == 0 if status == 0
@@ -322,64 +497,6 @@ module Rscons
end end
end end
# Execute a test command and log the result.
def log_and_test_command(command)
begin
@log_fh.puts("Command: #{command.join(" ")}")
stdout, stderr, status = Open3.capture3(*command)
@log_fh.puts("Exit status: #{status.to_i}")
@log_fh.write(stdout)
@log_fh.write(stderr)
[stdout, stderr, status]
rescue Errno::ENOENT
["", "", 127]
end
end
# Store construction variables for merging into the Cache.
#
# @param vars [Hash]
# Hash containing the variables to merge.
# @param options [Hash]
# Options.
def store_merge(vars, options = {})
store_vars = store_common(options)
store_vars["merge"] ||= {}
vars.each_pair do |key, value|
store_vars["merge"][key] = value
end
end
# Store construction variables for appending into the Cache.
#
# @param vars [Hash]
# Hash containing the variables to append.
# @param options [Hash]
# Options.
def store_append(vars, options = {})
store_vars = store_common(options)
store_vars["append"] ||= {}
vars.each_pair do |key, value|
if store_vars["append"][key].is_a?(Array) and value.is_a?(Array)
store_vars["append"][key] += value
else
store_vars["append"][key] = value
end
end
end
# Store flags to be parsed into the Cache.
#
# @param flags [String]
# String containing the flags to parse.
# @param options [Hash]
# Options.
def store_parse(flags, options = {})
store_vars = store_common(options)
store_vars["parse"] ||= []
store_vars["parse"] << flags
end
# Common functionality for all store methods. # Common functionality for all store methods.
# #
# @param options [Hash] # @param options [Hash]
@@ -403,34 +520,5 @@ module Rscons
end end
end end
# Perform processing common to several configure checks.
#
# @param status [Process::Status, Integer]
# Process exit code.
# @param options [Hash]
# Common check options.
# @option options [Boolean] :fail
# Whether to fail configuration if the requested item is not found.
# @option options [String] :set_define
# A define to set (in CPPDEFINES) if the requested item is found.
# @option options [String] :success_message
# Message to print on success (default "found").
def common_config_checks(status, options)
success_message = options[:success_message] || "found"
if status == 0
Ansi.write($stdout, :green, "#{success_message}\n")
if options[:set_define]
store_append("CPPDEFINES" => [options[:set_define]])
end
else
if options.has_key?(:fail) and not options[:fail]
Ansi.write($stdout, :yellow, "not found\n")
else
Ansi.write($stdout, :red, "not found\n")
raise ConfigureFailure.new
end
end
end
end end
end end
+4 -1
View File
@@ -59,6 +59,9 @@ module Rscons
# :command, :short, or :off (default :short) # :command, :short, or :off (default :short)
# @option options [Boolean] :exclude_builders # @option options [Boolean] :exclude_builders
# Whether to omit adding default builders (default false) # Whether to omit adding default builders (default false)
# @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.
# #
# If a block is given, the Environment object is yielded to the block and # If a block is given, the Environment object is yielded to the block and
# when the block returns, the {#process} method is automatically called. # when the block returns, the {#process} method is automatically called.
@@ -284,7 +287,7 @@ module Rscons
unless @process_failures.empty? unless @process_failures.empty?
msg = @process_failures.join("\n") msg = @process_failures.join("\n")
if Cache.instance["failed_commands"].size > 0 if Cache.instance["failed_commands"].size > 0
msg += "\nRun `#{$0} -F` to see the failed command(s)." msg += "\nUse -F to view the failed command log from the previous build operation"
end end
raise BuildError.new(msg) raise BuildError.new(msg)
end end
+15
View File
@@ -3,6 +3,7 @@ module Rscons
# The Script class encapsulates the state of a build script. # The Script class encapsulates the state of a build script.
class Script class Script
# DSL available to the Rsconscript.
class Dsl class Dsl
# Create a Dsl. # Create a Dsl.
def initialize(script) def initialize(script)
@@ -57,6 +58,7 @@ module Rscons
end end
end end
# DSL available to the 'configure' block.
class ConfigureDsl class ConfigureDsl
# Create a ConfigureDsl. # Create a ConfigureDsl.
# #
@@ -81,6 +83,19 @@ module Rscons
@configure_op.__send__(method_name, *args) @configure_op.__send__(method_name, *args)
end end
end end
# Perform a custom configuration check.
#
# @param message [String]
# Custom configuration check message (e.g. "Checking for foo").
# rscons will add "... " to the end of the message.
# @yieldparam configure_op [ConfigureOp]
# {ConfigureOp} object.
# @return [void]
def custom_check(message, &block)
$stdout.write(message + "... ")
block[@configure_op]
end
end end
# @return [String, nil] # @return [String, nil]
+20 -13
View File
@@ -1,4 +1,5 @@
module Rscons module Rscons
# A collection of stand-alone utility methods.
module Util module Util
class << self class << self
@@ -159,7 +160,7 @@ module Rscons
end end
end end
# Parse dependencies from a Makefile. # Parse dependencies from a Makefile or ldc2 dependency file.
# #
# This method is used internally by Rscons builders. # This method is used internally by Rscons builders.
# #
@@ -168,7 +169,7 @@ module Rscons
# #
# @return [Array<String>] # @return [Array<String>]
# Paths of dependency files. # Paths of dependency files.
def parse_makefile_deps(mf_fname) def parse_dependency_file(mf_fname)
deps = [] deps = []
buildup = '' buildup = ''
File.read(mf_fname).each_line do |line| File.read(mf_fname).each_line do |line|
@@ -176,7 +177,11 @@ module Rscons
buildup += ' ' + $1 buildup += ' ' + $1
else else
buildup += ' ' + line buildup += ' ' + line
if buildup =~ /^.*: (.*)$/ if buildup =~ /^[^:]+\(\S+\)\s*:.*?:[^:]+\((\S+)\)/
# ldc2-style dependency line
deps << $1
elsif buildup =~ /^.*: (.*)$/
# Makefile-style dependency line
mf_deps = $1 mf_deps = $1
deps += mf_deps.split(' ').map(&:strip) deps += mf_deps.split(' ').map(&:strip)
end end
@@ -199,16 +204,18 @@ module Rscons
File.file?(path) and File.executable?(path) File.file?(path) and File.executable?(path)
end end
if RbConfig::CONFIG["host_os"] =~ /mswin|windows|mingw/i if RbConfig::CONFIG["host_os"] =~ /mswin|windows|mingw/i
executable = executable.downcase if File.directory?(path_entry)
dir_entries = Dir.entries(path_entry) executable = executable.downcase
dir_entries.find do |entry| dir_entries = Dir.entries(path_entry)
path = "#{path_entry}/#{entry}" dir_entries.find do |entry|
entry = entry.downcase path = "#{path_entry}/#{entry}"
if ((entry == executable) or entry = entry.downcase
(entry == "#{executable}.exe") or if ((entry == executable) or
(entry == "#{executable}.com") or (entry == "#{executable}.exe") or
(entry == "#{executable}.bat")) and is_executable[path] (entry == "#{executable}.com") or
return path (entry == "#{executable}.bat")) and is_executable[path]
return path
end
end end
end end
else else
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons module Rscons
# Project version. # Project version.
VERSION = "2.0.0beta1" VERSION = "2.0.0beta2"
end end
+7 -5
View File
@@ -59,9 +59,10 @@ combined_file.each do |line|
end end
require "zlib" require "zlib"
require "base64"
compressed_script = Zlib::Deflate.deflate(stripped_comments.join) compressed_script = Zlib::Deflate.deflate(stripped_comments.join)
escaped_compressed_script = compressed_script.gsub("#", "#1").gsub("\n", "#2").gsub("\r", "#3").gsub("\0", "#4") encoded_compressed_script = Base64.encode64(compressed_script).gsub("\n", "")
hash = Digest::MD5.hexdigest(escaped_compressed_script) hash = Digest::MD5.hexdigest(encoded_compressed_script)
FileUtils.mkdir_p(DIST) FileUtils.mkdir_p(DIST)
File.open("#{DIST}/#{PROG_NAME}", "wb", 0755) do |fh| File.open("#{DIST}/#{PROG_NAME}", "wb", 0755) do |fh|
@@ -70,9 +71,10 @@ File.open("#{DIST}/#{PROG_NAME}", "wb", 0755) do |fh|
script = File.join(File.dirname(__FILE__), ".rscons-#{VERSION}-#{hash}.rb") script = File.join(File.dirname(__FILE__), ".rscons-#{VERSION}-#{hash}.rb")
unless File.exists?(script) unless File.exists?(script)
if File.read(__FILE__, mode: "rb") =~ /^#==>(.*)/ if File.read(__FILE__, mode: "rb") =~ /^#==>(.*)/
escaped_compressed = $1
unescaped_compressed = escaped_compressed.gsub("#4", "\\0").gsub("#3", "\\r").gsub("#2", "\\n").gsub("#1", "#")
require "zlib" require "zlib"
require "base64"
encoded_compressed = $1
unescaped_compressed = Base64.decode64(encoded_compressed)
inflated = Zlib::Inflate.inflate(unescaped_compressed) inflated = Zlib::Inflate.inflate(unescaped_compressed)
File.open(script, "wb") do |fh| File.open(script, "wb") do |fh|
fh.write(inflated) fh.write(inflated)
@@ -85,6 +87,6 @@ load script
if __FILE__ == $0 if __FILE__ == $0
Rscons::Cli.run(ARGV) Rscons::Cli.run(ARGV)
end end
#==>#{escaped_compressed_script} #==>#{encoded_compressed_script}
EOF EOF
end end
+11
View File
@@ -59,6 +59,8 @@ class Generator
line = @lines.slice!(0) line = @lines.slice!(0)
if line =~ /^```(.*)$/ if line =~ /^```(.*)$/
@sections.last.append(gather_code_section($1)) @sections.last.append(gather_code_section($1))
elsif line.chomp == "${remove}"
remove_section
elsif line =~ /^(#+)(>)?\s*(.*)$/ elsif line =~ /^(#+)(>)?\s*(.*)$/
level_text, new_page_text, title_text = $1, $2, $3 level_text, new_page_text, title_text = $1, $2, $3
level = $1.size level = $1.size
@@ -196,6 +198,15 @@ class Generator
end end
end end
def remove_section
loop do
line = @lines.slice!(0)
if line.chomp == "${/remove}"
break
end
end
end
def get_next_section_number(level) def get_next_section_number(level)
if @current_section_number.size == level - 1 if @current_section_number.size == level - 1
@current_section_number << 1 @current_section_number << 1
+188 -20
View File
@@ -65,6 +65,9 @@ describe Rscons do
def create_exe(exe_name, contents) def create_exe(exe_name, contents)
exe_file = "#{@build_test_run_dir}/_bin/#{exe_name}" exe_file = "#{@build_test_run_dir}/_bin/#{exe_name}"
if RUBY_PLATFORM =~ /mingw/
exe_file += ".bat"
end
File.open(exe_file, "wb") do |fh| File.open(exe_file, "wb") do |fh|
fh.puts("#!/bin/sh") fh.puts("#!/bin/sh")
fh.puts(contents) fh.puts(contents)
@@ -142,7 +145,11 @@ EOF
stdout, stderr, status = nil, nil, nil stdout, stderr, status = nil, nil, nil
Bundler.with_clean_env do Bundler.with_clean_env do
env = ENV.to_h env = ENV.to_h
env["PATH"] = "#{@build_test_run_dir}/_bin#{File::PATH_SEPARATOR}#{env["PATH"]}" path = ["#{@build_test_run_dir}/_bin", "#{env["PATH"]}"]
if options[:path]
path = Array(options[:path]) + path
end
env["PATH"] = path.join(File::PATH_SEPARATOR)
stdout, stderr, status = Open3.capture3(env, *command) stdout, stderr, status = Open3.capture3(env, *command)
File.open("#{@build_test_run_dir}/.stdout", "wb") do |fh| File.open("#{@build_test_run_dir}/.stdout", "wb") do |fh|
fh.write(stdout) fh.write(stdout)
@@ -588,18 +595,51 @@ EOF
expect(result.stdout).to eq "" expect(result.stdout).to eq ""
end end
unless ENV["omit_gdc_tests"] it "supports building D sources with gdc" do
it "supports building D sources" do test_dir("d")
test_dir("d") result = run_rscons
result = run_rscons expect(result.stderr).to eq ""
expect(result.stderr).to eq "" slines = lines(result.stdout)
slines = lines(result.stdout) verify_lines(slines, [%r{gdc -c -o build/e.1/main.o -MMD -MF build/e.1/main.mf main.d}])
verify_lines(slines, [%r{gdc -c -o build/e.1/main.o -MMD -MF build/e.1/main.mf main.d}]) verify_lines(slines, [%r{gdc -c -o build/e.1/mod.o -MMD -MF build/e.1/mod.mf mod.d}])
verify_lines(slines, [%r{gdc -c -o build/e.1/mod.o -MMD -MF build/e.1/mod.mf mod.d}]) verify_lines(slines, [%r{gdc -o hello-d.exe build/e.1/main.o build/e.1/mod.o}])
verify_lines(slines, [%r{gdc -o hello-d.exe build/e.1/main.o build/e.1/mod.o}]) expect(`./hello-d.exe`.rstrip).to eq "Hello from D, value is 42!"
expect(`./hello-d.exe`.rstrip).to eq "Hello from D, value is 42!" end
end
it "supports building D sources with ldc2" do
test_dir("d")
result = run_rscons(rsconscript: "build-ldc2.rb")
expect(result.stderr).to eq ""
slines = lines(result.stdout)
verify_lines(slines, [%r{ldc2 -c -of build/e.1/main.o(bj)? -deps=build/e.1/main.mf main.d}])
verify_lines(slines, [%r{ldc2 -c -of build/e.1/mod.o(bj)? -deps=build/e.1/mod.mf mod.d}])
verify_lines(slines, [%r{ldc2 -of hello-d.exe build/e.1/main.o(bj)? build/e.1/mod.o(bj)?}])
expect(`./hello-d.exe`.rstrip).to eq "Hello from D, value is 42!"
end
it "rebuilds D modules with ldc2 when deep dependencies change" do
test_dir("d")
result = run_rscons(rsconscript: "build-ldc2.rb")
expect(result.stderr).to eq ""
slines = lines(result.stdout)
verify_lines(slines, [%r{ldc2 -c -of build/e.1/main.o(bj)? -deps=build/e.1/main.mf main.d}])
verify_lines(slines, [%r{ldc2 -c -of build/e.1/mod.o(bj)? -deps=build/e.1/mod.mf mod.d}])
verify_lines(slines, [%r{ldc2 -of hello-d.exe build/e.1/main.o(bj)? build/e.1/mod.o(bj)?}])
expect(`./hello-d.exe`.rstrip).to eq "Hello from D, value is 42!"
contents = File.read("mod.d", mode: "rb").sub("42", "33")
File.open("mod.d", "wb") do |fh|
fh.write(contents)
end
result = run_rscons(rsconscript: "build-ldc2.rb")
expect(result.stderr).to eq ""
slines = lines(result.stdout)
verify_lines(slines, [%r{ldc2 -c -of build/e.1/main.o(bj)? -deps=build/e.1/main.mf main.d}])
verify_lines(slines, [%r{ldc2 -c -of build/e.1/mod.o(bj)? -deps=build/e.1/mod.mf mod.d}])
verify_lines(slines, [%r{ldc2 -of hello-d.exe build/e.1/main.o(bj)? build/e.1/mod.o(bj)?}])
expect(`./hello-d.exe`.rstrip).to eq "Hello from D, value is 33!"
end
unless ENV["omit_gdc_tests"]
it "links with the D linker when object files were built from D sources" do it "links with the D linker when object files were built from D sources" do
test_dir("d") test_dir("d")
result = run_rscons(rsconscript: "link_objects.rb") result = run_rscons(rsconscript: "link_objects.rb")
@@ -633,11 +673,7 @@ EOF
test_dir("shared_library") test_dir("shared_library")
result = run_rscons(rsconscript: "shared_library_d.rb") result = run_rscons(rsconscript: "shared_library_d.rb")
# Currently gdc produces an error while trying to build the shared expect(result.stderr).to eq ""
# library. Since this isn't really an rscons problem, I'm commenting out
# this check. I'm not sure what I want to do about D support at this
# point anyway...
#expect(result.stderr).to eq ""
slines = lines(result.stdout) slines = lines(result.stdout)
if RUBY_PLATFORM =~ /mingw/ if RUBY_PLATFORM =~ /mingw/
verify_lines(slines, [%r{Linking mine.dll}]) verify_lines(slines, [%r{Linking mine.dll}])
@@ -998,7 +1034,7 @@ EOF
result = run_rscons result = run_rscons
expect(result.stderr).to match /Failed to build/ expect(result.stderr).to match /Failed to build/
expect(result.stderr).to match /^Run.*-F.*to see the failed command/ expect(result.stderr).to match /^Use.*-F.*to view the failed command log/
expect(result.status).to_not eq 0 expect(result.status).to_not eq 0
result = run_rscons(rscons_args: %w[-F]) result = run_rscons(rscons_args: %w[-F])
@@ -1835,6 +1871,21 @@ EOF
expect(result.status).to eq 0 expect(result.status).to eq 0
expect(result.stdout).to_not match /-DHAVE_/ expect(result.stdout).to_not match /-DHAVE_/
end end
it "modifies CPPPATH based on check_cpppath" do
test_dir "configure"
FileUtils.mkdir_p("usr1")
FileUtils.mkdir_p("usr2")
File.open("usr2/frobulous.h", "wb") do |fh|
fh.puts("#define FOO 42")
end
result = run_rscons(rsconscript: "check_c_header_cpppath.rb", op: "configure")
expect(result.stderr).to eq ""
expect(result.status).to eq 0
result = run_rscons(rsconscript: "check_c_header_cpppath.rb", rscons_args: %w[-v])
expect(result.stdout).to_not match %r{-I./usr1}
expect(result.stdout).to match %r{-I./usr2}
end
end end
context "check_cxx_header" do context "check_cxx_header" do
@@ -1861,6 +1912,21 @@ EOF
expect(result.status).to eq 0 expect(result.status).to eq 0
expect(result.stdout).to match /Checking for C\+\+ header 'not___found\.h'... not found/ expect(result.stdout).to match /Checking for C\+\+ header 'not___found\.h'... not found/
end end
it "modifies CPPPATH based on check_cpppath" do
test_dir "configure"
FileUtils.mkdir_p("usr1")
FileUtils.mkdir_p("usr2")
File.open("usr2/frobulous.h", "wb") do |fh|
fh.puts("#define FOO 42")
end
result = run_rscons(rsconscript: "check_cxx_header_cpppath.rb", op: "configure")
expect(result.stderr).to eq ""
expect(result.status).to eq 0
result = run_rscons(rsconscript: "check_cxx_header_cpppath.rb", rscons_args: %w[-v])
expect(result.stdout).to_not match %r{-I./usr1}
expect(result.stdout).to match %r{-I./usr2}
end
end end
context "check_d_import" do context "check_d_import" do
@@ -1887,6 +1953,21 @@ EOF
expect(result.status).to eq 0 expect(result.status).to eq 0
expect(result.stdout).to match /Checking for D import 'not\.found'... not found/ expect(result.stdout).to match /Checking for D import 'not\.found'... not found/
end end
it "modifies D_IMPORT_PATH based on check_d_import_path" do
test_dir "configure"
FileUtils.mkdir_p("usr1")
FileUtils.mkdir_p("usr2")
File.open("usr2/frobulous.d", "wb") do |fh|
fh.puts("int foo = 42;")
end
result = run_rscons(rsconscript: "check_d_import_d_import_path.rb", op: "configure")
expect(result.stderr).to eq ""
expect(result.status).to eq 0
result = run_rscons(rsconscript: "check_d_import_d_import_path.rb", rscons_args: %w[-v])
expect(result.stdout).to_not match %r{-I./usr1}
expect(result.stdout).to match %r{-I./usr2}
end
end end
context "check_lib" do context "check_lib" do
@@ -1941,6 +2022,23 @@ EOF
expect(result.stdout).to match /Checking for library 'm'... found/ expect(result.stdout).to match /Checking for library 'm'... found/
expect(result.stdout).to_not match /-lm/ expect(result.stdout).to_not match /-lm/
end end
it "modifies LIBPATH based on check_libpath" do
test_dir "configure"
FileUtils.mkdir_p("usr1")
FileUtils.mkdir_p("usr2")
result = run_rscons(rsconscript: "check_lib_libpath1.rb")
expect(result.stderr).to eq ""
expect(result.status).to eq 0
result = run_rscons(rsconscript: "check_lib_libpath2.rb", op: "configure")
expect(result.stderr).to eq ""
expect(result.status).to eq 0
result = run_rscons(rsconscript: "check_lib_libpath2.rb")
expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(result.stdout).to match %r{-L\./usr2}
expect(result.stdout).to_not match %r{-L\./usr1}
end
end end
context "check_program" do context "check_program" do
@@ -1952,6 +2050,16 @@ EOF
expect(result.stdout).to match /Checking for program 'find'... .*find/ expect(result.stdout).to match /Checking for program 'find'... .*find/
end end
context "with non-existent PATH entries" do
it "succeeds when the requested program is found" do
test_dir "configure"
result = run_rscons(rsconscript: "check_program_success.rb", op: "configure", path: "/foo/bar")
expect(result.stderr).to eq ""
expect(result.status).to eq 0
expect(result.stdout).to match /Checking for program 'find'... .*find/
end
end
it "fails when the requested program is not found" do it "fails when the requested program is not found" do
test_dir "configure" test_dir "configure"
result = run_rscons(rsconscript: "check_program_failure.rb", op: "configure") result = run_rscons(rsconscript: "check_program_failure.rb", op: "configure")
@@ -2024,6 +2132,66 @@ EOF
end end
end end
context "custom_check" do
context "when running a test command" do
context "when executing the command fails" do
context "when failures are fatal" do
it "fails configuration with the correct error message" do
test_dir "configure"
create_exe "grep", "exit 4"
result = run_rscons(rsconscript: "custom_config_check.rb", op: "configure")
expect(result.stderr).to eq ""
expect(result.stdout).to match /Checking 'grep' version\.\.\. error executing grep/
expect(result.status).to_not eq 0
end
end
context "when the custom logic indicates a failure" do
it "fails configuration with the correct error message" do
test_dir "configure"
create_exe "grep", "echo 'grep (GNU grep) 1.1'"
result = run_rscons(rsconscript: "custom_config_check.rb", op: "configure")
expect(result.stderr).to eq ""
expect(result.stdout).to match /Checking 'grep' version\.\.\. too old!/
expect(result.status).to_not eq 0
end
end
end
context "when failures are not fatal" do
context "when the custom logic indicates a failure" do
it "displays the correct message and does not fail configuration" do
test_dir "configure"
create_exe "grep", "echo 'grep (GNU grep) 2.1'"
result = run_rscons(rsconscript: "custom_config_check.rb", op: "configure")
expect(result.stderr).to eq ""
expect(result.stdout).to match /Checking 'grep' version\.\.\. we'll work with it but you should upgrade/
expect(result.status).to eq 0
result = run_rscons(rsconscript: "custom_config_check.rb", op: "build")
expect(result.stderr).to eq ""
expect(result.stdout).to match /GREP_WORKAROUND/
expect(result.status).to eq 0
end
end
end
context "when the custom logic indicates success" do
it "passes configuration with the correct message" do
test_dir "configure"
create_exe "grep", "echo 'grep (GNU grep) 3.0'"
result = run_rscons(rsconscript: "custom_config_check.rb", op: "configure")
expect(result.stderr).to eq ""
expect(result.stdout).to match /Checking 'grep' version\.\.\. good!/
expect(result.status).to eq 0
result = run_rscons(rsconscript: "custom_config_check.rb", op: "build")
expect(result.stderr).to eq ""
expect(result.stdout).to match /GREP_FULL/
expect(result.status).to eq 0
end
end
end
end
it "does everything" do it "does everything" do
test_dir "configure" test_dir "configure"
create_exe "pkg-config", "echo '-DMYPACKAGE'" create_exe "pkg-config", "echo '-DMYPACKAGE'"
@@ -2034,8 +2202,8 @@ EOF
expect(result.stdout).to match /Setting build directory\.\.\. bb/ expect(result.stdout).to match /Setting build directory\.\.\. bb/
expect(result.stdout).to match %r{Setting prefix\.\.\. /my/prefix} 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\.\.\. gcc/
expect(result.stdout).to match /Checking for C\+\+ compiler\.\.\. g++/ expect(result.stdout).to match /Checking for C\+\+ compiler\.\.\. g\+\+/
expect(result.stdout).to match /Checking for D compiler\.\.\. gdc/ expect(result.stdout).to match /Checking for D compiler\.\.\. (gdc|ldc2)/
expect(result.stdout).to match /Checking for package 'mypackage'\.\.\. found/ expect(result.stdout).to match /Checking for package 'mypackage'\.\.\. found/
expect(result.stdout).to match /Checking for C header 'stdio.h'\.\.\. found/ expect(result.stdout).to match /Checking for C header 'stdio.h'\.\.\. found/
expect(result.stdout).to match /Checking for C\+\+ header 'iostream'\.\.\. found/ expect(result.stdout).to match /Checking for C\+\+ header 'iostream'\.\.\. found/
+20 -4
View File
@@ -121,7 +121,10 @@ EOF
before(:each) do before(:each) do
stub_const("File::PATH_SEPARATOR", ";") stub_const("File::PATH_SEPARATOR", ";")
stub_const("RbConfig::CONFIG", "host_os" => "mingw") stub_const("RbConfig::CONFIG", "host_os" => "mingw")
expect(ENV).to receive(:[]).with("PATH").and_return("C:\\bin;C:\\Windows") expect(ENV).to receive(:[]).with("PATH").and_return("C:\\none;C:\\bin;C:\\Windows")
allow(File).to receive(:directory?).with("C:\\none").and_return(false)
allow(File).to receive(:directory?).with("C:\\bin").and_return(true)
allow(File).to receive(:directory?).with("C:\\Windows").and_return(true)
allow(Dir).to receive(:entries).with("C:\\bin").and_return(%w[one.com]) allow(Dir).to receive(:entries).with("C:\\bin").and_return(%w[one.com])
allow(Dir).to receive(:entries).with("C:\\Windows").and_return(%w[two.exe Three.bat]) allow(Dir).to receive(:entries).with("C:\\Windows").and_return(%w[two.exe Three.bat])
end end
@@ -197,12 +200,12 @@ EOF
end end
end end
describe ".parse_makefile_deps" do describe ".parse_dependency_file" do
it 'handles dependencies on one line' do it 'handles dependencies on one line' do
expect(File).to receive(:read).with('makefile').and_return(<<EOS) expect(File).to receive(:read).with('makefile').and_return(<<EOS)
module.o: source.cc module.o: source.cc
EOS EOS
expect(Util.parse_makefile_deps('makefile')).to eq ['source.cc'] expect(Util.parse_dependency_file('makefile')).to eq ['source.cc']
end end
it 'handles dependencies split across many lines' do it 'handles dependencies split across many lines' do
@@ -211,9 +214,22 @@ module.o: module.c \\
module.h \\ module.h \\
other.h other.h
EOS EOS
expect(Util.parse_makefile_deps('makefile')).to eq [ expect(Util.parse_dependency_file('makefile')).to eq [
'module.c', 'module.h', 'other.h'] 'module.c', 'module.h', 'other.h']
end end
it "parses ldc2-style dependency lines" do
expect(File).to receive(:read).with("deps.out").and_return(<<EOS)
std.traits (/usr/lib/ldc/x86_64-linux-gnu/include/d/std/traits.d) : private : std.meta (/usr/lib/ldc/x86_64-linux-gnu/include/d/std/meta.d):staticIndexOf
std.conv (/usr/lib/ldc/x86_64-linux-gnu/include/d/std/conv.d) : private : std.array (/usr/lib/ldc/x86_64-linux-gnu/include/d/std/array.d):array
std.stdio (/usr/lib/ldc/x86_64-linux-gnu/include/d/std/stdio.d) : private static : core.sys.posix.sys.socket (/usr/lib/ldc/x86_64-linux-gnu/include/d/core/sys/posix/sys/socket.d) -> sock
EOS
expect(Util.parse_dependency_file("deps.out")).to eq [
"/usr/lib/ldc/x86_64-linux-gnu/include/d/std/meta.d",
"/usr/lib/ldc/x86_64-linux-gnu/include/d/std/array.d",
"/usr/lib/ldc/x86_64-linux-gnu/include/d/core/sys/posix/sys/socket.d",
]
end
end end
end end