Compare commits

...
56 Commits
Author SHA1 Message Date
josh 5beddd63ac bundle update 2019-07-02 19:46:30 -04:00
josh 4967f89a76 fix inconsistent type handling in VarSet#expand_varref - #73 2019-04-14 14:10:13 -04:00
josh 7cc68b2ce1 Remove Cache dirty-tracking
It really wasn't necessary since Cache#write is not called often.
2019-04-04 15:20:35 -04:00
josh be058dd18b avoid mkdir() race conditions - close #75 2018-12-11 22:13:33 -05:00
josh ad703a5c84 call Bundler.setup in build_tests_spec.rb 2018-12-09 21:20:16 -05:00
John Lindgren fb5d107581 Reduce recursion of VarSet#expand_varref by expanding vars earlier.
It's common for a build command to contain a reference like:

    ${INCPREFIX}${CPPPATH}

Where ${INCPREFIX} is "-I" and ${CPPPATH} is an array of include
paths.  Without this change, Rscons expands ${INCPREFIX} (and then
tries to expand "-I") repeatedly, once for each path.

This change simply makes Rscons intelligent enough to expand
${INCPREFIX} only once, then join the expanded value to each path.

In a mid-sized rake project, ruby-prof shows that the number of
calls to expand_varref is reduced from ~80,000 to ~30,000., and the
total CPU time spent in expand_varref is reduced by about 30%.

In a larger project, the improvement in real build time is small
but consistent enough to be measurable (~0.5 seconds off a total
of ~20 seconds).
2018-12-07 20:06:18 -05:00
josh d5c1a4b41a v1.17.0 2018-11-02 17:01:52 -04:00
josh 3f63bf5d13 update CHANGELOG.md for v1.17.0 2018-11-02 17:01:21 -04:00
josh ce47bd3599 remove makefile target name check when parsing dependencies - close #57 2018-11-02 14:23:47 -04:00
michael.metivier 68cab5e24d Environment.expand_varref has the same allowed parameter types and returns as Varset.expand_varref 2018-11-02 08:52:23 -04:00
michael.metivier f92dd62ebd Allow construction variable expansion on _true_ and _false_ literals 2018-11-02 00:03:54 -04:00
josh 5cf77f1f58 v1.16.0 2018-08-24 09:24:03 -04:00
josh b4cc45c381 update documentation 2018-08-24 09:17:23 -04:00
josh 750506201f add github URL to README.md 2018-08-23 15:37:18 -04:00
josh 284312a21c support command-line variables - close #51 2018-08-23 14:58:08 -04:00
josh d081a4695f add shebang line to bin/rscons 2018-08-23 11:30:18 -04:00
josh c7f66694ab add Rscons.glob - close #50 2018-08-23 11:26:20 -04:00
josh 57de94a3fb improve debuggability of cache.up_to_date? - close #46 2018-08-23 10:10:05 -04:00
josh 53ba7dad41 allow passing a VarSet into cache methods - close #47 2018-08-22 16:32:05 -04:00
josh 8cb02a7e34 generate dependencies for D builds - close #49 2018-08-22 15:23:48 -04:00
josh dbbce9e476 update a few dependencies 2018-05-24 15:32:39 -04:00
josh 0dc3a90660 v1.15.0 2017-09-08 15:13:04 -04:00
josh d48c7587d6 add v1.15.0 release notes 2017-09-08 15:06:55 -04:00
josh 70d4740a69 split CHANGELOG.md out of README.md 2017-09-08 15:06:23 -04:00
josh aaae845d60 allow json 1.x or 2.x 2017-09-08 15:04:40 -04:00
josh e900e74342 v1.14.0 2017-08-03 15:55:54 -04:00
josh 62ae3f162d add v1.14.0 release notes 2017-08-03 15:55:38 -04:00
josh 099d26f33c Add Rscons::VarSet#values_at - close #45 2017-08-03 15:54:24 -04:00
josh 89562c584a Environment#print_builder_run_message should support string commands - close #44 2017-08-03 14:10:53 -04:00
josh a9aeaf2356 v1.13.0 2017-07-14 16:24:27 -04:00
josh 5409b3cb34 add v1.13.0 release notes 2017-07-14 16:24:04 -04:00
josh 6d04500c13 Add ability to record side-effect file production - close #43 2017-07-14 16:17:01 -04:00
josh 45e30662d2 add YARD comment for Rscons::Ansi to get to 100% coverage 2017-07-12 15:51:00 -04:00
josh 8f7e044bfd fix YARD @option tag 2017-07-12 15:50:13 -04:00
josh dbea6f0a26 v1.12.0 2017-07-11 16:16:01 -04:00
josh c923eb5c7c add v1.12.0 release notes 2017-07-11 16:14:49 -04:00
josh 529d9aacaa colorize a couple other builders' output messages 2017-07-11 16:12:38 -04:00
josh 65c1bac4f9 be more colorful - close #41 2017-07-11 13:52:01 -04:00
josh 775363ddbd cloned Environments should inherit n_threads - close #42 2017-07-11 10:29:25 -04:00
josh 2a96495e83 wait for in-progress subcommands to complete on build failure - close #39 2017-06-26 22:44:49 -04:00
josh 15e52e488c env.depends should imply env.build_after - close #40 2017-06-21 10:44:52 -04:00
josh c0b12132f0 1.11.1 release notes 2017-06-14 09:58:09 -04:00
josh 9141cf91e7 v1.11.1 2017-06-14 09:57:11 -04:00
josh 7f7e6ef3da fix circular build dependency detection logic 2017-06-14 09:56:51 -04:00
josh 60b3fdbcac update YARD documentation link 2017-06-14 09:12:13 -04:00
josh 5263ea03c6 v1.11.0 2017-06-14 08:56:21 -04:00
josh 6c91823ce2 v1.11.0 release notes 2017-06-14 08:54:57 -04:00
josh 8deb1eb6f8 Change default :clone option to :all to clone all Environment attributes 2017-06-14 08:53:28 -04:00
josh 6d6319799a add v1.10.1 release notes 2017-06-13 19:47:58 -04:00
josh 694642dfbd Allow overriding n_threads on a per-Environment level - close #34 2017-06-13 19:45:53 -04:00
josh 6dd70ff65e raise error when circular dependencies are found - close #38 2017-06-13 19:37:41 -04:00
josh eca01c38df Fix non-blocking thread-wait if Rscons.n_threads is set to 0 - close #37 2017-06-13 19:07:04 -04:00
josh 4ad6ce1d9c SHCFLAGS and SHCXXFLAGS should inherit non-SH flags by default - close #36 2017-06-13 18:41:47 -04:00
josh a49ebc486e env.build_after should expand paths - close #35 2017-06-13 18:38:47 -04:00
josh 74d19ea235 fix construction variable reference table OBJSUFFIX entries 2017-06-12 09:33:52 -04:00
josh d7a6d69a92 update v1.10.0 release notes 2017-06-12 09:33:13 -04:00
53 changed files with 1096 additions and 375 deletions

No files matched your search

+210
View File
@@ -0,0 +1,210 @@
## ChangeLog
### v1.17.0
#### New Features
- allow construction variable expansion on `true` and `false` values.
- remove makefile target name check when parsing dependencies
### v1.16.0
#### New Features
- Add `Rscons.glob`
- Support command-line variables
- improve debuggability of cache.up_to_date?
- allow passing a VarSet into cache methods
#### Fixes
- generate dependencies for D builds
### v1.15.0
- allow json 1.x or 2.x
### v1.14.0
#### New Features
- #45 - Add Rscons::VarSet#values_at
#### Fixes
- #44 - Environment#print_builder_run_message should support string commands
### v1.13.0
#### New Features
- #43 - Add ability to record side-effect file production
### v1.12.0
#### New Features
- #40 - env.depends should imply env.build_after
- #41 - be more colorful
#### Fixes
- #39 - wait for in-progress subcommands to complete on build failure
- #42 - cloned Environments should inherit n_threads
### v1.11.1
#### Fixes
- fix the circular build dependency detection logic
### v1.11.0
#### New Features
- Change default Environment :clone option to :all to clone all attributes
- #38 - raise error when circular dependencies are found
- #34 - Allow overriding n_threads on a per-Environment level
#### Fixes
- #35 - env.build_after should expand paths
- #36 - SHCFLAGS and SHCXXFLAGS should inherit non-SH flags by default
- #37 - Fix non-blocking thread-wait if Rscons.n_threads is set to 0
### v1.10.0
#### New Features
- #23 - add parallelization - builds are now parallelized by default
- #31 - add LEXSUFFIX, YACCSUFFIX construction variables
- #30 - place object files for absolute source paths under build_root
- #28 - support redirecting standard output using the Command builder
- Always use a build root and default it to "build"
- Add builder features
- #8 - add SharedObject and SharedLibrary builders
#### Fixes
- expand target and source paths before calling Builder#create_build_target
- #29 - fix PROGSUFFIX handling
- #32 - Pre-build hooks do not respect modified key values
### v1.9.3
- Environment#parse_flags should put -std=XXX flags in CCFLAGS, not CFLAGS
### v1.9.2
- allow phony targets in conjunction with build roots
### v1.9.1
- change *SUFFIX defaults to arrays
- add various C++ file suffixes
- use ${INCPREFIX} instead of hard-coded "-I" in Preprocess builder
### v1.9.0
#### New Features
- #6 - add Install and Copy builders
- #22 - allow overriding Command builder short description with CMD_DESC variable
- #24 - add "rscons" executable
- #25 - add support for phony targets given as Symbols instead of Strings
- #26 - support registering multiple build targets with the same target name
- #27 - add Directory builder
#### Fixes
- #20 - fix variable references that expand to arrays in build target sources
- #21 - rework Preprocess builder to consider deep dependencies
- fix Rscons.set_suffix to append the given suffix if the filename has none
- remove ${CFLAGS} from default CPP_CMD
### v1.8.1
- fix Environment#dump when construction variables are symbols
### v1.8.0
- new Command builder to execute arbitrary user commands
- new SimpleBuilder class
- create new builders quickly by passing a block to Environment#add_builder
- improved YARD documentation
- add Environment#dump to debug Environment construction variables
### v1.7.0
- allow build hooks to register new build targets
- add post-build hooks (register with Environment#add_post_build_hook)
- clear all build targets after processing an Environment
- allow trailing slashes in arguments to Environment#build_dir
### v1.6.1
- add DEPFILESUFFIX construction variable to override dependency file suffix
- fix Environment#depends to expand its arguments for construction variables
### v1.6.0
- support lambdas as construction variable values
### v1.5.0
- add "json" as a runtime dependency
- update construction variables to match SCons more closely
- add CPPDEFPREFIX, INCPREFIX, CPPDEFINES, CCFLAGS, LIBDIRPREFIX, and LIBLINKPREFIX
- add Environment#shell
- add Environment#parse_flags, #parse_flags!, #merge_flags
- unbuffer $stdout by default
- add PROGSUFFIX construction variable (defaults to .exe on MinGW/Cygwin)
- add Rscons::BuildTarget and Builder#create_build_target
- update specs to RSpec 3.x and fix to run on MinGW/Cygwin/Linux
- add YARD documentation to get to 100% coverage
### v1.4.3
- fix builders properly using construction variable overrides
- expand nil construction variables to empty strings
### v1.4.2
- add Environment#expand_path
- expand construction variable references in builder targets and sources before invoking builder
### v1.4.1
- fix invoking a builder with no sources while a build root defined
### v1.4.0
- add CFile builder
- add Disassemble builder
- add Preprocess builder
- pass the Environment object to build hooks in the :env key of the build_op parameter
- expand target/source paths beginning with "^/" to be relative to the Environment's build root
- many performance improvements, including:
- use JSON instead of YAML for the cache to improve loading speed (Issue #7)
- store a hash of the build command instead of the full command contents in the cache
- implement copy-on-write semantics for construction variables when cloning Environments
- only load the cache once instead of on each Environment#process
- only write the cache when something has changed
- fix Cache#mkdir_p to handle relative paths (Issue #5)
- flush the cache to disk if a builder raises an exception (Issue #4)
### v1.3.0
- change Environment#execute() options parameter to accept the following options keys:
- :env to pass an environment Hash to Kernel#system
- :options to pass an options Hash to Kernel#system
### v1.2.0
- add :clone option to Environment#clone to control exactly which Environment attributes are cloned
- allow nil to be passed in to Environment#build_root=
### v1.1.0
- Change Cache#up_to_date?() and #register_build() to accept a single target
file or an array of target file names
+23 -26
View File
@@ -1,38 +1,36 @@
PATH
remote: .
specs:
rscons (1.10.0)
json (~> 1.0)
rscons (1.17.0)
json (>= 1.8, < 3.0)
GEM
remote: https://rubygems.org/
specs:
diff-lcs (1.2.5)
docile (1.1.5)
json (1.8.3)
rake (10.4.2)
rdoc (4.2.0)
json (~> 1.4)
redcarpet (3.3.3)
rspec (3.4.0)
rspec-core (~> 3.4.0)
rspec-expectations (~> 3.4.0)
rspec-mocks (~> 3.4.0)
rspec-core (3.4.1)
rspec-support (~> 3.4.0)
rspec-expectations (3.4.0)
diff-lcs (1.3)
docile (1.3.2)
json (2.2.0)
rake (12.3.2)
rdoc (6.1.1)
rspec (3.8.0)
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-core (3.8.2)
rspec-support (~> 3.8.0)
rspec-expectations (3.8.4)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-mocks (3.4.0)
rspec-support (~> 3.8.0)
rspec-mocks (3.8.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-support (3.4.1)
simplecov (0.11.1)
rspec-support (~> 3.8.0)
rspec-support (3.8.2)
simplecov (0.15.0)
docile (~> 1.1.0)
json (~> 1.8)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
yard (0.8.7.6)
simplecov-html (0.10.2)
yard (0.9.20)
PLATFORMS
ruby
@@ -41,11 +39,10 @@ PLATFORMS
DEPENDENCIES
rake
rdoc
redcarpet
rscons!
rspec
simplecov
yard
BUNDLED WITH
1.10.6
1.17.3
+46 -165
View File
@@ -1,7 +1,7 @@
# Rscons
Rscons is a software construction framework inspired by SCons and implemented
in Ruby.
Rscons (https://github.com/holtrop/rscons) is a software construction framework
inspired by SCons and implemented in Ruby.
[![Gem Version](https://badge.fury.io/rb/rscons.png)](http://badge.fury.io/rb/rscons)
@@ -30,7 +30,7 @@ task block or split among multiple tasks.
```ruby
Rscons::Environment.new do |env|
env["CFLAGS"] << "-Wall"
env.Program("program", Dir["src/**/*.c"])
env.Program("program", Rscons.glob("src/**/*.c"))
end
```
@@ -39,7 +39,7 @@ end
```ruby
Rscons::Environment.new do |env|
env["DFLAGS"] << "-Wall"
env.Program("program", Dir["src/**/*.d"])
env.Program("program", Rscons.glob("src/**/*.d"))
end
```
@@ -51,7 +51,7 @@ main_env = Rscons::Environment.new do |env|
env.build_dir("src", "build/main")
env["CFLAGS"] = ["-DSOME_DEFINE", "-O3"]
env["LIBS"] = ["SDL"]
env.Program("program", Dir["src/**/*.cc"])
env.Program("program", Rscons.glob("src/**/*.cc"))
end
debug_env = main_env.clone do |env|
@@ -59,7 +59,7 @@ debug_env = main_env.clone do |env|
env.build_dir("src", "build/debug")
env["CFLAGS"] -= ["-O3"]
env["CFLAGS"] += ["-g", "-O0"]
env.Program("program-debug", Dir["src/**/*.cc"])
env.Program("program-debug", Rscons.glob("src/**/*.cc"))
end
```
@@ -86,7 +86,7 @@ end
Rscons::Environment.new do |env|
env.add_builder(GenerateFoo.new)
env.GenerateFoo("foo.h", [])
env.Program("a.out", Dir["*.c"])
env.Program("a.out", Rscons.glob("*.c"))
end
```
@@ -111,6 +111,15 @@ Rscons::Environment.new do |env|
end
```
The `Cache#up_to_date?` method accepts an optional 5th parameter which is an
options Hash. The `:debug` option can be specified in this Hash with a value
of `true` to aid in debugging builders while developing them. For example:
```ruby
unless cache.up_to_date?(target, cmd, sources, env, debug: true)
end
```
### Example: Custom Builder That Generates Multiple Output Files
```ruby
@@ -191,7 +200,7 @@ Rscons::Environment.new do |env|
end
end
env.build_dir("src", "build")
env.Program("program", Dir["**/*.cc"])
env.Program("program", Rscons.glob("**/*.cc"))
end
```
@@ -199,7 +208,7 @@ end
```ruby
Rscons::Environment.new do |env|
env.Library("mylib.a", Dir["src/**/*.c"])
env.Library("mylib.a", Rscons.glob("src/**/*.c"))
end
```
@@ -230,14 +239,15 @@ An Environment consists of:
* a collection of user-defined build targets
* a collection of user-defined build hooks
When cloning an environment, by default the construction variables and builders
are cloned, but the new environment does not inherit any of the targets, build
hooks, build directories, or the build root from the source environment.
When cloning an environment, by default the construction variables, builders,
build hooks, build directories, and build root are cloned, but the new
environment does not inherit any of the registered build targets.
The set of environment attributes that are cloned is controllable via the
`:clone` option to the `#clone` method.
For example, `env.clone(clone: :all)` will include construction variables,
builders, build hooks, build directories, and the build root.
For example, `env.clone(clone: [:variables, :builders])` will include
construction variables, and builders but not build hooks, build directories, or
the build root.
The set of pending targets is never cloned.
@@ -326,7 +336,7 @@ env.Install("dist/share", "share")
```ruby
env.Library(target, sources)
# Example
env.Library("lib.a", Dir["src/**/*.c"])
env.Library("lib.a", Rscons.glob("src/**/*.c"))
```
The Library builder creates a static library archive from the given source
@@ -361,7 +371,7 @@ preprocessed output to the target file.
```ruby
env.Program(target, sources)
# Example
env.Program("myprog", Dir["src/**/*.cc"])
env.Program("myprog", Rscons.glob("src/**/*.cc"))
```
The Program builder compiles and links the given sources to an executable file.
@@ -374,7 +384,7 @@ This can be controlled with the `PROGSUFFIX` construction variable.
```ruby
env.SharedLibrary(target, sources)
# Example
env.SharedLibrary("mydll", Dir["src/**/*.cc"])
env.SharedLibrary("mydll", Rscons.glob("src/**/*.cc"))
```
The SharedLibrary builder compiles and links the given sources to a dynamically
@@ -410,14 +420,14 @@ construction variables can be overridden by the user.
| ARFLAGS | Array | Static library archiver flags | ["rcs"] |
| AS | String | Assembler executable | "${CC}" |
| ASCMD | Array | Assembler command line | ["${AS}", "-c", "-o", "${_TARGET}", "${ASDEPGEN}", "${INCPREFIX}${ASPPPATH}", "${ASPPFLAGS}", "${ASFLAGS}", "${_SOURCES}"] |
| ASDEPGEN | Array | Assembly dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}"] |
| ASDEPGEN | Array | Assembly dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}", "-MT", "TARGET"] |
| ASFLAGS | Array | Assembler flags | [] |
| ASPPFLAGS | Array | Assembler preprocessor flags | ["${CPPFLAGS}"] |
| ASPPPATH | Array | Assembler preprocessor path | ["${CPPPATH}"] |
| ASSUFFIX | Array | Assembly file suffixes | [".S"] |
| CC | String | C compiler executable | "gcc" |
| CCCMD | Array | C compiler command line | ["${CC}", "-c", "-o", "${_TARGET}", "${CCDEPGEN}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${CFLAGS}", "${CCFLAGS}", "${_SOURCES}"] |
| CCDEPGEN | Array | C compiler dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}"] |
| CCDEPGEN | Array | C compiler dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}", "-MT", "TARGET"] |
| CCFLAGS | Array | Common flags for both C and C++ compiler | [] |
| CFLAGS | Array | C compiler flags | [] |
| CPP_CMD | Array | Preprocess command line | ["${_PREPROCESS_CC}", "-E", "${_PREPROCESS_DEPGEN}", "-o", "${_TARGET}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${_SOURCES}"] |
@@ -429,12 +439,13 @@ construction variables can be overridden by the user.
| CSUFFIX | Array | C source file suffixes | [".c"] |
| CXX | String | C++ compiler executable | "g++" |
| CXXCMD | Array | C++ compiler command line | ["${CXX}", "-c", "-o", "${_TARGET}", "${CXXDEPGEN}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${CXXFLAGS}", "${CCFLAGS}", "${_SOURCES}"] |
| CXXDEPGEN | Array | C++ compiler dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}"] |
| CXXDEPGEN | Array | C++ compiler dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}", "-MT", "TARGET"] |
| CXXFLAGS | Array | C++ compiler flags | [] |
| CXXSUFFIX | Array | C++ source file suffixes | [".cc", ".cpp", ".cxx", ".C"] |
| D_IMPORT_PATH | Array | D compiler import path | [] |
| DC | String | D compiler executable | "gdc" |
| DCCMD | Array | D compiler command line | ["${DC}", "-c", "-o", "${_TARGET}", "${INCPREFIX}${D_IMPORT_PATH}", "${DFLAGS}", "${_SOURCES}"] |
| DCCMD | Array | D compiler command line | ["${DC}", "-c", "-o", "${_TARGET}", "${DDEPGEN}", "${INCPREFIX}${D_IMPORT_PATH}", "${DFLAGS}", "${_SOURCES}"] |
| DDEPGEN | Array | D compiler dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}", "-MT", "TARGET"] |
| DEPFILESUFFIX | String | Dependency file suffix for Makefile-style dependency rules emitted by the compiler (used internally for temporary dependency files used to determine a source file's dependencies) | ".mf" |
| DFLAGS | Array | D compiler flags | [] |
| DISASM_CMD | Array | Disassemble command line | ["${OBJDUMP}", "${DISASM_FLAGS}", "${_SOURCES}"] |
@@ -455,16 +466,14 @@ construction variables can be overridden by the user.
| LIBSUFFIX | String/Array | Default static library file suffix | ".a" |
| OBJDUMP | String | Objdump executable | "objdump" |
| OBJSUFFIX | String/Array | Default object file suffix | ".o" |
| This name will include variant components. |
| The user should not change this construction variable. | Determined at runtime. |
| PROGSUFFIX | String | Default program suffix. | Windows: ".exe", POSIX: "" |
| SHCC | String | Shared object C compiler | "${CC}" |
| SHCCCMD | Array | Shared object C compiler command line | ["${SHCC}", "-c", "-o", "${_TARGET}", "${CCDEPGEN}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${SHCFLAGS}", "${SHCCFLAGS}", "${_SOURCES}"] |
| SHCCFLAGS | Array | Shared object C and C++ compiler flags | Windows: ["${CCFLAGS}"], POSIX: ["${CCFLAGS}", -fPIC"] |
| SHCFLAGS | Array | Shared object C compiler flags | [] |
| SHCFLAGS | Array | Shared object C compiler flags | ["${CFLAGS}"] |
| SHCXX | String | Shared object C++ compiler | "${CXX}" |
| SHCXXCMD | Array | Shared object C++ compiler command line | ["${SHCXX}", "-c", "-o", "${_TARGET}", "${CXXDEPGEN}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${SHCXXFLAGS}", "${SHCCFLAGS}", "${_SOURCES}"] |
| SHCXXFLAGS | Array | Shared object C++ compiler flags | [] |
| SHCXXFLAGS | Array | Shared object C++ compiler flags | ["${CXXFLAGS}"] |
| SHDC | String | Shared object D compiler | "gdc" |
| SHDCCMD | Array | Shared object D compiler command line | ["${SHDC}", "-c", "-o", "${_TARGET}", "${INCPREFIX}${D_IMPORT_PATH}", "${SHDFLAGS}", "${_SOURCES}"] |
| SHDFLAGS | Array | Shared object D compiler flags | Windows: ["${DFLAGS}"], POSIX: ["${DFLAGS}", "-fPIC"] |
@@ -549,9 +558,19 @@ end
You can pass multiple dependency files to `Environment#depends`:
```ruby
env.depends("my_app", "config/link.ld", "README.txt", *Dir.glob("assets/**/*"))
env.depends("my_app", "config/link.ld", "README.txt", *Rscons.glob("assets/**/*"))
```
### Command-Line Variables
Variables can be specified on the rscons command line. For example:
```
rscons VAR=val
```
These variables are accessible in a global VarSet called `Rscons.vars`.
### Construction Variable Naming
* uppercase strings - the default construction variables that Rscons uses
@@ -561,145 +580,7 @@ env.depends("my_app", "config/link.ld", "README.txt", *Dir.glob("assets/**/*"))
### API documentation
Documentation for the complete Rscons API can be found at
http://rubydoc.info/github/holtrop/rscons/frames.
## Release Notes
### v1.10.0
#### New Features
- #23 - add parallelization - builds are now parallelized by default
- #31 - add LEXSUFFIX, YACCSUFFIX construction variables
- #30 - place object files for absolute source paths under build_root
- #28 - support redirecting standard output using the Command builder
- Always use a build root and default it to "build"
- Add builder features
- #8 - add SharedObject and SharedLibrary builders
#### Fixes
- expand target and source paths before calling Builder#create_build_target
- #29 - fix PROGSUFFIX handling
### v1.9.3
- Environment#parse_flags should put -std=XXX flags in CCFLAGS, not CFLAGS
### v1.9.2
- allow phony targets in conjunction with build roots
### v1.9.1
- change *SUFFIX defaults to arrays
- add various C++ file suffixes
- use ${INCPREFIX} instead of hard-coded "-I" in Preprocess builder
### v1.9.0
#### New Features
- #6 - add Install and Copy builders
- #22 - allow overriding Command builder short description with CMD_DESC variable
- #24 - add "rscons" executable
- #25 - add support for phony targets given as Symbols instead of Strings
- #26 - support registering multiple build targets with the same target name
- #27 - add Directory builder
#### Fixes
- #20 - fix variable references that expand to arrays in build target sources
- #21 - rework Preprocess builder to consider deep dependencies
- fix Rscons.set_suffix to append the given suffix if the filename has none
- remove ${CFLAGS} from default CPP_CMD
### v1.8.1
- fix Environment#dump when construction variables are symbols
### v1.8.0
- new Command builder to execute arbitrary user commands
- new SimpleBuilder class
- create new builders quickly by passing a block to Environment#add_builder
- improved YARD documentation
- add Environment#dump to debug Environment construction variables
### v1.7.0
- allow build hooks to register new build targets
- add post-build hooks (register with Environment#add_post_build_hook)
- clear all build targets after processing an Environment
- allow trailing slashes in arguments to Environment#build_dir
### v1.6.1
- add DEPFILESUFFIX construction variable to override dependency file suffix
- fix Environment#depends to expand its arguments for construction variables
### v1.6.0
- support lambdas as construction variable values
### v1.5.0
- add "json" as a runtime dependency
- update construction variables to match SCons more closely
- add CPPDEFPREFIX, INCPREFIX, CPPDEFINES, CCFLAGS, LIBDIRPREFIX, and LIBLINKPREFIX
- add Environment#shell
- add Environment#parse_flags, #parse_flags!, #merge_flags
- unbuffer $stdout by default
- add PROGSUFFIX construction variable (defaults to .exe on MinGW/Cygwin)
- add Rscons::BuildTarget and Builder#create_build_target
- update specs to RSpec 3.x and fix to run on MinGW/Cygwin/Linux
- add YARD documentation to get to 100% coverage
### v1.4.3
- fix builders properly using construction variable overrides
- expand nil construction variables to empty strings
### v1.4.2
- add Environment#expand_path
- expand construction variable references in builder targets and sources before invoking builder
### v1.4.1
- fix invoking a builder with no sources while a build root defined
### v1.4.0
- add CFile builder
- add Disassemble builder
- add Preprocess builder
- pass the Environment object to build hooks in the :env key of the build_op parameter
- expand target/source paths beginning with "^/" to be relative to the Environment's build root
- many performance improvements, including:
- use JSON instead of YAML for the cache to improve loading speed (Issue #7)
- store a hash of the build command instead of the full command contents in the cache
- implement copy-on-write semantics for construction variables when cloning Environments
- only load the cache once instead of on each Environment#process
- only write the cache when something has changed
- fix Cache#mkdir_p to handle relative paths (Issue #5)
- flush the cache to disk if a builder raises an exception (Issue #4)
### v1.3.0
- change Environment#execute() options parameter to accept the following options keys:
- :env to pass an environment Hash to Kernel#system
- :options to pass an options Hash to Kernel#system
### v1.2.0
- add :clone option to Environment#clone to control exactly which Environment attributes are cloned
- allow nil to be passed in to Environment#build_root=
### v1.1.0
- Change Cache#up_to_date?() and #register_build() to accept a single target
file or an array of target file names
http://www.rubydoc.info/github/holtrop/rscons/master.
## Contributing
+2
View File
@@ -1,3 +1,5 @@
#!/usr/bin/env ruby
require "rscons/cli"
Rscons::Cli.run(ARGV)
+2 -2
View File
@@ -1,5 +1,5 @@
Rscons::Environment.new do |env|
env.append('CPPPATH' => Dir['src/**/*/'].sort)
env.append('CPPPATH' => Rscons.glob('src/**'))
env.build_dir(%r{^src/([^/]+)/}, 'build_\\1/')
env.Program('build_dir.exe', Dir['src/**/*.c'])
env.Program('build_dir.exe', Rscons.glob('src/**/*.c'))
end
@@ -6,7 +6,7 @@ end
Rscons::Environment.new(echo: :command) do |env|
env.add_builder(MyObject.new)
env.append('CPPPATH' => Dir['src/**/*/'].sort)
env.append('CPPPATH' => Rscons.glob('src/**'))
env.add_build_hook do |build_op|
if build_op[:builder].name == "MyObject" && build_op[:sources].first =~ %r{one\.c}
build_op[:vars]["CFLAGS"] << "-O1"
+2 -2
View File
@@ -1,6 +1,6 @@
env = Rscons::Environment.new do |env|
env.append('CPPPATH' => Dir['src/**/*/'].sort)
env.append('CPPPATH' => Rscons.glob('src/**/*/'))
env.build_dir("src", "build")
env.build_root = "build_root"
env.Program('build_dir.exe', Dir['src/**/*.c'])
env.Program('build_dir.exe', Rscons.glob('src/**/*.c'))
end
+2 -2
View File
@@ -1,5 +1,5 @@
Rscons::Environment.new(echo: :command) do |env|
env.append('CPPPATH' => Dir['src/**/*/'].sort)
env.append('CPPPATH' => Rscons.glob('src/**/*/'))
env.build_dir(%r{^src/([^/]+)/}, 'build_\\1/')
env.add_build_hook do |build_op|
if build_op[:target] =~ %r{build_one/.*\.o}
@@ -8,5 +8,5 @@ Rscons::Environment.new(echo: :command) do |env|
build_op[:vars]["CFLAGS"] << "-O2"
end
end
env.Program('build_hook.exe', Dir['src/**/*.c'].sort)
env.Program('build_hook.exe', Rscons.glob('src/**/*.c'))
end
+2 -2
View File
@@ -1,8 +1,8 @@
Rscons::Environment.new(echo: :command) do |env|
env.append('CPPPATH' => Dir['src/**/*/'].sort)
env.append('CPPPATH' => Rscons.glob('src/**').sort)
env.build_root = "build_root"
FileUtils.mkdir_p(env.build_root)
FileUtils.mv("src/one/one.c", "build_root")
env.Object("^/one.o", "^/one.c")
env.Program("build_dir.exe", Dir['src/**/*.c'] + ["^/one.o"])
env.Program("build_dir.exe", Rscons.glob('src/**/*.c') + ["^/one.o"])
end
+2 -2
View File
@@ -1,6 +1,6 @@
Rscons::Environment.new do |env|
env["CSUFFIX"] = %w[.yargh .c]
env["CFLAGS"] += %w[-x c]
env["CPPPATH"] += Dir["src/**/"]
env.Program("build_dir.exe", Dir["src/**/*.{c,yargh}"])
env["CPPPATH"] += Rscons.glob("src/**")
env.Program("build_dir.exe", Rscons.glob("src/**/*.{c,yargh}"))
end
@@ -0,0 +1,5 @@
Rscons::Environment.new do |env|
env.echo = :command
env.Install("inst.exe", "install.rb")
end
@@ -0,0 +1,13 @@
class MyBuilder < Rscons::Builder
def run(options)
env, target = options.values_at(:env, :target)
env.print_builder_run_message("MyBuilder #{target}", "MyBuilder #{target} command")
target
end
end
Rscons::Environment.new do |env|
env.echo = :command
env.add_builder(MyBuilder.new)
env.MyBuilder("foo")
end
+2 -2
View File
@@ -1,6 +1,6 @@
Rscons::Environment.new do |env|
env.append('CPPPATH' => Dir['src/**/*/'].sort)
env.append('CPPPATH' => Rscons.glob('src/**'))
env.build_dir("src2", "build")
env.build_root = "build_root"
env.Program('build_dir.exe', Dir['src/**/*.c'])
env.Program('build_dir.exe', Rscons.glob('src/**/*.c'))
end
+2 -2
View File
@@ -1,6 +1,6 @@
Rscons::Environment.new do |env|
env.append("CPPPATH" => Dir["src/**/*/"].sort)
env.append("CPPPATH" => Rscons.glob("src/**"))
env.build_dir("src/one/", "build_one/")
env.build_dir("src/two", "build_two")
env.Program("build_dir.exe", Dir["src/**/*.c"])
env.Program("build_dir.exe", Rscons.glob("src/**/*.c"))
end
+1 -1
View File
@@ -10,6 +10,6 @@ env1 = Rscons::Environment.new(echo: :command) do |env|
env.Program('program.exe', Dir['src/*.c'])
end
env2 = env1.clone(clone: :all) do |env|
env2 = env1.clone do |env|
env.Program('program2.exe', Dir['src/*.c'])
end
+3 -1
View File
@@ -3,6 +3,8 @@ Rscons::Environment.new do |env|
[],
"CMD" => %w[ruby gen.rb ${_TARGET}],
"CMD_DESC" => "Generating")
env.build_after("#{env.build_root}/program.o", "inc.c")
env["build_root"] = env.build_root
env["inc_c"] = "inc.c"
env.build_after("${build_root}/program.o", "${inc_c}")
env.Program("program.exe", ["program.c", "inc.c"])
end
+14
View File
@@ -0,0 +1,14 @@
Rscons::Environment.new do |env|
env["build_root"] = env.build_root
env["inc_h"] = "inc.h"
env.Copy("copy_inc.h", "${inc_h}")
env.depends("${build_root}/program.o", "${inc_h}")
env.Program("program.exe", ["program.c", "inc.c"])
env.Command("inc.c",
[],
"CMD" => %w[ruby gen.rb ${_TARGET}],
"CMD_DESC" => "Generating")
env.produces("inc.c", "inc.h")
end
+1 -1
View File
@@ -1,3 +1,3 @@
Rscons::Environment.new(echo: :command) do |env|
env.Program("hello-d.exe", Dir["*.d"])
env.Program("hello-d.exe", Rscons.glob("*.d"))
end
+2 -1
View File
@@ -1,6 +1,7 @@
import std.stdio;
import mod;
void main()
{
writeln("Hello from D!");
writeln("Hello from D, value is ", Klass.VAL, "!");
}
+4
View File
@@ -0,0 +1,4 @@
class Klass
{
enum VAL = 42;
};
+1 -1
View File
@@ -1,4 +1,4 @@
Rscons::Environment.new(echo: :command) do |env|
env["ARCMD"] = %w[ar rcf ${_TARGET} ${_SOURCES}]
env.Library("lib.a", Dir["*.c"].sort)
env.Library("lib.a", Rscons.glob("*.c"))
end
+3 -3
View File
@@ -1,11 +1,11 @@
Rscons::Environment.new do |env|
env["CPPPATH"] << "src/lib"
libmine = env.SharedLibrary("mine", Dir["src/lib/*.c"])
libmine = env.SharedLibrary("mine", Rscons.glob("src/lib/*.c"))
env.Program("test-shared.exe",
Dir["src/*.c"],
Rscons.glob("src/*.c"),
"LIBPATH" => %w[.],
"LIBS" => %w[mine])
env.build_after("test-shared.exe", libmine.to_s)
env.Program("test-static.exe",
Dir["src/**/*.c"])
Rscons.glob("src/**/*.c"))
end
@@ -1,11 +1,11 @@
Rscons::Environment.new do |env|
env["CPPPATH"] << "src/lib"
libmine = env.SharedLibrary("mine", Dir["src/lib/*.cc"])
libmine = env.SharedLibrary("mine", Rscons.glob("src/lib/*.cc"))
env.Program("test-shared.exe",
Dir["src/*.cc"],
Rscons.glob("src/*.cc"),
"LIBPATH" => %w[.],
"LIBS" => %w[mine])
env.build_after("test-shared.exe", libmine.to_s)
env.Program("test-static.exe",
Dir["src/**/*.cc"])
Rscons.glob("src/**/*.cc"))
end
@@ -1,8 +1,8 @@
Rscons::Environment.new do |env|
env["CPPPATH"] << "src/lib"
libmine = env.SharedLibrary("mine", Dir["src/lib/*.d"])
libmine = env.SharedLibrary("mine", Rscons.glob("src/lib/*.d"))
env.Program("test-shared.exe",
Dir["src/*.c"],
Rscons.glob("src/*.c"),
"LIBPATH" => %w[.],
"LIBS" => %w[mine])
env.build_after("test-shared.exe", libmine.to_s)
@@ -1,12 +1,12 @@
Rscons::Environment.new do |env|
env["CPPPATH"] << "src/lib"
env["SHLD"] = "gcc"
libmine = env.SharedLibrary("mine", Dir["src/lib/*.c"])
libmine = env.SharedLibrary("mine", Rscons.glob("src/lib/*.c"))
env.Program("test-shared.exe",
Dir["src/*.c"],
Rscons.glob("src/*.c"),
"LIBPATH" => %w[.],
"LIBS" => %w[mine])
env.build_after("test-shared.exe", libmine.to_s)
env.Program("test-static.exe",
Dir["src/**/*.c"])
Rscons.glob("src/**/*.c"))
end
+34
View File
@@ -0,0 +1,34 @@
class DebugBuilder < Rscons::Builder
def run(options)
target, sources, cache, env, vars = options.values_at(:target, :sources, :cache, :env, :vars)
command = %W[gcc -c -o #{target} #{sources.first}]
if Rscons.vars["command_change"]
command += %w[-Wall]
end
if Rscons.vars["new_dep"]
sources += ["extra"]
end
if Rscons.vars["strict_deps1"]
sources += ["extra"]
strict_deps = true
end
if Rscons.vars["strict_deps2"]
sources = ["extra"] + sources
strict_deps = true
end
unless cache.up_to_date?(target, command, sources, env, debug: true, strict_deps: strict_deps)
desc = "#{self.class.name} #{target}"
return false unless env.execute(desc, command)
cache.register_build(target, command, sources, env)
end
target
end
end
Rscons::Environment.new do |env|
env.add_builder(DebugBuilder.new)
if Rscons.vars["new_user_dep"]
env.depends("foo.o", "new_dep")
end
env.DebugBuilder("foo.o", "simple.c")
end
+20
View File
@@ -0,0 +1,20 @@
class TestBuilder < Rscons::Builder
def run(options)
target, sources, cache, env = options.values_at(:target, :sources, :cache, :env)
command = Rscons::VarSet.new("A" => "a", "B" => "b")
unless cache.up_to_date?(target, command, sources, env)
File.open(target, "w") do |fh|
fh.puts("hi")
end
msg = "#{self.class.name} #{target}"
env.print_builder_run_message(msg, msg)
cache.register_build(target, command, sources, env)
end
target
end
end
Rscons::Environment.new do |env|
env.add_builder(TestBuilder.new)
env.TestBuilder("foo")
end
+7
View File
@@ -0,0 +1,7 @@
base_env = Rscons::Environment.new do |env|
env.n_threads = 165
end
my_env = base_env.clone
puts my_env.n_threads
+1 -1
View File
@@ -1,4 +1,4 @@
Rscons::Environment.new do |env|
env["sources"] = Dir["*.c"].sort
env["sources"] = Rscons.glob("*.c")
env.Program("simple.exe", "${sources}")
end
@@ -0,0 +1,5 @@
Rscons::Environment.new do |env|
env.Command("foo", "bar")
env.Command("bar", "baz")
env.Command("baz", "foo")
end
@@ -0,0 +1,3 @@
Rscons::Environment.new do |env|
env.Command("foo", "foo")
end
@@ -0,0 +1,22 @@
class TestBuilder < Rscons::Builder
def run(options)
target, env, vars, cache = options.values_at(:target, :env, :vars, :cache)
if target == "two"
return false unless File.exists?("one")
end
wait_time = env.expand_varref("${wait_time}", vars)
command = ["ruby", "-e", "require 'fileutils'; sleep #{wait_time}; FileUtils.touch('#{target}');"]
standard_threaded_build("TestBuilder", target, command, [], env, cache)
end
def finalize(options)
standard_finalize(options)
end
end
Rscons::Environment.new do |env|
env.add_builder(TestBuilder.new)
env.TestBuilder("one", [], "wait_time" => "3")
env.TestBuilder("two", [], "wait_time" => "0")
env.depends("two", "one")
end
@@ -0,0 +1,20 @@
class Fail < Rscons::Builder
def run(options)
target, cache, env, vars = options.values_at(:target, :cache, :env, :vars)
wait_time = env.expand_varref("${wait_time}", vars)
ruby_command = %[sleep #{wait_time}; exit 2]
command = %W[ruby -e #{ruby_command}]
standard_threaded_build("Fail #{target}", target, command, [], env, cache)
end
def finalize(options)
standard_finalize(options)
end
end
Rscons::Environment.new do |env|
env.add_builder(Fail.new)
4.times do |i|
wait_time = i + 1
env.Fail("foo_#{wait_time}", [], "wait_time" => wait_time.to_s)
end
end
+40
View File
@@ -1,3 +1,4 @@
require_relative "rscons/ansi"
require_relative "rscons/build_target"
require_relative "rscons/builder"
require_relative "rscons/cache"
@@ -50,6 +51,15 @@ module Rscons
# The number of threads to use when scheduling subprocesses.
attr_accessor :n_threads
# @return [Boolean]
# Whether to output ANSI color escape sequences.
attr_accessor :do_ansi_color
# @since 1.16.0
# @return [VarSet]
# Access any variables set on the rscons command-line.
attr_reader :vars
# Remove all generated files.
#
# @return [void]
@@ -160,6 +170,35 @@ module Rscons
@command_executer = val
end
# Return a list of paths matching the specified pattern(s).
#
# @since 1.16.0
#
# 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
private
# Determine the number of threads to use by default.
@@ -198,6 +237,7 @@ module Rscons
end
@n_threads = determine_n_threads
@vars = VarSet.new
end
+53
View File
@@ -0,0 +1,53 @@
module Rscons
# Module to contain logic to write ANSI color escape codes.
module Ansi
class << self
# Write a message to an IO with ANSI escape codes.
#
# @param io [IO]
# The IO to write to.
# @param message [Array<String, Symbol>]
# Strings to be printed, with Symbols representing ANSI escape codes.
#
# @return [void]
def write(io, *message)
do_color = Rscons.do_ansi_color
if do_color.nil?
do_color = do_ansi?(io)
end
out = ""
message.each do |m|
if m.is_a?(String)
out += m
elsif do_color
case m
when :red
out += "\e[0;31m"
when :cyan
out += "\e[0;36m"
when :reset
out += "\e[0m"
end
end
end
io.write(out)
end
private
# Determine whether to output ANSI color escape codes.
#
# @return [Boolean]
# Whether to output ANSI color escape codes.
def do_ansi?(io)
if RUBY_PLATFORM =~ /mingw/
(ENV["TERM"] == "xterm") && %w[fifo characterSpecial].include?(io.stat.ftype)
else
io.tty?
end
end
end
end
end
+1 -1
View File
@@ -231,7 +231,7 @@ module Rscons
# @param env [Environment] The Environment executing the builder.
# @param cache [Cache] The Cache object.
# @param options [Hash] Options.
# @options options [String] :stdout
# @option options [String] :stdout
# File name to redirect standard output to.
#
# @return [String,ThreadedCommand]
+2 -2
View File
@@ -17,10 +17,10 @@ module Rscons
if File.directory?(target)
target
elsif File.exists?(target)
$stderr.puts "Error: `#{target}' already exists and is not a directory"
Ansi.write($stderr, :red, "Error: `#{target}' already exists and is not a directory", :reset, "\n")
false
else
puts "Directory #{target}"
env.print_builder_run_message("Directory #{target}", nil)
cache.mkdir_p(target)
target
end
+1 -1
View File
@@ -43,7 +43,7 @@ module Rscons
# Check the cache and copy if necessary
unless cache.up_to_date?(dest, :Copy, [src], env)
unless printed_message
puts "#{name} #{target}"
env.print_builder_run_message("#{name} #{target}", nil)
printed_message = true
end
cache.mkdir_p(File.dirname(dest))
+3 -2
View File
@@ -54,8 +54,9 @@ module Rscons
'DC' => 'gdc',
'DFLAGS' => [],
'DSUFFIX' => ['.d'],
'DDEPGEN' => ['-MMD', '-MF', '${_DEPFILE}'],
'D_IMPORT_PATH' => [],
'DCCMD' => ['${DC}', '-c', '-o', '${_TARGET}', '${INCPREFIX}${D_IMPORT_PATH}', '${DFLAGS}', '${_SOURCES}'],
'DCCMD' => ['${DC}', '-c', '-o', '${_TARGET}', '${DDEPGEN}', '${INCPREFIX}${D_IMPORT_PATH}', '${DFLAGS}', '${_SOURCES}'],
}
end
@@ -114,7 +115,7 @@ module Rscons
if options[:command_status]
target, deps, cache, env, vars = options.values_at(:target, :sources, :cache, :env, :vars)
if File.exists?(vars['_DEPFILE'])
deps += Environment.parse_makefile_deps(vars['_DEPFILE'], target)
deps += Environment.parse_makefile_deps(vars['_DEPFILE'])
FileUtils.rm_f(vars['_DEPFILE'])
end
cache.register_build(target, options[:tc].command, deps.uniq, env)
+1 -1
View File
@@ -55,7 +55,7 @@ module Rscons
if options[:command_status]
target, deps, cache, env, vars = options.values_at(:target, :sources, :cache, :env, :vars)
if File.exists?(vars['_DEPFILE'])
deps += Environment.parse_makefile_deps(vars['_DEPFILE'], nil)
deps += Environment.parse_makefile_deps(vars['_DEPFILE'])
FileUtils.rm_f(vars['_DEPFILE'])
end
cache.register_build(target, options[:tc].command, deps.uniq, env)
+3 -3
View File
@@ -24,11 +24,11 @@ module Rscons
'SHCCFLAGS' => ['${CCFLAGS}'] + pic_flags,
'SHCC' => '${CC}',
'SHCFLAGS' => [],
'SHCFLAGS' => ['${CFLAGS}'],
'SHCCCMD' => ['${SHCC}', '-c', '-o', '${_TARGET}', '${CCDEPGEN}', '${INCPREFIX}${CPPPATH}', '${CPPFLAGS}', '${SHCFLAGS}', '${SHCCFLAGS}', '${_SOURCES}'],
'SHCXX' => '${CXX}',
'SHCXXFLAGS' => [],
'SHCXXFLAGS' => ['${CXXFLAGS}'],
'SHCXXCMD' => ['${SHCXX}', '-c', '-o', '${_TARGET}', '${CXXDEPGEN}', '${INCPREFIX}${CPPPATH}', '${CPPFLAGS}', '${SHCXXFLAGS}', '${SHCCFLAGS}', '${_SOURCES}'],
'SHDC' => 'gdc',
@@ -100,7 +100,7 @@ module Rscons
if options[:command_status]
target, deps, cache, env, vars = options.values_at(:target, :sources, :cache, :env, :vars)
if File.exists?(vars['_DEPFILE'])
deps += Environment.parse_makefile_deps(vars['_DEPFILE'], target)
deps += Environment.parse_makefile_deps(vars['_DEPFILE'])
FileUtils.rm_f(vars['_DEPFILE'])
end
cache.register_build(target, options[:tc].command, deps.uniq, env)
+57 -19
View File
@@ -85,17 +85,14 @@ module Rscons
@lookup_checksums = {}
end
# Write the cache to disk to be loaded next time.
# Write the cache to disk.
#
# @return [void]
def write
if @dirty || (@cache["version"] != VERSION)
@cache["version"] = VERSION
File.open(CACHE_FILE, "w") do |fh|
fh.puts(JSON.dump(@cache))
end
@cache["version"] = VERSION
File.open(CACHE_FILE, "w") do |fh|
fh.puts(JSON.dump(@cache))
end
@dirty = false
end
# Check if target(s) are up to date.
@@ -111,6 +108,10 @@ module Rscons
# @param deps [Array<String>] List of the target's dependency files.
# @param env [Environment] The Rscons::Environment.
# @param options [Hash] Optional options.
# @option options [Boolean] :debug
# If turned on, this causes the Cache to print messages explaining why
# a build target is out of date. This could aid a builder author in
# debugging the operation of their builder.
# @option options [Boolean] :strict_deps
# Only consider a target up to date if its list of dependencies is
# exactly equal (including order) to the cached list of dependencies
@@ -133,39 +134,79 @@ module Rscons
unless Rscons.phony_target?(target)
# target file must exist on disk
return false unless File.exists?(target)
unless File.exists?(target)
if options[:debug]
puts "Target #{target} needs rebuilding because it does not exist on disk"
end
return false
end
end
# target must be registered in the cache
return false unless @cache["targets"].has_key?(cache_key)
unless @cache["targets"].has_key?(cache_key)
if options[:debug]
puts "Target #{target} needs rebuilding because there is no cached build information for it"
end
return false
end
unless Rscons.phony_target?(target)
# target must have the same checksum as when it was built last
return false unless @cache["targets"][cache_key]["checksum"] == lookup_checksum(target)
unless @cache["targets"][cache_key]["checksum"] == lookup_checksum(target)
if options[:debug]
puts "Target #{target} needs rebuilding because it has been changed on disk since being built last"
end
return false
end
end
# command used to build target must be identical
return false unless @cache["targets"][cache_key]["command"] == Digest::MD5.hexdigest(command.inspect)
unless @cache["targets"][cache_key]["command"] == Digest::MD5.hexdigest(command.inspect)
if options[:debug]
puts "Target #{target} needs rebuilding because the command used to build it has changed"
end
return false
end
cached_deps = @cache["targets"][cache_key]["deps"] || []
cached_deps_fnames = cached_deps.map { |dc| dc["fname"] }
if options[:strict_deps]
# depedencies passed in must exactly equal those in the cache
return false unless deps == cached_deps_fnames
unless deps == cached_deps_fnames
if options[:debug]
puts "Target #{target} needs rebuilding because the :strict_deps option is given and the set of dependencies does not match the previous set of dependencies"
end
return false
end
else
# all dependencies passed in must exist in cache (but cache may have more)
return false unless (Set.new(deps) - Set.new(cached_deps_fnames)).empty?
unless (Set.new(deps) - Set.new(cached_deps_fnames)).empty?
if options[:debug]
puts "Target #{target} needs rebuilding because there are new dependencies"
end
return false
end
end
# set of user dependencies must match
user_deps = env.get_user_deps(target) || []
cached_user_deps = @cache["targets"][cache_key]["user_deps"] || []
cached_user_deps_fnames = cached_user_deps.map { |dc| dc["fname"] }
return false unless user_deps == cached_user_deps_fnames
unless user_deps == cached_user_deps_fnames
if options[:debug]
puts "Target #{target} needs rebuilding because the set of user-specified dependency files has changed"
end
return false
end
# all cached dependencies must have their checksums match
(cached_deps + cached_user_deps).each do |dep_cache|
return false unless dep_cache["checksum"] == lookup_checksum(dep_cache["fname"])
unless dep_cache["checksum"] == lookup_checksum(dep_cache["fname"])
if options[:debug]
puts "Target #{target} needs rebuilding because dependency file #{dep_cache["fname"]} has changed"
end
return false
end
end
end
@@ -205,7 +246,6 @@ module Rscons
}
end,
}
@dirty = true
end
end
@@ -228,9 +268,8 @@ module Rscons
next if parts[i] == ""
subpath = File.join(*parts[0, i + 1])
unless File.exists?(subpath)
FileUtils.mkdir(subpath)
FileUtils.mkdir_p(subpath)
@cache["directories"][subpath] = true
@dirty = true
end
end
end
@@ -271,7 +310,6 @@ module Rscons
@cache["targets"] ||= {}
@cache["directories"] ||= {}
@lookup_checksums = {}
@dirty = false
end
# Return a file's checksum, or the previously calculated checksum for
+15 -1
View File
@@ -39,6 +39,15 @@ module Rscons
Rscons.n_threads = n_threads.to_i
end
opts.on("-r", "--color MODE", "Set color mode (off, auto, force)") do |color_mode|
case color_mode
when "off"
Rscons.do_ansi_color = false
when "force"
Rscons.do_ansi_color = true
end
end
opts.on_tail("--version", "Show version") do
puts "Rscons version #{Rscons::VERSION}"
exit 0
@@ -51,6 +60,12 @@ module Rscons
end.parse!(argv)
argv.each do |arg|
if arg =~ /^([^=]+)=(.*)$/
Rscons.vars[$1] = $2
end
end
if rsconsfile
unless File.exists?(rsconsfile)
$stderr.puts "Cannot read #{rsconsfile}"
@@ -70,7 +85,6 @@ module Rscons
begin
load rsconsfile
rescue Rscons::BuildError => e
$stderr.puts e.message
exit 1
end
+113 -40
View File
@@ -17,6 +17,11 @@ module Rscons
# @return [String] The build root.
attr_reader :build_root
# @return [Integer]
# The number of threads to use for this Environment. If nil (the
# default), the global Rscons.n_threads default value will be used.
attr_writer :n_threads
# Set the build root.
#
# @param build_root [String] The build root.
@@ -40,8 +45,9 @@ module Rscons
def initialize(options = {})
@threaded_commands = Set.new
@registered_build_dependencies = {}
@side_effects = {}
@varset = VarSet.new
@job_set = JobSet.new(@registered_build_dependencies)
@job_set = JobSet.new(@registered_build_dependencies, @side_effects)
@user_deps = {}
@builders = {}
@build_dirs = []
@@ -73,9 +79,9 @@ module Rscons
# following:
# - :variables to clone construction variables (on by default)
# - :builders to clone the builders (on by default)
# - :build_root to clone the build root (off by default)
# - :build_dirs to clone the build directories (off by default)
# - :build_hooks to clone the build hooks (off by default)
# - :build_root to clone the build root (on by default)
# - :build_dirs to clone the build directories (on by default)
# - :build_hooks to clone the build hooks (on by default)
#
# If a block is given, the Environment object is yielded to the block and
# when the block returns, the {#process} method is automatically called.
@@ -84,7 +90,7 @@ module Rscons
#
# @return [Environment] The newly created {Environment} object.
def clone(options = {})
clone = options[:clone] || Set[:variables, :builders]
clone = options[:clone] || :all
clone = Set[:variables, :builders, :build_root, :build_dirs, :build_hooks] if clone == :all
clone = Set[] if clone == :none
clone = Set.new(clone) if clone.is_a?(Array)
@@ -101,7 +107,7 @@ module Rscons
env.append(@varset) if clone.include?(:variables)
env.build_root = @build_root if clone.include?(:build_root)
if clone.include?(:build_dirs)
@build_dirs.each do |src_dir, obj_dir|
@build_dirs.reverse.each do |src_dir, obj_dir|
env.build_dir(src_dir, obj_dir)
end
end
@@ -113,6 +119,7 @@ module Rscons
env.add_post_build_hook(&build_hook_block)
end
end
env.instance_variable_set(:@n_threads, @n_threads)
if block_given?
yield env
@@ -222,7 +229,7 @@ module Rscons
if src_dir.is_a?(String)
src_dir = src_dir.gsub("\\", "/").sub(%r{/*$}, "")
end
@build_dirs << [src_dir, obj_dir]
@build_dirs.unshift([src_dir, obj_dir])
end
# Return the file name to be built from +source_fname+ with suffix
@@ -297,13 +304,19 @@ module Rscons
# @return [void]
def process
cache = Cache.instance
failure = nil
begin
while @job_set.size > 0 or @threaded_commands.size > 0
targets_still_building = @threaded_commands.map do |tc|
tc.build_operation[:target]
if failure
@job_set.clear!
job = nil
else
targets_still_building = @threaded_commands.map do |tc|
tc.build_operation[:target]
end
job = @job_set.get_next_job_to_run(targets_still_building)
end
job = @job_set.get_next_job_to_run(targets_still_building)
# TODO: have Cache determine when checksums may be invalid based on
# file size and/or timestamp.
@@ -318,7 +331,9 @@ module Rscons
allow_delayed_execution: true,
setup_info: job[:setup_info])
unless result
raise BuildError.new("Failed to build #{job[:target]}")
failure = "Failed to build #{job[:target]}"
Ansi.write($stderr, :red, failure, :reset, "\n")
next
end
end
@@ -330,7 +345,8 @@ module Rscons
end
# If needed, do a blocking wait.
if (completed_tcs.empty? and job.nil?) or @threaded_commands.size >= Rscons.n_threads
if (@threaded_commands.size > 0) and
((completed_tcs.empty? and job.nil?) or (@threaded_commands.size >= n_threads))
completed_tcs << wait_for_threaded_commands
end
@@ -343,9 +359,11 @@ module Rscons
end
else
unless @echo == :command
$stdout.puts "Failed command was: #{command_to_s(tc.command)}"
print_failed_command(tc.command)
end
raise BuildError.new("Failed to build #{tc.build_operation[:target]}")
failure = "Failed to build #{tc.build_operation[:target]}"
Ansi.write($stderr, :red, failure, :reset, "\n")
break
end
end
@@ -353,6 +371,9 @@ module Rscons
ensure
cache.write
end
if failure
raise BuildError.new(failure)
end
end
# Clear all targets registered for the Environment.
@@ -364,12 +385,12 @@ module Rscons
# Expand a construction variable reference.
#
# @param varref [Array, String] Variable reference to expand.
# @param varref [nil, String, Array, Proc, Symbol, TrueClass, FalseClass] Variable reference to expand.
# @param extra_vars [Hash, VarSet]
# Extra variables to use in addition to (or replace) the Environment's
# construction variables when expanding the variable reference.
#
# @return [Array, String] Expansion of the variable reference.
# @return [nil, String, Array, Symbol, TrueClass, FalseClass] Expansion of the variable reference.
def expand_varref(varref, extra_vars = nil)
vars = if extra_vars.nil?
@varset
@@ -392,16 +413,12 @@ module Rscons
#
# @return [true,false,nil] Return value from Kernel.system().
def execute(short_desc, command, options = {})
if @echo == :command
puts command_to_s(command)
elsif @echo == :short
puts short_desc
end
print_builder_run_message(short_desc, command)
env_args = options[:env] ? [options[:env]] : []
options_args = options[:options] ? [options[:options]] : []
system(*env_args, *Rscons.command_executer, *command, *options_args).tap do |result|
unless result or @echo == :command
$stdout.puts "Failed command was: #{command_to_s(command)}"
print_failed_command(command)
end
end
end
@@ -445,6 +462,7 @@ module Rscons
user_deps = user_deps.map {|ud| expand_varref(ud)}
@user_deps[target] ||= []
@user_deps[target] = (@user_deps[target] + user_deps).uniq
build_after(target, user_deps)
end
# Manually record the given target(s) as needing to be built after the
@@ -476,13 +494,35 @@ module Rscons
targets = Array(targets)
prerequisites = Array(prerequisites)
targets.each do |target|
target = expand_path(expand_varref(target))
@registered_build_dependencies[target] ||= Set.new
prerequisites.each do |prerequisite|
prerequisite = expand_path(expand_varref(prerequisite))
@registered_build_dependencies[target] << prerequisite
end
end
end
# Manually record the given side effect file(s) as being produced when the
# named target is produced.
#
# @since 1.13.0
#
# @param target [String]
# Target of a build operation.
# @param side_effects [Array<String>]
# File(s) produced when the target file is produced.
#
# @return [void]
def produces(target, *side_effects)
target = expand_path(expand_varref(target))
side_effects = Array(side_effects).map do |side_effect|
expand_path(expand_varref(side_effect))
end.flatten
@side_effects[target] ||= []
@side_effects[target] += side_effects
end
# Return the list of user dependencies for a given target.
#
# @param target [String] Target file name.
@@ -634,7 +674,7 @@ module Rscons
call_build_hooks[:post]
else
unless @echo == :command
$stdout.puts "Failed command was: #{command_to_s(tc.command)}"
print_failed_command(tc.command)
end
end
end
@@ -821,10 +861,53 @@ module Rscons
varset_hash = @varset.to_h
varset_hash.keys.sort_by(&:to_s).each do |var|
var_str = var.is_a?(Symbol) ? var.inspect : var
puts "#{var_str} => #{varset_hash[var].inspect}"
Ansi.write($stdout, :cyan, var_str, :reset, " => #{varset_hash[var].inspect}\n")
end
end
# Get the number of threads to use for parallelized builds in this
# Environment.
#
# @return [Integer]
# Number of threads to use for parallelized builds in this Environment.
def n_threads
@n_threads || Rscons.n_threads
end
# Print the builder run message, depending on the Environment's echo mode.
#
# @param short_description [String]
# Builder short description, printed if the echo mode is :short.
# @param command [Array<String>]
# Builder command, printed if the echo mode is :command.
#
# @return [void]
def print_builder_run_message(short_description, command)
case @echo
when :command
if command.is_a?(Array)
message = command_to_s(command)
elsif command.is_a?(String)
message = command
elsif short_description.is_a?(String)
message = short_description
end
when :short
message = short_description if short_description
end
Ansi.write($stdout, :cyan, message, :reset, "\n") if message
end
# Print a failed command.
#
# @param command [Array<String>]
# Builder command.
#
# @return [void]
def print_failed_command(command)
Ansi.write($stdout, :red, "Failed command was: #{command_to_s(command)}", :reset, "\n")
end
private
# Add a build target.
@@ -857,13 +940,7 @@ module Rscons
#
# @return [void]
def start_threaded_command(tc)
if @echo == :command
puts command_to_s(tc.command)
elsif @echo == :short
if tc.short_description
puts tc.short_description
end
end
print_builder_run_message(tc.short_description, tc.command)
env_args = tc.system_env ? [tc.system_env] : []
options_args = tc.system_options ? [tc.system_options] : []
@@ -988,16 +1065,14 @@ module Rscons
end
end
# Parse dependencies for a given target from a Makefile.
# Parse dependencies from a Makefile.
#
# This method is used internally by Rscons builders.
#
# @param mf_fname [String] File name of the Makefile to read.
# @param target [String, nil]
# Name of the target to gather dependencies for, nil for any/all.
#
# @return [Array<String>] Paths of dependency files.
def self.parse_makefile_deps(mf_fname, target)
def self.parse_makefile_deps(mf_fname)
deps = []
buildup = ''
File.read(mf_fname).each_line do |line|
@@ -1005,11 +1080,9 @@ module Rscons
buildup += ' ' + $1
else
buildup += ' ' + line
if buildup =~ /^(.*): (.*)$/
mf_target, mf_deps = $1.strip, $2
if target.nil? or mf_target == target
deps += mf_deps.split(' ').map(&:strip)
end
if buildup =~ /^.*: (.*)$/
mf_deps = $1
deps += mf_deps.split(' ').map(&:strip)
end
buildup = ''
end
+19 -11
View File
@@ -9,9 +9,14 @@ module Rscons
# @param build_dependencies [Hash]
# Hash mapping targets to a set of build dependencies. A job will not be
# returned as ready to run if any of its dependencies are still building.
def initialize(build_dependencies)
# @param side_effects [Hash]
# Hash mapping targets to a set of side-effect files. A job will not be
# returned as ready to run if any of its dependencies is a side-effect
# of another target that has not yet been built.
def initialize(build_dependencies, side_effects)
@jobs = {}
@build_dependencies = build_dependencies
@side_effects = side_effects
end
# Add a job to the JobSet.
@@ -47,18 +52,16 @@ module Rscons
# @return [nil, Hash]
# The next job to run.
def get_next_job_to_run(targets_still_building)
targets_not_built_yet = targets_still_building + @jobs.keys
side_effects = targets_not_built_yet.map do |target|
@side_effects[target] || []
end.flatten
targets_not_built_yet += side_effects
@jobs.keys.each do |target|
skip = false
(@jobs[target][0][:sources] + (@build_dependencies[target] || []).to_a).each do |src|
if @jobs.include?(src)
# Skip this target because it depends on another target later in
# the job set.
skip = true
break
end
if targets_still_building.include?(src)
# Skip this target because it depends on another target that is
# still being built.
if targets_not_built_yet.include?(src)
skip = true
break
end
@@ -73,7 +76,12 @@ module Rscons
return job
end
nil
# If there is a job to run, and nothing is still building, but we did
# not find a job to run above, then there might be a circular dependency
# introduced by the user.
if (@jobs.size > 0) and targets_still_building.empty?
raise "Could not find a runnable job. Possible circular dependency for #{@jobs.keys.first}"
end
end
# Remove all jobs from the JobSet.
+45 -15
View File
@@ -86,40 +86,50 @@ module Rscons
# Replace "$!{var}" variable references in varref with the expanded
# variables' values, recursively.
#
# @param varref [nil, String, Array, Proc]
# @param varref [nil, String, Array, Proc, Symbol, TrueClass, FalseClass]
# Value containing references to variables.
# @param lambda_args [Array]
# Arguments to pass to any lambda variable values to be expanded.
#
# @return [nil, String, Array]
# @return [nil, String, Array, Symbol, TrueClass, FalseClass]
# Expanded value with "$!{var}" variable references replaced.
def expand_varref(varref, lambda_args)
if varref.is_a?(String)
case varref
when Symbol, true, false, nil
varref
when String
if varref =~ /^(.*)\$\{([^}]+)\}(.*)$/
prefix, varname, suffix = $1, $2, $3
prefix = expand_varref(prefix, lambda_args) unless prefix.empty?
varval = expand_varref(self[varname], lambda_args)
if varval.is_a?(String) or varval.nil?
expand_varref("#{prefix}#{varval}#{suffix}", lambda_args)
elsif varval.is_a?(Array)
varval.map {|vv| expand_varref("#{prefix}#{vv}#{suffix}", lambda_args)}.flatten
# suffix needs no expansion since the regex matches the last occurence
case varval
when Symbol, true, false, nil, String
if prefix.is_a?(Array)
prefix.map {|p| "#{p}#{varval}#{suffix}"}
else
"#{prefix}#{varval}#{suffix}"
end
when Array
if prefix.is_a?(Array)
varval.map {|vv| prefix.map {|p| "#{p}#{vv}#{suffix}"}}.flatten
else
varval.map {|vv| "#{prefix}#{vv}#{suffix}"}
end
else
raise "I do not know how to expand a variable reference to a #{varval.class.name} (from #{varname.inspect} => #{self[varname].inspect})"
raise "Unknown construction variable type: #{varval.class} (from #{varname.inspect} => #{self[varname].inspect})"
end
else
varref
end
elsif varref.is_a?(Array)
when Array
varref.map do |ent|
expand_varref(ent, lambda_args)
end.flatten
elsif varref.is_a?(Proc)
when Proc
expand_varref(varref[*lambda_args], lambda_args)
elsif varref.nil?
nil
elsif varref.is_a?(Symbol)
varref
else
raise "Unknown varref type: #{varref.class} (#{varref.inspect})"
raise "Unknown construction variable type: #{varref.class} (#{varref.inspect})"
end
end
@@ -143,6 +153,26 @@ module Rscons
end
end
# Return a String representing the VarSet.
#
# @return [String] Representation of the VarSet.
def inspect
to_h.inspect
end
# Return an array containing the values associated with the given keys.
#
# @param keys [Array<String, Symbol>]
# Keys to look up in the VarSet.
#
# @return [Array]
# An array containing the values associated with the given keys.
def values_at(*keys)
keys.map do |key|
self[key]
end
end
private
# Move all VarSet variables into the copy-on-access list.
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons
# gem version
VERSION = "1.10.0"
VERSION = "1.17.0"
end
+1 -2
View File
@@ -18,12 +18,11 @@ Gem::Specification.new do |gem|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.require_paths = ["lib"]
gem.add_dependency "json", "~> 1.0"
gem.add_dependency "json", ">= 1.8", "< 3.0"
gem.add_development_dependency "rspec"
gem.add_development_dependency "rake"
gem.add_development_dependency "simplecov"
gem.add_development_dependency "yard"
gem.add_development_dependency "rdoc"
gem.add_development_dependency "redcarpet"
end
+208 -12
View File
@@ -74,6 +74,8 @@ describe Rscons do
command_name = "#{command_prefix}#{@statics[:build_test_id]}"
File.open("_simplecov_setup.rb", "w") do |fh|
fh.puts <<EOF
require "bundler"
Bundler.setup
require "simplecov"
class MyFormatter
def format(*args)
@@ -91,6 +93,7 @@ end
$LOAD_PATH.unshift(#{@owd.inspect} + "/lib")
# force color off
ENV["TERM"] = nil
#{options[:ruby_setup_code]}
EOF
end
stdout, stderr, status = nil, nil, nil
@@ -249,8 +252,8 @@ EOF
result = run_test(rsconsfile: "carat.rb")
expect(result.stderr).to eq ""
expect(Set[*lines(result.stdout)]).to eq Set[
%q{gcc -c -o build_root/one.o -MMD -MF build_root/one.mf -Isrc/one/ -Isrc/two/ build_root/one.c},
%q{gcc -c -o build_root/src/two/two.o -MMD -MF build_root/src/two/two.mf -Isrc/one/ -Isrc/two/ src/two/two.c},
%q{gcc -c -o build_root/one.o -MMD -MF build_root/one.mf -Isrc -Isrc/one -Isrc/two build_root/one.c},
%q{gcc -c -o build_root/src/two/two.o -MMD -MF build_root/src/two/two.mf -Isrc -Isrc/one -Isrc/two src/two/two.c},
%Q{gcc -o build_dir.exe build_root/src/two/two.o build_root/one.o},
]
end
@@ -336,7 +339,7 @@ EOF
]
end
it 'allows cloning all attributes of an Environment object' do
it 'clones all attributes of an Environment object by default' do
test_dir('clone_env')
result = run_test(rsconsfile: "clone_all.rb")
expect(result.stderr).to eq ""
@@ -392,8 +395,8 @@ EOF
result = run_test(rsconsfile: "build_hooks.rb")
expect(result.stderr).to eq ""
expect(Set[*lines(result.stdout)]).to eq Set[
'gcc -c -o build_one/one.o -MMD -MF build_one/one.mf -Isrc/one/ -Isrc/two/ -O1 src/one/one.c',
'gcc -c -o build_two/two.o -MMD -MF build_two/two.mf -Isrc/one/ -Isrc/two/ -O2 src/two/two.c',
'gcc -c -o build_one/one.o -MMD -MF build_one/one.mf -Isrc/one -Isrc/two -O1 src/one/one.c',
'gcc -c -o build_two/two.o -MMD -MF build_two/two.mf -Isrc/one -Isrc/two -O2 src/two/two.c',
'gcc -o build_hook.exe build_one/one.o build_two/two.o',
]
expect(`./build_hook.exe`).to eq "Hello from two()\n"
@@ -429,11 +432,31 @@ EOF
test_dir("d")
result = run_test
expect(result.stderr).to eq ""
expect(lines(result.stdout)).to eq [
"gdc -c -o build/main.o main.d",
"gdc -o hello-d.exe build/main.o",
]
expect(`./hello-d.exe`.rstrip).to eq "Hello from D!"
slines = lines(result.stdout)
expect(slines).to include("gdc -c -o build/main.o -MMD -MF build/main.mf main.d")
expect(slines).to include("gdc -c -o build/mod.o -MMD -MF build/mod.mf mod.d")
expect(slines.last).to eq("gdc -o hello-d.exe build/main.o build/mod.o")
expect(`./hello-d.exe`.rstrip).to eq "Hello from D, value is 42!"
end
it "does dependency generation for D sources" do
test_dir("d")
result = run_test
expect(result.stderr).to eq ""
slines = lines(result.stdout)
expect(slines).to include("gdc -c -o build/main.o -MMD -MF build/main.mf main.d")
expect(slines).to include("gdc -c -o build/mod.o -MMD -MF build/mod.mf mod.d")
expect(slines.last).to eq("gdc -o hello-d.exe build/main.o build/mod.o")
expect(`./hello-d.exe`.rstrip).to eq "Hello from D, value is 42!"
fcontents = File.read("mod.d", mode: "rb").sub("42", "33")
File.open("mod.d", "wb") {|fh| fh.write(fcontents)}
result = run_test
expect(result.stderr).to eq ""
slines = lines(result.stdout)
expect(slines).to include("gdc -c -o build/main.o -MMD -MF build/main.mf main.d")
expect(slines).to include("gdc -c -o build/mod.o -MMD -MF build/mod.mf mod.d")
expect(slines.last).to eq("gdc -o hello-d.exe build/main.o build/mod.o")
expect(`./hello-d.exe`.rstrip).to eq "Hello from D, value is 33!"
end
it "creates shared libraries using D" do
@@ -732,6 +755,82 @@ EOF
expect(`./test-static.exe`).to match /Hi from one/
end
it "raises an error for a circular dependency" do
test_dir("simple")
result = run_test(rsconsfile: "error_circular_dependency.rb")
expect(result.stderr).to match /Possible circular dependency for (foo|bar|baz)/
expect(result.status).to_not eq 0
end
it "raises an error for a circular dependency where a build target contains itself in its source list" do
test_dir("simple")
result = run_test(rsconsfile: "error_circular_dependency2.rb")
expect(result.stderr).to match /Possible circular dependency for foo/
expect(result.status).to_not eq 0
end
it "orders builds to respect user dependencies" do
test_dir("simple")
result = run_test(rsconsfile: "user_dep_build_order.rb", rscons_args: %w[-j4])
expect(result.stderr).to eq ""
end
it "waits for all parallelized builds to complete if one fails" do
test_dir("simple")
result = run_test(rsconsfile: "wait_for_builds_on_failure.rb", rscons_args: %w[-j4])
expect(result.status).to_not eq 0
expect(result.stderr).to match /Failed to build foo_1/
expect(result.stderr).to match /Failed to build foo_2/
expect(result.stderr).to match /Failed to build foo_3/
expect(result.stderr).to match /Failed to build foo_4/
end
it "clones n_threads attribute when cloning an Environment" do
test_dir("simple")
result = run_test(rsconsfile: "clone_n_threads.rb")
expect(result.stderr).to eq ""
expect(lines(result.stdout)).to eq ["165"]
end
it "prints a builder's short description with 'command' echo mode if there is no command" do
test_dir("build_dir")
result = run_test(rsconsfile: "echo_command_ruby_builder.rb")
expect(result.stderr).to eq ""
expect(lines(result.stdout)).to eq ["Install inst.exe"]
end
it "supports a string for a builder's echoed 'command' with Environment#print_builder_run_message" do
test_dir("build_dir")
result = run_test(rsconsfile: "echo_command_string.rb")
expect(result.stderr).to eq ""
expect(lines(result.stdout)).to eq ["MyBuilder foo command"]
end
context "colored output" do
it "does not output in color with --color=off" do
test_dir("simple")
result = run_test(rscons_args: %w[--color=off])
expect(result.stderr).to eq ""
expect(result.stdout).to_not match(/\e\[/)
end
it "displays output in color with --color=force" do
test_dir("simple")
result = run_test(rscons_args: %w[--color=force])
expect(result.stderr).to eq ""
expect(result.stdout).to match(/\e\[/)
File.open("simple.c", "wb") do |fh|
fh.write("foobar")
end
result = run_test(rscons_args: %w[--color=force])
expect(result.stderr).to match(/\e\[/)
end
end
context "backward compatibility" do
it "allows a builder to call Environment#run_builder in a non-threaded manner" do
test_dir("simple")
@@ -754,6 +853,21 @@ EOF
]
end
it "prints the failed build command for a non-threaded builder" do
test_dir("simple")
File.open("simple.c", "wb") do |fh|
fh.write(<<EOF)
void one()
{
}
EOF
end
result = run_test(rsconsfile: "build_sources.rb")
expect(result.status).to_not eq 0
expect(result.stderr).to match /main/
expect(result.stdout).to match /Failed command was: gcc/
end
it "prints the failed build command for a threaded builder when called via Environment#run_builder without delayed execution" do
test_dir("simple")
File.open("simple.c", "wb") do |fh|
@@ -782,8 +896,8 @@ EOF
result = run_test(rsconsfile: "backward_compatible_build_hooks.rb")
expect(result.stderr).to eq ""
expect(Set[*lines(result.stdout)]).to eq Set[
'gcc -c -o one.o -MMD -MF one.mf -Isrc/one/ -Isrc/two/ -O1 src/two/two.c',
'gcc -c -o two.o -MMD -MF two.mf -Isrc/one/ -Isrc/two/ -O2 src/two/two.c'
'gcc -c -o one.o -MMD -MF one.mf -Isrc -Isrc/one -Isrc/two -O1 src/two/two.c',
'gcc -c -o two.o -MMD -MF two.mf -Isrc -Isrc/one -Isrc/two -O2 src/two/two.c'
]
expect(File.exists?('one.o')).to be_truthy
expect(File.exists?('two.o')).to be_truthy
@@ -1150,6 +1264,81 @@ EOF
"LD simple.exe",
]
end
it "allows a VarSet to be passed in as the command parameter" do
test_dir("simple")
result = run_test(rsconsfile: "cache_varset.rb")
expect(result.stderr).to eq ""
expect(lines(result.stdout)).to eq [
"TestBuilder foo",
]
result = run_test(rsconsfile: "cache_varset.rb")
expect(result.stderr).to eq ""
expect(result.stdout).to eq ""
end
context "debugging" do
it "prints a message when the target does not exist" do
test_dir("simple")
result = run_test(rsconsfile: "cache_debugging.rb")
expect(result.stdout).to match /Target foo\.o needs rebuilding because it does not exist on disk/
end
it "prints a message when there is no cached build information for the target" do
test_dir("simple")
FileUtils.touch("foo.o")
result = run_test(rsconsfile: "cache_debugging.rb")
expect(result.stdout).to match /Target foo\.o needs rebuilding because there is no cached build information for it/
end
it "prints a message when the target file has changed on disk" do
test_dir("simple")
result = run_test(rsconsfile: "cache_debugging.rb")
File.open("foo.o", "wb") {|fh| fh.puts "hi"}
result = run_test(rsconsfile: "cache_debugging.rb")
expect(result.stdout).to match /Target foo\.o needs rebuilding because it has been changed on disk since being built last/
end
it "prints a message when the command has changed" do
test_dir("simple")
result = run_test(rsconsfile: "cache_debugging.rb")
result = run_test(rsconsfile: "cache_debugging.rb", rscons_args: %w[command_change=yes])
expect(result.stdout).to match /Target foo\.o needs rebuilding because the command used to build it has changed/
end
it "prints a message when strict_deps is in use and the set of dependencies does not match" do
test_dir("simple")
result = run_test(rsconsfile: "cache_debugging.rb", rscons_args: %w[strict_deps1=yes])
result = run_test(rsconsfile: "cache_debugging.rb", rscons_args: %w[strict_deps2=yes])
expect(result.stdout).to match /Target foo\.o needs rebuilding because the :strict_deps option is given and the set of dependencies does not match the previous set of dependencies/
end
it "prints a message when there is a new dependency" do
test_dir("simple")
result = run_test(rsconsfile: "cache_debugging.rb")
result = run_test(rsconsfile: "cache_debugging.rb", rscons_args: %w[new_dep=yes])
expect(result.stdout).to match /Target foo\.o needs rebuilding because there are new dependencies/
end
it "prints a message when there is a new user-specified dependency" do
test_dir("simple")
result = run_test(rsconsfile: "cache_debugging.rb")
result = run_test(rsconsfile: "cache_debugging.rb", rscons_args: %w[new_user_dep=yes])
expect(result.stdout).to match /Target foo\.o needs rebuilding because the set of user-specified dependency files has changed/
end
it "prints a message when a dependency file has changed" do
test_dir("simple")
result = run_test(rsconsfile: "cache_debugging.rb")
f = File.read("simple.c", mode: "rb")
f += "\n"
File.open("simple.c", "wb") do |fh|
fh.write(f)
end
result = run_test(rsconsfile: "cache_debugging.rb")
expect(result.stdout).to match /Target foo\.o needs rebuilding because dependency file simple\.c has changed/
end
end
end
context "Object builder" do
@@ -1231,6 +1420,13 @@ EOF
result = run_test(rsconsfile: "build_after.rb", rscons_args: %w[-j 4])
expect(result.stderr).to eq ""
end
it "allows the user to specify side-effect files produced by another builder" do
test_dir("custom_builder")
result = run_test(rsconsfile: "produces.rb", rscons_args: %w[-j 4])
expect(result.stderr).to eq ""
expect(File.exists?("copy_inc.h")).to be_truthy
end
end
context "CLI" do
+33
View File
@@ -0,0 +1,33 @@
module Rscons
describe Ansi do
describe ".do_ansi?" do
context "on Windows" do
it "returns true when IO is a fifo on an xterm" do
stub_const("RUBY_PLATFORM", "mingw")
expect(ENV).to receive(:[]).with("TERM").and_return("xterm")
io = double
expect(io).to receive(:stat).and_return(Struct.new(:ftype).new("fifo"))
expect(Ansi.__send__(:do_ansi?, io)).to be_truthy
end
it "returns false when TERM is not set appropriately" do
stub_const("RUBY_PLATFORM", "mingw")
expect(ENV).to receive(:[]).with("TERM").and_return(nil)
io = double
expect(Ansi.__send__(:do_ansi?, io)).to be_falsey
end
end
context "on POSIX" do
it "returns true when IO is a TTY" do
stub_const("RUBY_PLATFORM", "linux")
io = double
expect(io).to receive(:tty?).and_return(true)
expect(Ansi.__send__(:do_ansi?, io)).to be_truthy
end
end
end
end
end
+4 -4
View File
@@ -26,13 +26,13 @@ module Rscons
cache = build_from(_cache)
expect(File).to receive(:exists?).with("one").and_return(true)
expect(File).to receive(:exists?).with("one/two").and_return(false)
expect(FileUtils).to receive(:mkdir).with("one/two")
expect(FileUtils).to receive(:mkdir_p).with("one/two")
expect(File).to receive(:exists?).with("one/two/three").and_return(false)
expect(FileUtils).to receive(:mkdir).with("one/two/three")
expect(FileUtils).to receive(:mkdir_p).with("one/two/three")
expect(File).to receive(:exists?).with("one").and_return(true)
expect(File).to receive(:exists?).with("one/two").and_return(true)
expect(File).to receive(:exists?).with("one/two/four").and_return(false)
expect(FileUtils).to receive(:mkdir).with("one/two/four")
expect(FileUtils).to receive(:mkdir_p).with("one/two/four")
cache.mkdir_p("one/two/three")
cache.mkdir_p("one\\two\\four")
expect(cache.directories).to eq ["one/two", "one/two/three", "one/two/four"]
@@ -43,7 +43,7 @@ module Rscons
cache = build_from(_cache)
expect(File).to receive(:exists?).with("/one").and_return(true)
expect(File).to receive(:exists?).with("/one/two").and_return(false)
expect(FileUtils).to receive(:mkdir).with("/one/two")
expect(FileUtils).to receive(:mkdir_p).with("/one/two")
cache.mkdir_p("/one/two")
expect(cache.directories).to eq ["/one/two"]
end
+3 -35
View File
@@ -184,44 +184,12 @@ module Rscons
env["foo"] = {}
expect(env.expand_varref(["-p${path}", "${flags}"])).to eq ["-pdir1", "-pdir2", "-x", "-y", "-z"]
expect(env.expand_varref("foo")).to eq "foo"
expect {env.expand_varref("${foo}")}.to raise_error /Unknown.varref.type/
expect {env.expand_varref("${foo}")}.to raise_error /Unknown.construction.variable.type/
expect(env.expand_varref("${specialflag}")).to eq "-z"
expect(env.expand_varref("${path}")).to eq ["dir1", "dir2"]
end
end
describe "#execute" do
context "with echo: :short" do
context "with no errors" do
it "prints the short description and executes the command" do
env = Environment.new(echo: :short)
expect(env).to receive(:puts).with("short desc")
expect(env).to receive(:system).with(*Rscons.command_executer, "a", "command").and_return(true)
env.execute("short desc", ["a", "command"])
end
end
context "with errors" do
it "prints the short description, executes the command, and prints the failed command line" do
env = Environment.new(echo: :short)
expect(env).to receive(:puts).with("short desc")
expect(env).to receive(:system).with(*Rscons.command_executer, "a", "command").and_return(false)
expect($stdout).to receive(:puts).with("Failed command was: a command")
env.execute("short desc", ["a", "command"])
end
end
end
context "with echo: :command" do
it "prints the command executed and executes the command" do
env = Environment.new(echo: :command)
expect(env).to receive(:puts).with("a command '--arg=val with spaces'")
expect(env).to receive(:system).with({modified: :environment}, *Rscons.command_executer, "a", "command", "--arg=val with spaces", {opt: :val}).and_return(false)
env.execute("short desc", ["a", "command", "--arg=val with spaces"], env: {modified: :environment}, options: {opt: :val})
end
end
end
describe "#method_missing" do
it "calls the original method missing when the target method is not a known builder" do
env = Environment.new
@@ -361,7 +329,7 @@ module Rscons
expect(File).to receive(:read).with('makefile').and_return(<<EOS)
module.o: source.cc
EOS
expect(Environment.parse_makefile_deps('makefile', 'module.o')).to eq ['source.cc']
expect(Environment.parse_makefile_deps('makefile')).to eq ['source.cc']
end
it 'handles dependencies split across many lines' do
@@ -370,7 +338,7 @@ module.o: module.c \\
module.h \\
other.h
EOS
expect(Environment.parse_makefile_deps('makefile', 'module.o')).to eq [
expect(Environment.parse_makefile_deps('makefile')).to eq [
'module.c', 'module.h', 'other.h']
end
end
+30 -2
View File
@@ -159,14 +159,33 @@ module Rscons
it "calls a lambda with the given lambda arguments" do
expect(v.expand_varref("${lambda}", [v: "fez"])).to eq("fez--12")
end
it "returns true when passed true" do
expect(v.expand_varref(true, :lambda_args)).to eq(true)
end
it "returns false when passed false" do
expect(v.expand_varref(false, :lambda_args)).to eq(false)
end
it "raises an error when given an invalid argument" do
expect { v.expand_varref({a: :b}, :lambda_args) }.to raise_error /Unknown varref type: Hash/
expect { v.expand_varref({a: :b}, :lambda_args) }.to raise_error /Unknown construction variable type: Hash/
end
it "raises an error when an expanded variable is an unexpected type" do
expect(v).to receive(:[]).at_least(1).times.with("bad").and_return("bad_val")
expect(v).to receive(:expand_varref).with("bad_val", :lambda_args).and_return({a: :b})
expect(v).to receive(:expand_varref).and_call_original
expect { v.expand_varref("${bad}", :lambda_args) }.to raise_error /I do not know how to expand a variable reference to a Hash/
expect { v.expand_varref("${bad}", :lambda_args) }.to raise_error /Unknown construction variable type: Hash/
end
it "expands symbols within a string outside an array" do
v['var'] = :a_symbol
expect(v.expand_varref("this is ${var}", :lambda_args)).to eq "this is a_symbol"
end
it "expands booleans within a string outside an array" do
v['var'] = false
expect(v.expand_varref("this is ${var}", :lambda_args)).to eq "this is false"
end
it "expands symbols and booleans in an array" do
v['var'] = [:a_symbol, false]
expanded = v.expand_varref("this is ${var}", :lambda_args)
expect(expanded).to eq(["this is a_symbol", "this is false"])
end
end
@@ -199,5 +218,14 @@ module Rscons
end
end
describe "#values_at" do
it "returns an array of the values associated with the given keys" do
v = VarSet.new({"fuz" => "a string", "foo" => 42, "bar" => :baz,
"qax" => [3, 6], "qux" => {a: :b}})
expect(v.values_at).to eq []
expect(v.values_at(*%w[fuz qux qax])).to eq ["a string", {a: :b}, [3, 6]]
end
end
end
end