Compare commits

..
1 Commits
Author SHA1 Message Date
josh 784bbd471b wip 2019-04-21 21:08:02 -04:00
61 changed files with 1350 additions and 3440 deletions

No files matched your search

+1 -3
View File
@@ -4,8 +4,6 @@
/build_test_run/ /build_test_run/
/coverage/ /coverage/
/dist/ /dist/
/gen/ /doc/
/large_project/
/pkg/ /pkg/
/test/ /test/
/yard/
+70 -103
View File
@@ -1,113 +1,80 @@
## v2.0.0 ## ChangeLog
- convert rscons from a Ruby gem to a standalone script ### v1.17.0
- 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 #### New Features
### New Features
- allow construction variable expansion on `true` and `false` values. - allow construction variable expansion on `true` and `false` values.
- remove makefile target name check when parsing dependencies - remove makefile target name check when parsing dependencies
## v1.16.0 ### v1.16.0
### New Features #### New Features
- Add `Rscons.glob` - Add `Rscons.glob`
- Support command-line variables - Support command-line variables
- improve debuggability of `cache.up_to_date?` - improve debuggability of cache.up_to_date?
- allow passing a VarSet into cache methods - allow passing a VarSet into cache methods
### Fixes #### Fixes
- generate dependencies for D builds - generate dependencies for D builds
## v1.15.0 ### v1.15.0
- allow json 1.x or 2.x - allow json 1.x or 2.x
## v1.14.0 ### v1.14.0
### New Features #### New Features
- #45 - Add `Rscons::VarSet#values_at` - #45 - Add Rscons::VarSet#values_at
### Fixes #### Fixes
- #44 - `Environment#print_builder_run_message` should support string commands - #44 - Environment#print_builder_run_message should support string commands
## v1.13.0 ### v1.13.0
### New Features #### New Features
- #43 - Add ability to record side-effect file production - #43 - Add ability to record side-effect file production
## v1.12.0 ### v1.12.0
### New Features #### New Features
- #40 - env.depends should imply `env.build_after` - #40 - env.depends should imply env.build_after
- #41 - be more colorful - #41 - be more colorful
### Fixes #### Fixes
- #39 - wait for in-progress subcommands to complete on build failure - #39 - wait for in-progress subcommands to complete on build failure
- #42 - cloned Environments should inherit `n_threads` - #42 - cloned Environments should inherit n_threads
## v1.11.1 ### v1.11.1
### Fixes #### Fixes
- fix the circular build dependency detection logic - fix the circular build dependency detection logic
## v1.11.0 ### v1.11.0
### New Features #### New Features
- Change default Environment :clone option to :all to clone all attributes - Change default Environment :clone option to :all to clone all attributes
- #38 - raise error when circular dependencies are found - #38 - raise error when circular dependencies are found
- #34 - Allow overriding `n_threads` on a per-Environment level - #34 - Allow overriding n_threads on a per-Environment level
### Fixes #### Fixes
- #35 - `env.build_after` should expand paths - #35 - env.build_after should expand paths
- #36 - `SHCFLAGS` and `SHCXXFLAGS` should inherit non-SH flags by default - #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 - #37 - Fix non-blocking thread-wait if Rscons.n_threads is set to 0
## v1.10.0 ### v1.10.0
### New Features #### New Features
- #23 - add parallelization - builds are now parallelized by default - #23 - add parallelization - builds are now parallelized by default
- #31 - add LEXSUFFIX, YACCSUFFIX construction variables - #31 - add LEXSUFFIX, YACCSUFFIX construction variables
@@ -117,100 +84,100 @@
- Add builder features - Add builder features
- #8 - add SharedObject and SharedLibrary builders - #8 - add SharedObject and SharedLibrary builders
### Fixes #### Fixes
- expand target and source paths before calling `Builder#create_build_target` - expand target and source paths before calling Builder#create_build_target
- #29 - fix `PROGSUFFIX` handling - #29 - fix PROGSUFFIX handling
- #32 - Pre-build hooks do not respect modified key values - #32 - Pre-build hooks do not respect modified key values
## v1.9.3 ### v1.9.3
- `Environment#parse_flags` should put -std=XXX flags in CCFLAGS, not CFLAGS - Environment#parse_flags should put -std=XXX flags in CCFLAGS, not CFLAGS
## v1.9.2 ### v1.9.2
- allow phony targets in conjunction with build roots - allow phony targets in conjunction with build roots
## v1.9.1 ### v1.9.1
- change *SUFFIX defaults to arrays - change *SUFFIX defaults to arrays
- add various C++ file suffixes - add various C++ file suffixes
- use ${INCPREFIX} instead of hard-coded "-I" in Preprocess builder - use ${INCPREFIX} instead of hard-coded "-I" in Preprocess builder
## v1.9.0 ### v1.9.0
### New Features #### New Features
- #6 - add Install and Copy builders - #6 - add Install and Copy builders
- #22 - allow overriding Command builder short description with `CMD_DESC` variable - #22 - allow overriding Command builder short description with CMD_DESC variable
- #24 - add "rscons" executable - #24 - add "rscons" executable
- #25 - add support for phony targets given as Symbols instead of Strings - #25 - add support for phony targets given as Symbols instead of Strings
- #26 - support registering multiple build targets with the same target name - #26 - support registering multiple build targets with the same target name
- #27 - add Directory builder - #27 - add Directory builder
### Fixes #### Fixes
- #20 - fix variable references that expand to arrays in build target sources - #20 - fix variable references that expand to arrays in build target sources
- #21 - rework Preprocess builder to consider deep dependencies - #21 - rework Preprocess builder to consider deep dependencies
- fix `Rscons.set_suffix` to append the given suffix if the filename has none - fix Rscons.set_suffix to append the given suffix if the filename has none
- remove ${CFLAGS} from default `CPP_CMD` - remove ${CFLAGS} from default CPP_CMD
## v1.8.1 ### v1.8.1
- fix Environment#dump when construction variables are symbols - fix Environment#dump when construction variables are symbols
## v1.8.0 ### v1.8.0
- new Command builder to execute arbitrary user commands - new Command builder to execute arbitrary user commands
- new SimpleBuilder class - new SimpleBuilder class
- create new builders quickly by passing a block to `Environment#add_builder` - create new builders quickly by passing a block to Environment#add_builder
- improved YARD documentation - improved YARD documentation
- add Environment#dump to debug Environment construction variables - add Environment#dump to debug Environment construction variables
## v1.7.0 ### v1.7.0
- allow build hooks to register new build targets - allow build hooks to register new build targets
- add post-build hooks (register with `Environment#add_post_build_hook`) - add post-build hooks (register with Environment#add_post_build_hook)
- clear all build targets after processing an Environment - clear all build targets after processing an Environment
- allow trailing slashes in arguments to `Environment#build_dir` - allow trailing slashes in arguments to Environment#build_dir
## v1.6.1 ### v1.6.1
- add DEPFILESUFFIX construction variable to override dependency file suffix - add DEPFILESUFFIX construction variable to override dependency file suffix
- fix Environment#depends to expand its arguments for construction variables - fix Environment#depends to expand its arguments for construction variables
## v1.6.0 ### v1.6.0
- support lambdas as construction variable values - support lambdas as construction variable values
## v1.5.0 ### v1.5.0
- add "json" as a runtime dependency - add "json" as a runtime dependency
- update construction variables to match SCons more closely - update construction variables to match SCons more closely
- add `CPPDEFPREFIX`, `INCPREFIX`, `CPPDEFINES`, `CCFLAGS`, `LIBDIRPREFIX`, and `LIBLINKPREFIX` - add CPPDEFPREFIX, INCPREFIX, CPPDEFINES, CCFLAGS, LIBDIRPREFIX, and LIBLINKPREFIX
- add `Environment#shell` - add Environment#shell
- add `Environment#parse_flags`, `#parse_flags!`, `#merge_flags` - add Environment#parse_flags, #parse_flags!, #merge_flags
- unbuffer `$stdout` by default - unbuffer $stdout by default
- add `PROGSUFFIX` construction variable (defaults to `.exe` on MinGW/Cygwin) - add PROGSUFFIX construction variable (defaults to .exe on MinGW/Cygwin)
- add `Rscons::BuildTarget` and `Builder#create_build_target` - add Rscons::BuildTarget and Builder#create_build_target
- update specs to RSpec 3.x and fix to run on MinGW/Cygwin/Linux - update specs to RSpec 3.x and fix to run on MinGW/Cygwin/Linux
- add YARD documentation to get to 100% coverage - add YARD documentation to get to 100% coverage
## v1.4.3 ### v1.4.3
- fix builders properly using construction variable overrides - fix builders properly using construction variable overrides
- expand nil construction variables to empty strings - expand nil construction variables to empty strings
## v1.4.2 ### v1.4.2
- add `Environment#expand_path` - add Environment#expand_path
- expand construction variable references in builder targets and sources before invoking builder - expand construction variable references in builder targets and sources before invoking builder
## v1.4.1 ### v1.4.1
- fix invoking a builder with no sources while a build root defined - fix invoking a builder with no sources while a build root defined
## v1.4.0 ### v1.4.0
- add CFile builder - add CFile builder
- add Disassemble builder - add Disassemble builder
@@ -223,21 +190,21 @@
- implement copy-on-write semantics for construction variables when cloning Environments - implement copy-on-write semantics for construction variables when cloning Environments
- only load the cache once instead of on each Environment#process - only load the cache once instead of on each Environment#process
- only write the cache when something has changed - only write the cache when something has changed
- fix `Cache#mkdir_p` to handle relative paths (Issue #5) - fix Cache#mkdir_p to handle relative paths (Issue #5)
- flush the cache to disk if a builder raises an exception (Issue #4) - flush the cache to disk if a builder raises an exception (Issue #4)
## v1.3.0 ### v1.3.0
- change Environment#execute() options parameter to accept the following options keys: - change Environment#execute() options parameter to accept the following options keys:
- :env to pass an environment Hash to Kernel#system - :env to pass an environment Hash to Kernel#system
- :options to pass an options Hash to Kernel#system - :options to pass an options Hash to Kernel#system
## v1.2.0 ### v1.2.0
- add :clone option to Environment#clone to control exactly which Environment attributes are cloned - add :clone option to Environment#clone to control exactly which Environment attributes are cloned
- allow nil to be passed in to `Environment#build_root=` - allow nil to be passed in to Environment#build_root=
## v1.1.0 ### v1.1.0
- Change `Cache#up_to_date?` and `#register_build` to accept a single target - Change Cache#up_to_date?() and #register_build() to accept a single target
file or an array of target file names file or an array of target file names
-2
View File
@@ -6,5 +6,3 @@ gem "rake"
gem "simplecov" gem "simplecov"
gem "yard" gem "yard"
gem "rdoc" gem "rdoc"
gem "redcarpet"
gem "syntax"
+17 -21
View File
@@ -2,31 +2,29 @@ GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
diff-lcs (1.3) diff-lcs (1.3)
docile (1.3.2) docile (1.1.5)
json (2.2.0) json (2.1.0)
rake (12.3.2) rake (12.3.1)
rdoc (6.1.1) rdoc (5.1.0)
redcarpet (3.4.0) rspec (3.6.0)
rspec (3.8.0) rspec-core (~> 3.6.0)
rspec-core (~> 3.8.0) rspec-expectations (~> 3.6.0)
rspec-expectations (~> 3.8.0) rspec-mocks (~> 3.6.0)
rspec-mocks (~> 3.8.0) rspec-core (3.6.0)
rspec-core (3.8.2) rspec-support (~> 3.6.0)
rspec-support (~> 3.8.0) rspec-expectations (3.6.0)
rspec-expectations (3.8.4)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0) rspec-support (~> 3.6.0)
rspec-mocks (3.8.1) rspec-mocks (3.6.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0) rspec-support (~> 3.6.0)
rspec-support (3.8.2) rspec-support (3.6.0)
simplecov (0.15.0) simplecov (0.15.0)
docile (~> 1.1.0) docile (~> 1.1.0)
json (>= 1.8, < 3) json (>= 1.8, < 3)
simplecov-html (~> 0.10.0) simplecov-html (~> 0.10.0)
simplecov-html (0.10.2) simplecov-html (0.10.2)
syntax (1.2.2) yard (0.9.12)
yard (0.9.20)
PLATFORMS PLATFORMS
ruby ruby
@@ -36,11 +34,9 @@ DEPENDENCIES
json json
rake rake
rdoc rdoc
redcarpet
rspec rspec
simplecov simplecov
syntax
yard yard
BUNDLED WITH BUNDLED WITH
1.17.3 1.16.2
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2013-2019 Josh Holtrop Copyright (c) 2013 Josh Holtrop
MIT License MIT License
+587 -4
View File
@@ -1,8 +1,591 @@
# Rscons # Rscons
Rscons (https://github.com/holtrop/rscons) is an open-source build system Rscons (https://github.com/holtrop/rscons) is a software construction framework
for developers. inspired by SCons and waf but implemented in Ruby.
Rscons is written in Ruby, and is inspired by [SCons](https://scons.org/) and [waf](https://waf.io/). ## Installation
See [https://holtrop.github.io/rscons/index.html](https://holtrop.github.io/rscons/index.html) for User Guide and Installation instructions. Rscons is distributed as a standalone Ruby script named ``rscons``. This file
(containing the complete rscons build system) can be checked into your project
and versioned along with your project.
## Usage
### Standalone
Rscons provides a standalone executable ("rscons") with a command-line
interface. The rscons executable will read a build script (by default named
Rsconsfile or Rsconsfile.rb) and execute its contents.
### With Rake
Rscons can be used with rake as well. The same content that would be written
in Rsconsfile can be placed in a Rakefile. It could be placed within a rake
task block or split among multiple tasks.
## Example Build Scripts
### Example: Building a C Program
```ruby
Rscons::Environment.new do |env|
env["CFLAGS"] << "-Wall"
env.Program("program", Rscons.glob("src/**/*.c"))
end
```
### Example: Building a D Program
```ruby
Rscons::Environment.new do |env|
env["DFLAGS"] << "-Wall"
env.Program("program", Rscons.glob("src/**/*.d"))
end
```
### Example: Cloning an Environment
```ruby
main_env = Rscons::Environment.new do |env|
# Store object files from sources under "src" in "build/main"
env.build_dir("src", "build/main")
env["CFLAGS"] = ["-DSOME_DEFINE", "-O3"]
env["LIBS"] = ["SDL"]
env.Program("program", Rscons.glob("src/**/*.cc"))
end
debug_env = main_env.clone do |env|
# Store object files from sources under "src" in "build/debug"
env.build_dir("src", "build/debug")
env["CFLAGS"] -= ["-O3"]
env["CFLAGS"] += ["-g", "-O0"]
env.Program("program-debug", Rscons.glob("src/**/*.cc"))
end
```
### Example: Custom Builder
Custom builders are implemented as classes which extend from `Rscons::Builder`.
The builder must have a `run` method which is called to invoke the builder.
The `run` method should return the name of the target built on success, and
`false` on failure.
```ruby
class GenerateFoo < Rscons::Builder
def run(target, sources, cache, env, vars)
cache.mkdir_p(File.dirname(target))
File.open(target, "w") do |fh|
fh.puts <<EOF
#define GENERATED 42
EOF
end
target
end
end
Rscons::Environment.new do |env|
env.add_builder(GenerateFoo.new)
env.GenerateFoo("foo.h", [])
env.Program("a.out", Rscons.glob("*.c"))
end
```
### Example: Custom Builder That Only Regenerates When Necessary
```ruby
class CmdBuilder < Rscons::Builder
def run(target, sources, cache, env, vars)
cmd = ["cmd", "-i", sources.first, "-o", target]
unless cache.up_to_date?(target, cmd, sources, env)
cache.mkdir_p(File.dirname(target))
system(cmd)
cache.register_build(target, cmd, sources, env)
end
target
end
end
Rscons::Environment.new do |env|
env.add_builder(CmdBuilder.new)
env.CmdBuilder("foo.gen", "foo_gen.cfg")
end
```
The `Cache#up_to_date?` method accepts an optional 5th parameter which is an
options Hash. The `:debug` option can be specified in this Hash with a value
of `true` to aid in debugging builders while developing them. For example:
```ruby
unless cache.up_to_date?(target, cmd, sources, env, debug: true)
end
```
### Example: Custom Builder That Generates Multiple Output Files
```ruby
class CModuleGenerator < Rscons::Builder
def run(target, sources, cache, env, vars)
c_fname = target
h_fname = target.sub(/\.c$/, ".h")
cmd = ["generate_c_and_h", sources.first, c_fname, h_fname]
unless cache.up_to_date?([c_fname, h_fname], cmd, sources, env)
cache.mkdir_p(File.dirname(target))
system(cmd)
cache.register_build([c_fname, h_fname], cmd, sources, env)
end
target
end
end
Rscons::Environment.new do |env|
env.add_builder(CModuleGenerator.new)
env.CModuleGenerator("build/foo.c", "foo_gen.cfg")
end
```
### Example: Custom Builder Using Builder#standard_build()
The `standard_build` method from the `Rscons::Builder` base class can be used
when the builder needs to execute a system command to produce the target file.
The `standard_build` method will return the correct value so its return value
can be used as the return value from the `run` method.
```ruby
class CmdBuilder < Rscons::Builder
def run(target, sources, cache, env, vars)
cmd = ["cmd", "-i", sources.first, "-o", target]
standard_build("CmdBld #{target}", target, cmd, sources, env, cache)
end
end
Rscons::Environment.new do |env|
env.add_builder(CmdBuilder.new)
env.CmdBuilder("foo.gen", "foo_gen.cfg")
end
```
### Example: Custom Builder Using Environment#add_builder()
The `add_builder` method of the `Rscons::Environment` class optionally allows
you to define and register a builder by providing a name and action block. This
can be useful if the builder you are trying to define is easily expressed as a
short ruby procedure. When `add_builder` is called in this manner a new builder
will be registered with the environment with the given name. When this builder
is used it will call the provided block in order to build the target.
```ruby
Rscons::Environment.new do |env|
env.add_builder(:JsonToYaml) do |target, sources, cache, env, vars|
unless cache.up_to_date?(target, :JsonToYaml, sources, env)
cache.mkdir_p(File.dirname(target))
File.open(target, 'w') do |f|
f.write(YAML.dump(JSON.load(IO.read(sources.first))))
end
cache.register_build(target, :JsonToYaml, sources, env)
end
target
end
env.JsonToYaml('foo.yml','foo.json')
end
```
### Example: Using different compilation flags for some sources
```ruby
Rscons::Environment.new do |env|
env["CFLAGS"] = ["-O3", "-Wall", "-DDEFINE"]
env.add_build_hook do |build_op|
if build_op[:target] =~ %r{build/third-party}
build_op[:vars]["CFLAGS"] -= ["-Wall"]
end
end
env.build_dir("src", "build")
env.Program("program", Rscons.glob("**/*.cc"))
end
```
### Example: Creating a static library
```ruby
Rscons::Environment.new do |env|
env.Library("mylib.a", Rscons.glob("src/**/*.c"))
end
```
### Example: Creating a C++ parser source from a Yacc/Bison input file
```ruby
Rscons::Environment.new do |env|
env.CFile("#{env.build_root}/parser.tab.cc", "parser.yy")
end
```
## Details
### Environments
The Environment is the main top-level object that Rscons operates with. An
Environment must be created by the user in order to build anything. All build
targets are registered within an Environment. In many cases only a single
Environment will be needed, but more than one can be created (either from
scratch or by cloning another existing Environment) if needed.
An Environment consists of:
* a collection of builders
* a collection of construction variables used by those builders
* a mapping of build directories from source directories
* a default build root to apply if no specific build directories are matched
* a collection of user-defined build targets
* a collection of user-defined build hooks
When cloning an environment, by default the construction variables, builders,
build hooks, build directories, and build root are cloned, but the new
environment does not inherit any of the registered build targets.
The set of environment attributes that are cloned is controllable via the
`:clone` option to the `#clone` method.
For example, `env.clone(clone: [:variables, :builders])` will include
construction variables, and builders but not build hooks, build directories, or
the build root.
The set of pending targets is never cloned.
Cloned environments contain "deep copies" of construction variables.
For example, in:
```ruby
base_env = Rscons::Environment.new
base_env["CPPPATH"] = ["one", "two"]
cloned_env = base_env.clone
cloned_env["CPPPATH"] << "three"
```
`base_env["CPPPATH"]` will not include "three".
### Builders
Builders are the workhorses that Rscons uses to execute build operations.
Each builder is specialized to perform a particular operation.
Rscons ships with a number of builders:
* Command, which executes a user-defined command to produce the target.
* Copy, which is identical to Install.
* CFile, which builds a C or C++ source file from a lex or yacc input file.
* Disassemble, which disassembles an object file to a disassembly listing.
* Install, which installs files or directories to a specified destination.
* Library, which collects object files into a static library archive file.
* Object, which compiles source files to produce an object file.
* Preprocess, which invokes the C/C++ preprocessor on a source file.
* Program, which links object files to produce an executable.
* SharedLibrary, which links object files to produce a dynamically loadable
library.
* SharedObject, which compiles source files to produce an object file, in a way
that is able to be used to create a shared library.
If you want to create an Environment that does not contain any builders,
you can use the `:exclude_builders` key to the Environment constructor.
#### Command
```ruby
env.Command(target, sources, "CMD" => command)
# Example
env.Command("docs.html", "docs.md",
"CMD" => ["pandoc", "-fmarkdown", "-thtml", "-o${_TARGET}", "${_SOURCES}"],
"CMD_DESC" => "PANDOC")
```
The command builder executes a user-defined command in order to produce the
desired target file based on the provided source files.
#### CFile
```ruby
env.CFile(target, source)
# Example
env.CFile("parser.c", "parser.y")
```
The CFile builder will generate a C or C++ source file from a lex (.l, .ll)
or yacc (.y, .yy) input file.
#### Disassemble
```ruby
env.Disassemble(target, source)
# Example
env.Disassemble("module.dis", "module.o")
```
The Disassemble builder generates a disassembly listing using objdump from
and object file.
#### Install
```ruby
env.Install(destination, sources)
# Example
env.Install("dist/bin", "app.exe")
env.Install("dist/share", "share")
```
#### Library
```ruby
env.Library(target, sources)
# Example
env.Library("lib.a", Rscons.glob("src/**/*.c"))
```
The Library builder creates a static library archive from the given source
files.
#### Object
```ruby
env.Object(target, sources)
# Example
env.Object("module.o", "module.c")
```
The Object builder compiles the given sources to an object file. Although it
can be called explicitly, it is more commonly implicitly called by the Program
builder.
#### Preprocess
```ruby
env.Preprocess(target, source)
# Example
env.Preprocess("module-preprocessed.cc", "module.cc")
```
The Preprocess builder invokes either `${CC}` or `${CXX}` (depending on if the
source contains an extension in `${CXXSUFFIX}` or not) and writes the
preprocessed output to the target file.
#### Program
```ruby
env.Program(target, sources)
# Example
env.Program("myprog", Rscons.glob("src/**/*.cc"))
```
The Program builder compiles and links the given sources to an executable file.
Object files or source files can be given as `sources`. A platform-dependent
program suffix will be appended to the target name if one is not specified.
This can be controlled with the `PROGSUFFIX` construction variable.
#### SharedLibrary
```ruby
env.SharedLibrary(target, sources)
# Example
env.SharedLibrary("mydll", Rscons.glob("src/**/*.cc"))
```
The SharedLibrary builder compiles and links the given sources to a dynamically
loadable library. Object files or source files can be given as `sources`.
A platform-dependent prefix and suffix will be appended to the target name if
they are not specified by the user. These values can be controlled by
overriding the `SHLIBPREFIX` and `SHLIBSUFFIX` construction variables.
#### SharedObject
```ruby
env.SharedObject(target, sources)
# Example
env.SharedObject("lib_module.o", "lib_module.c")
```
The SharedObject builder compiles the given sources to an object file. Any
compilation flags necessary to build the object file in a manner that allows it
to be used to create a shared library are added. Although it can be called
explicitly, it is more commonly implicitly called by the SharedLibrary builder.
### Construction Variables
Construction variables are used to define the toolset and any build options
that Rscons will use to build a project. The default construction variable
values are configured to build applications using gcc. However, all
construction variables can be overridden by the user.
| Name | Type | Description | Default |
| --- | --- | --- | --- |
| AR | String | Static library archiver executable | "ar" |
| ARCMD | Array | Static library archiver command line | ["${AR}", "${ARFLAGS}", "${_TARGET}", "${_SOURCES}"] |
| ARFLAGS | Array | Static library archiver flags | ["rcs"] |
| AS | String | Assembler executable | "${CC}" |
| ASCMD | Array | Assembler command line | ["${AS}", "-c", "-o", "${_TARGET}", "${ASDEPGEN}", "${INCPREFIX}${ASPPPATH}", "${ASPPFLAGS}", "${ASFLAGS}", "${_SOURCES}"] |
| ASDEPGEN | Array | Assembly dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}", "-MT", "TARGET"] |
| ASFLAGS | Array | Assembler flags | [] |
| ASPPFLAGS | Array | Assembler preprocessor flags | ["${CPPFLAGS}"] |
| ASPPPATH | Array | Assembler preprocessor path | ["${CPPPATH}"] |
| ASSUFFIX | Array | Assembly file suffixes | [".S"] |
| CC | String | C compiler executable | "gcc" |
| CCCMD | Array | C compiler command line | ["${CC}", "-c", "-o", "${_TARGET}", "${CCDEPGEN}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${CFLAGS}", "${CCFLAGS}", "${_SOURCES}"] |
| CCDEPGEN | Array | C compiler dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}", "-MT", "TARGET"] |
| CCFLAGS | Array | Common flags for both C and C++ compiler | [] |
| CFLAGS | Array | C compiler flags | [] |
| CPP_CMD | Array | Preprocess command line | ["${_PREPROCESS_CC}", "-E", "${_PREPROCESS_DEPGEN}", "-o", "${_TARGET}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${_SOURCES}"] |
| CPP_TARGET_SUFFIX | String | Suffix used for crt:preprocess target filename. | ".c" |
| CPPDEFINES | Array | C preprocessor defines | [] |
| CPPDEFPREFIX | String | Prefix used for C preprocessor to introduce a define | "-D" |
| CPPFLAGS | Array | C preprocessor flags | ["${CPPDEFPREFIX}${CPPDEFINES}"] |
| CPPPATH | Array | C preprocessor path | [] |
| CSUFFIX | Array | C source file suffixes | [".c"] |
| CXX | String | C++ compiler executable | "g++" |
| CXXCMD | Array | C++ compiler command line | ["${CXX}", "-c", "-o", "${_TARGET}", "${CXXDEPGEN}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${CXXFLAGS}", "${CCFLAGS}", "${_SOURCES}"] |
| CXXDEPGEN | Array | C++ compiler dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}", "-MT", "TARGET"] |
| CXXFLAGS | Array | C++ compiler flags | [] |
| CXXSUFFIX | Array | C++ source file suffixes | [".cc", ".cpp", ".cxx", ".C"] |
| D_IMPORT_PATH | Array | D compiler import path | [] |
| DC | String | D compiler executable | "gdc" |
| DCCMD | Array | D compiler command line | ["${DC}", "-c", "-o", "${_TARGET}", "${DDEPGEN}", "${INCPREFIX}${D_IMPORT_PATH}", "${DFLAGS}", "${_SOURCES}"] |
| DDEPGEN | Array | D compiler dependency generation flags | ["-MMD", "-MF", "${_DEPFILE}", "-MT", "TARGET"] |
| DEPFILESUFFIX | String | Dependency file suffix for Makefile-style dependency rules emitted by the compiler (used internally for temporary dependency files used to determine a source file's dependencies) | ".mf" |
| DFLAGS | Array | D compiler flags | [] |
| DISASM_CMD | Array | Disassemble command line | ["${OBJDUMP}", "${DISASM_FLAGS}", "${_SOURCES}"] |
| DISASM_FLAGS | Array | Disassemble flags | ["--disassemble", "--source"] |
| DSUFFIX | String/Array | Default D source file suffix | ".d" |
| INCPREFIX | String | Prefix used for C preprocessor to add an include path | "-I" |
| LD | String | nil | Linker executable (automatically determined when nil) | nil (if nil, ${CC}, ${CXX}, or ${DC} is used depending on the sources being linked) |
| LDCMD | Array | Link command line | ["${LD}", "-o", "${_TARGET}", "${LDFLAGS}", "${_SOURCES}", "${LIBDIRPREFIX}${LIBPATH}", "${LIBLINKPREFIX}${LIBS}"] |
| LDFLAGS | Array | Linker flags | [] |
| LEX | String | Lex executable | "flex" |
| LEX_CMD | Array | Lex command line | ["${LEX}", "${LEX_FLAGS}", "-o", "${_TARGET}", "${_SOURCES}"] |
| LEX_FLAGS | Array | Lex flags | [] |
| LEXSUFFIX | Array | Lex input file suffixes | [".l", ".ll"] |
| LIBDIRPREFIX | String | Prefix given to linker to add a library search path | "-L" |
| LIBLINKPREFIX | String | Prefix given to linker to add a library to link with | "-l" |
| LIBPATH | Array | Library load path | [] |
| LIBS | Array | Libraries to link with | [] |
| LIBSUFFIX | String/Array | Default static library file suffix | ".a" |
| OBJDUMP | String | Objdump executable | "objdump" |
| OBJSUFFIX | String/Array | Default object file suffix | ".o" |
| PROGSUFFIX | String | Default program suffix. | Windows: ".exe", POSIX: "" |
| SHCC | String | Shared object C compiler | "${CC}" |
| SHCCCMD | Array | Shared object C compiler command line | ["${SHCC}", "-c", "-o", "${_TARGET}", "${CCDEPGEN}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${SHCFLAGS}", "${SHCCFLAGS}", "${_SOURCES}"] |
| SHCCFLAGS | Array | Shared object C and C++ compiler flags | Windows: ["${CCFLAGS}"], POSIX: ["${CCFLAGS}", -fPIC"] |
| SHCFLAGS | Array | Shared object C compiler flags | ["${CFLAGS}"] |
| SHCXX | String | Shared object C++ compiler | "${CXX}" |
| SHCXXCMD | Array | Shared object C++ compiler command line | ["${SHCXX}", "-c", "-o", "${_TARGET}", "${CXXDEPGEN}", "${INCPREFIX}${CPPPATH}", "${CPPFLAGS}", "${SHCXXFLAGS}", "${SHCCFLAGS}", "${_SOURCES}"] |
| SHCXXFLAGS | Array | Shared object C++ compiler flags | ["${CXXFLAGS}"] |
| SHDC | String | Shared object D compiler | "gdc" |
| SHDCCMD | Array | Shared object D compiler command line | ["${SHDC}", "-c", "-o", "${_TARGET}", "${INCPREFIX}${D_IMPORT_PATH}", "${SHDFLAGS}", "${_SOURCES}"] |
| SHDFLAGS | Array | Shared object D compiler flags | Windows: ["${DFLAGS}"], POSIX: ["${DFLAGS}", "-fPIC"] |
| SHLD | String | Shared library linker | nil (if nil, ${SHCC}, ${SHCXX}, or ${SHDC} is used depending on the sources being linked) |
| SHLDCMD | Array | Shared library linker command line | ["${SHLD}", "-o", "${_TARGET}", "${SHLDFLAGS}", "${_SOURCES}", "${SHLIBDIRPREFIX}${LIBPATH}", "${SHLIBLINKPREFIX}${LIBS}"] |
| SHLDFLAGS | Array | Shared library linker flags | ["${LDFLAGS}", "-shared"] |
| SHLIBDIRPREFIX | String | Prefix given to shared library linker to add a library search path | "-L" |
| SHLIBLINKPREFIX | String | Prefix given to shared library linker to add a library to link with | "-l" |
| SHLIBPREFIX | String | Shared library file name prefix | Windows: "", POSIX: "lib" |
| SHLIBSUFFIX | String | Shared library file name suffix | Windows: ".dll", POSIX: ".so" |
| SIZE | String | Size executable. | "size" |
| YACC | String | Yacc executable | "bison" |
| YACC_CMD | Array | Yacc command line | ["${YACC}", "${YACC_FLAGS}", "-o", "${_TARGET}", "${_SOURCES}"] |
| YACC_FLAGS | Array | Yacc flags | ["-d"] |
| YACCSUFFIX | Array | Yacc input file suffixes | [".y", ".yy"] |
### Build Hooks
Environments can have build hooks which are added with `env.add_build_hook()`.
Build hooks are invoked immediately before a builder executes.
Build hooks can modify the construction variables in use for the build
operation.
They can also register new build targets.
Environments can also have post-build hooks added with `env.add_post_build_hook()`.
Post-build hooks are only invoked if the build operation was a success.
Post-build hooks can invoke commands using the newly-built files, or register
new build targets.
Each build hook block will be invoked for every build operation, so the block
should test the target or sources if its action should only apply to some
subset of build targets or source files.
Example build hook:
```ruby
Rscons::Environment.new do |env|
# Build third party sources without -Wall
env.add_build_hook do |build_op|
if build_op[:builder].name == "Object" and
build_op[:sources].first =~ %r{src/third-party}
build_op[:vars]["CFLAGS"] -= ["-Wall"]
end
end
end
```
The `build_op` parameter to the build hook block is a Hash describing the
build operation with the following keys:
* `:builder` - `Builder` instance in use
* `:env` - `Environment` calling the build hook; note that this may be
different from the Environment that the build hook was added to in the case
that the original Environment was cloned with build hooks!
* `:target` - `String` name of the target file
* `:sources` - `Array` of the source files
* `:vars` - `Rscons::VarSet` containing the construction variables to use.
The build hook can overwrite entries in `build_op[:vars]` to alter the
construction variables in use for this specific build operation.
### Phony Targets
A build target name given as a Symbol instead of a String is interpreted as a
"phony" target.
Phony targets operate similarly to normal build targets, except that a file is
not expected to be produced by the builder.
Phony targets will still be "rebuilt" if any source or the command is out of
date.
### Explicit Dependencies
A target can be marked as depending on another file that Rscons would not
otherwise know about via the `Environment#depends` function. For example,
to force the linker to re-link a Program output when a linker script changes:
```ruby
Rscons::Environment.new do |env|
env.Program("a.out", "foo.c", "LDFLAGS" => %w[-T linker_script.ld])
env.depends("a.out", "linker_script.ld")
end
```
You can pass multiple dependency files to `Environment#depends`:
```ruby
env.depends("my_app", "config/link.ld", "README.txt", *Rscons.glob("assets/**/*"))
```
### Command-Line Variables
Variables can be specified on the rscons command line. For example:
```
rscons VAR=val
```
These variables are accessible in a global VarSet called `Rscons.vars`.
### Construction Variable Naming
* uppercase strings - the default construction variables that Rscons uses
* strings beginning with "_" - set and used internally by builders
* symbols, lowercase strings - reserved as user-defined construction variables
### API documentation
Documentation for the complete Rscons API can be found at
http://www.rubydoc.info/github/holtrop/rscons/master.
## Contributing
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request
+1 -52
View File
@@ -10,7 +10,7 @@ require "yard"
require "rake/clean" require "rake/clean"
require "fileutils" require "fileutils"
CLEAN.include %w[build_test_run .yardoc yard coverage test] CLEAN.include %w[build_test_run .yardoc doc coverage test]
CLOBBER.include %w[dist pkg] CLOBBER.include %w[dist pkg]
task :build_dist do task :build_dist do
@@ -39,57 +39,6 @@ end
YARD::Rake::YardocTask.new do |yard| YARD::Rake::YardocTask.new do |yard|
yard.files = ['lib/**/*.rb'] yard.files = ['lib/**/*.rb']
yard.options = ["-ogen/yard"]
end
task :gen_large_project, [:size] => :build_dist do |task, args|
size = (args.size || 10000).to_i
FileUtils.rm_rf("large_project")
FileUtils.mkdir_p("large_project/src")
size.times do |i|
File.open("large_project/src/fn#{i}.c", "w") do |fh|
fh.puts(<<-EOF)
int fn#{i}(void)
{
return #{i};
}
EOF
end
File.open("large_project/src/fn#{i}.h", "w") do |fh|
fh.puts %[int fn#{i}(void);]
end
end
File.open("large_project/src/main.c", "w") do |fh|
size.times do |i|
fh.puts %[#include "fn#{i}.h"]
end
fh.puts <<-EOF
int main(int argc, char * argv[])
{
int result = 0;
EOF
size.times do |i|
fh.puts %[result += fn#{i}();]
end
fh.puts <<-EOF
return result;
}
EOF
end
File.open("large_project/Rsconscript", "w") do |fh|
fh.puts <<EOF
build do
Environment.new do |env|
env.Program("project", glob("src/*.c"))
end
end
EOF
end
FileUtils.cp("dist/rscons", "large_project")
end
task :user_guide do
system("ruby", "-Ilib", "rb/gen_user_guide.rb")
end end
task :default => :spec task :default => :spec
@@ -1,10 +0,0 @@
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", set_define: "HAVE_NOT___FOUND_H" check_c_header "not___found.h", fail: false, set_define: "HAVE_NOT___FOUND_H"
end end
build do build do
@@ -1,10 +0,0 @@
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
@@ -1,10 +0,0 @@
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
@@ -1,5 +0,0 @@
build do
Environment.new(echo: :command) do |env|
env.Library("usr2/libfrobulous.a", "two.c")
end
end
@@ -1,10 +0,0 @@
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
@@ -1,26 +0,0 @@
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
@@ -1,4 +0,0 @@
int two(void)
{
return 42;
}
+2 -2
View File
@@ -7,10 +7,10 @@ build do
env.depends("${build_root}/program.o", "${inc_h}") env.depends("${build_root}/program.o", "${inc_h}")
env.Program("program.exe", ["program.c", "inc.c"]) env.Program("program.exe", ["program.c", "inc.c"])
inc_c = env.Command("inc.c", env.Command("inc.c",
[], [],
"CMD" => %w[ruby gen.rb ${_TARGET}], "CMD" => %w[ruby gen.rb ${_TARGET}],
"CMD_DESC" => "Generating") "CMD_DESC" => "Generating")
inc_c.produces("inc.h") env.produces("inc.c", "inc.h")
end end
end end
@@ -1,16 +0,0 @@
build do
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
end
-4
View File
@@ -1,7 +1,3 @@
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
@@ -1,9 +0,0 @@
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"] += ["m"] env["LIBS"] += ["c"]
env.Program('simple.exe', Dir['*.c']) env.Program('simple.exe', Dir['*.c'])
end end
end end
@@ -1,5 +0,0 @@
build do
Environment.new do |env|
env.produces("foo", "bar")
end
end
+1 -10
View File
@@ -1,14 +1,5 @@
project_name "install_test"
build do build do
Environment.new do |env| Environment.new do |env|
env["CPPPATH"] += glob("src/**") env.Install("inst.exe", "install.rb")
env.Program("^/program.exe", glob("src/**/*.c"))
env.InstallDirectory("${prefix}/bin")
env.Install("${prefix}/bin", "^/program.exe")
env.InstallDirectory("${prefix}/share")
env.Install("${prefix}/share/proj/install.rb", "install.rb")
env.Install("${prefix}/mult", ["install.rb", "copy.rb"])
env.Install("${prefix}/src", "src")
end end
end end
@@ -1,12 +0,0 @@
build do
Environment.new do |env|
env["CPPPATH"] += glob("src/**")
env.Program("^/prog.exe", glob("src/**/*.c"))
end
Environment.new do |env|
env["CPPPATH"] += glob("src/**")
env["CCFLAGS"] += %w[-Wall -g]
env.Program("^/prog-debug.exe", glob("src/**/*.c"))
end
end
-1326
View File
File diff suppressed because it is too large. Load diff
+1 -2
View File
@@ -26,7 +26,6 @@ module Rscons
:Directory, :Directory,
:Disassemble, :Disassemble,
:Install, :Install,
:InstallDirectory,
:Library, :Library,
:Object, :Object,
:Preprocess, :Preprocess,
@@ -146,9 +145,9 @@ require_relative "rscons/builders/mixins/program"
# default builders # default builders
require_relative "rscons/builders/cfile" require_relative "rscons/builders/cfile"
require_relative "rscons/builders/command" require_relative "rscons/builders/command"
require_relative "rscons/builders/copy"
require_relative "rscons/builders/directory" require_relative "rscons/builders/directory"
require_relative "rscons/builders/disassemble" require_relative "rscons/builders/disassemble"
require_relative "rscons/builders/install"
require_relative "rscons/builders/library" require_relative "rscons/builders/library"
require_relative "rscons/builders/object" require_relative "rscons/builders/object"
require_relative "rscons/builders/preprocess" require_relative "rscons/builders/preprocess"
-1
View File
@@ -3,7 +3,6 @@ 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.
+28 -94
View File
@@ -1,5 +1,3 @@
require "set"
module Rscons module Rscons
# Functionality for an instance of the rscons application invocation. # Functionality for an instance of the rscons application invocation.
@@ -25,19 +23,6 @@ module Rscons
def initialize def initialize
@n_threads = Util.determine_n_threads @n_threads = Util.determine_n_threads
@vars = VarSet.new @vars = VarSet.new
@operations = Set.new
@build_step = 0
end
# Check whether a requested operation is active.
#
# @param op [String]
# Operation name.
#
# @return [Boolean]
# Whether the requested operation is active.
def operation(op)
@operations.include?(op)
end end
# Run the specified operation. # Run the specified operation.
@@ -48,73 +33,35 @@ module Rscons
# The script. # The script.
# @param operation_options [Hash] # @param operation_options [Hash]
# Option values from the CLI for the operation. # Option values from the CLI for the operation.
# @param options [Hash]
# Optional parameters.
# @option sub_op [Boolean]
# Whether this operation is not the top-level operation.
# #
# @return [Integer] # @return [Integer]
# Process exit code (0 on success). # Process exit code (0 on success).
def run(operation, script, operation_options, options = {}) def run(operation, script, operation_options)
@start_time = Time.new
@script = script @script = script
@operations << operation
puts "Starting '#{operation}' at #{Time.new}" if verbose
rv =
case operation case operation
when "build" when "build"
rv = 0
unless Cache.instance["configuration_data"]["configured"] unless Cache.instance["configuration_data"]["configured"]
rv =
if @script.autoconf if @script.autoconf
run("configure", script, operation_options, sub_op: false) rv = run("configure", script, operation_options)
if rv != 0
return rv
end
else else
$stderr.puts "Project must be configured first, and autoconf is disabled" $stderr.puts "Project must be configured first, and autoconf is disabled"
1 return 1
end end
end end
if rv == 0
build(operation_options) build(operation_options)
end
when "clean" when "clean"
clean clean
when "configure" when "configure"
configure(operation_options) configure(operation_options)
when "distclean" when "distclean"
distclean distclean
when "install"
run("build", script, operation_options, sub_op: false)
when "uninstall"
uninstall
else else
$stderr.puts "Unknown operation: #{operation}" $stderr.puts "Unknown operation: #{operation}"
1 1
end end
if verbose and options[:sub_op].nil?
time = Time.new
elapsed = time - @start_time
puts "'#{operation}' complete at #{time} (#{Util.format_elapsed_time(elapsed)})"
end
rv
end
# Get the next build step number.
#
# This is used internally by the {Environment} class.
#
# @api private
def get_next_build_step
@build_step += 1
end
# Get the total number of build steps.
#
# @return [Integer]
# The total number of build steps.
def get_total_build_steps
Environment.environments.reduce(@build_step) do |result, env|
result + env.build_steps_remaining
end
end end
private private
@@ -147,19 +94,17 @@ module Rscons
def clean def clean
cache = Cache.instance cache = Cache.instance
# remove all built files # remove all built files
cache.targets(false).each do |target| cache.targets.each do |target|
cache.remove_target(target)
FileUtils.rm_f(target) FileUtils.rm_f(target)
end end
# remove all created directories if they are empty # remove all created directories if they are empty
cache.directories(false).sort {|a, b| b.size <=> a.size}.each do |directory| cache.directories.sort {|a, b| b.size <=> a.size}.each do |directory|
cache.remove_directory(directory)
next unless File.directory?(directory) next unless File.directory?(directory)
if (Dir.entries(directory) - ['.', '..']).empty? if (Dir.entries(directory) - ['.', '..']).empty?
Dir.rmdir(directory) rescue nil Dir.rmdir(directory) rescue nil
end end
end end
cache.write cache.clear
0 0
end end
@@ -171,9 +116,7 @@ module Rscons
cache = Cache.instance cache = Cache.instance
build_dir = cache["configuration_data"]["build_dir"] build_dir = cache["configuration_data"]["build_dir"]
clean clean
if build_dir
FileUtils.rm_rf(build_dir) FileUtils.rm_rf(build_dir)
end
cache.clear cache.clear
0 0
end end
@@ -186,41 +129,32 @@ module Rscons
# @return [Integer] # @return [Integer]
# Exit code. # Exit code.
def configure(options) def configure(options)
# Default options.
options[:build_dir] ||= "build"
options[:prefix] ||= "/usr/local"
cache = Cache.instance
cache["failed_commands"] = []
cache["configuration_data"] = {}
if project_name = @script.project_name
Ansi.write($stdout, "Configuring ", :cyan, project_name, :reset, "...\n")
else
$stdout.puts "Configuring project..."
end
Ansi.write($stdout, "Setting build directory... ", :green, options[:build_dir], :reset, "\n")
Ansi.write($stdout, "Setting prefix... ", :green, options[:prefix], :reset, "\n")
rv = 0 rv = 0
options = options.merge(project_name: @script.project_name) co = ConfigureOp.new("#{options[:build_dir]}/configure")
co = ConfigureOp.new(options)
begin begin
@script.configure(co) @script.configure(co)
rescue ConfigureOp::ConfigureFailure rescue ConfigureOp::ConfigureFailure
rv = 1 rv = 1
end end
co.close(rv == 0) co.close
rv cache["configuration_data"]["build_dir"] = options[:build_dir]
end cache["configuration_data"]["prefix"] = options[:prefix]
cache["configuration_data"]["configured"] = rv == 0
# Remove installed files.
#
# @return [Integer]
# Exit code.
def uninstall
cache = Cache.instance
cache.targets(true).each do |target|
cache.remove_target(target)
next unless File.exists?(target)
puts "Removing #{target}" if verbose
FileUtils.rm_f(target)
end
# remove all created directories if they are empty
cache.directories(true).sort {|a, b| b.size <=> a.size}.each do |directory|
cache.remove_directory(directory)
next unless File.directory?(directory)
if (Dir.entries(directory) - ['.', '..']).empty?
puts "Removing #{directory}" if verbose
Dir.rmdir(directory) rescue nil
end
end
cache.write cache.write
0 rv
end end
end end
+15 -63
View File
@@ -4,52 +4,27 @@ 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)
end end
# Access the value of a construction variable. # Get a construction variable's value.
# #
# @param key [String, Symbol] # @see VarSet#[]
# The construction variable name. def [](*args)
# @varset.__send__(:[], *args)
# @return [Object]
# The construction variable's value.
def [](key)
@varset[key]
end end
# Access the value of a construction variable. # Set a construction variable's value.
# #
# This method is similar to #[] but does not make a copy-on-access copy of # @see VarSet#[]=
# the variable accessed. This means that the returned value is NOT safe to def []=(*args)
# be modified by the caller. Thus the caller must guarantee that it does @varset.__send__(:[]=, *args)
# not modify the returned value.
#
# @param key [String, Symbol]
# The construction variable name.
#
# @return [Object]
# The construction variable's value.
def get_var(key)
@varset.get_var(key)
end
# Assign a value to a construction variable.
#
# @param key [String, Symbol]
# The construction variable name.
#
# @param val [Object]
# The value to set.
def []=(key, val)
@varset[key] = val
end end
# Add a set of construction variables to the BasicEnvironment. # Add a set of construction variables to the BasicEnvironment.
@@ -133,7 +108,7 @@ module Rscons
append["LDFLAGS", ["-arch", val]] append["LDFLAGS", ["-arch", val]]
end end
skip = true skip = true
elsif word =~ /^#{get_var("CPPDEFPREFIX")}(.*)$/ elsif word =~ /^#{self["CPPDEFPREFIX"]}(.*)$/
handle["CPPDEFINES", $1] handle["CPPDEFINES", $1]
elsif word == "-include" elsif word == "-include"
if val = words[i + 1] if val = words[i + 1]
@@ -146,11 +121,11 @@ module Rscons
append["LDFLAGS", ["-isysroot", val]] append["LDFLAGS", ["-isysroot", val]]
end end
skip = true skip = true
elsif word =~ /^#{get_var("INCPREFIX")}(.*)$/ elsif word =~ /^#{self["INCPREFIX"]}(.*)$/
handle["CPPPATH", $1] handle["CPPPATH", $1]
elsif word =~ /^#{get_var("LIBLINKPREFIX")}(.*)$/ elsif word =~ /^#{self["LIBLINKPREFIX"]}(.*)$/
handle["LIBS", $1] handle["LIBS", $1]
elsif word =~ /^#{get_var("LIBDIRPREFIX")}(.*)$/ elsif word =~ /^#{self["LIBDIRPREFIX"]}(.*)$/
handle["LIBPATH", $1] handle["LIBPATH", $1]
elsif word == "-mno-cygwin" elsif word == "-mno-cygwin"
append["CCFLAGS", [word]] append["CCFLAGS", [word]]
@@ -199,7 +174,7 @@ module Rscons
# @return [void] # @return [void]
def merge_flags(flags) def merge_flags(flags)
flags.each_pair do |key, val| flags.each_pair do |key, val|
if self.get_var(key).is_a?(Array) and val.is_a?(Array) if self[key].is_a?(Array) and val.is_a?(Array)
self[key] += val self[key] += val
else else
self[key] = val self[key] = val
@@ -248,28 +223,5 @@ module Rscons
end end
end end
# Execute a command using the system shell.
#
# The shell is automatically determined but can be overridden by the SHELL
# construction variable. If the SHELL construction variable is specified,
# the flag to pass to the shell is automatically dtermined but can be
# overridden by the SHELLFLAG construction variable.
#
# @param command [String] Command to execute.
#
# @return [String] The command's standard output.
def shell(command)
shell_cmd =
if shell = get_var("SHELL")
flag = get_var("SHELLFLAG") || (shell == "cmd" ? "/c" : "-c")
[shell, flag]
else
Rscons.get_system_shell
end
IO.popen([*shell_cmd, command]) do |io|
io.read
end
end
end end
end end
+2 -29
View File
@@ -47,14 +47,6 @@ module Rscons
# Construction variables used to perform the build operation. # Construction variables used to perform the build operation.
attr_accessor :vars attr_accessor :vars
# @return [Set<String>]
# Side effect file(s) produced when this builder runs.
attr_accessor :side_effects
# @return [Integer]
# Build step.
attr_accessor :build_step
# Create an instance of the Builder to build a target. # Create an instance of the Builder to build a target.
# #
# @param options [Hash] # @param options [Hash]
@@ -75,7 +67,6 @@ module Rscons
@cache = options[:cache] @cache = options[:cache]
@env = options[:env] @env = options[:env]
@vars = options[:vars] @vars = options[:vars]
@side_effects = Set.new
end end
# Return the name of the builder. # Return the name of the builder.
@@ -87,14 +78,6 @@ module Rscons
self.class.name self.class.name
end end
# Return whether the builder is a no-op.
#
# @return [Boolean]
# Whether the builder is a no-op.
def nop?
false
end
# Manually record a given build target as depending on the specified files. # Manually record a given build target as depending on the specified files.
# #
# @param user_deps [Array<String>] # @param user_deps [Array<String>]
@@ -105,18 +88,6 @@ module Rscons
@env.depends(@target, *user_deps) @env.depends(@target, *user_deps)
end end
# Manually record the given side effect file(s) as being produced when the
# named target is produced.
#
# @return [void]
def produces(*side_effects)
side_effects.each do |side_effect|
side_effect_expanded = @env.expand_path(@env.expand_varref(side_effect))
@env.register_side_effect(side_effect_expanded)
@side_effects << side_effect_expanded
end
end
# Run the builder to produce a build target. # Run the builder to produce a build target.
# #
# @param options [Hash] # @param options [Hash]
@@ -201,6 +172,8 @@ module Rscons
# Register build results from a {Command} with the cache. # Register build results from a {Command} with the cache.
# #
# @since 1.10.0
#
# @param options [Hash] # @param options [Hash]
# Builder finalize options. # Builder finalize options.
# @option options [String] :stdout # @option options [String] :stdout
+17 -29
View File
@@ -8,10 +8,10 @@ module Rscons
# Hash mapping targets to a set of build dependencies. A builder will not # Hash mapping targets to a set of build dependencies. A builder will not
# be returned as ready to run if any of its dependencies are still # be returned as ready to run if any of its dependencies are still
# building. # building.
# @param side_effects [Set] # @param side_effects [Hash]
# Set of side-effect files. A builder will not be returned as ready to # Hash mapping targets to a set of side-effect files. A builder will not
# run if any of its dependencies is a side-effect of another target that # be returned as ready to run if any of its dependencies is a side-effect
# has not yet been built. # of another target that has not yet been built.
def initialize(build_dependencies, side_effects) def initialize(build_dependencies, side_effects)
super() super()
@build_dependencies = build_dependencies @build_dependencies = build_dependencies
@@ -31,16 +31,6 @@ module Rscons
self[builder.target] << builder self[builder.target] << builder
end end
# Return the number of remaining build steps.
#
# @return [Integer]
# The number of remaining build steps.
def build_steps_remaining
self.reduce(0) do |result, (target, builders)|
result + builders.select {|b| not b.nop?}.size
end
end
# Get the next builder that is ready to run from the BuilderSet. # Get the next builder that is ready to run from the BuilderSet.
# #
# This method will remove the builder from the BuilderSet. # This method will remove the builder from the BuilderSet.
@@ -53,24 +43,22 @@ module Rscons
# @return [nil, Builder] # @return [nil, Builder]
# The next builder to run. # The next builder to run.
def get_next_builder_to_run(targets_still_building) def get_next_builder_to_run(targets_still_building)
to_build = self.find do |target, builders| not_built_yet = targets_still_building + self.keys
deps = builders.first.sources + (@build_dependencies[target] || []).to_a not_built_yet += not_built_yet.reduce([]) do |result, target|
# All dependencies must have been built for this target to be ready to result + (@side_effects[target] || [])
# build.
deps.all? do |dep|
!(targets_still_building.include?(dep) ||
self.include?(dep) ||
@side_effects.include?(dep))
end
end end
if to_build target_to_build = self.keys.find do |target|
target, builders = *to_build deps = self[target][0].sources + (@build_dependencies[target] || []).to_a
builder = builders.first !deps.find {|dep| not_built_yet.include?(dep)}
if builders.size > 1 end
builders.slice!(0)
if target_to_build
builder = self[target_to_build][0]
if self[target_to_build].size > 1
self[target_to_build].slice!(0)
else else
self.delete(target) self.delete(target_to_build)
end end
return builder return builder
end end
+1 -1
View File
@@ -26,7 +26,7 @@ module Rscons
raise "Unknown source file #{@sources.first.inspect} for CFile builder" raise "Unknown source file #{@sources.first.inspect} for CFile builder"
end end
command = @env.build_command("${#{cmd}_CMD}", @vars) command = @env.build_command("${#{cmd}_CMD}", @vars)
standard_command("#{message} from <source>#{Util.short_format_paths(@sources)}<reset> => <target>#{@target}<reset>", command) standard_command("#{message} from #{Util.short_format_paths(@sources)} => #{@target}", command)
end end
end end
+3 -1
View File
@@ -3,6 +3,8 @@ module Rscons
# A builder to execute an arbitrary command that will produce the given # A builder to execute an arbitrary command that will produce the given
# target based on the given sources. # target based on the given sources.
# #
# @since 1.8.0
#
# Example: # Example:
# env.Command("docs.html", "docs.md", # env.Command("docs.html", "docs.md",
# CMD => %w[pandoc -fmarkdown -thtml -o${_TARGET} ${_SOURCES}]) # CMD => %w[pandoc -fmarkdown -thtml -o${_TARGET} ${_SOURCES}])
@@ -21,7 +23,7 @@ module Rscons
if @vars["CMD_STDOUT"] if @vars["CMD_STDOUT"]
options[:stdout] = @env.expand_varref("${CMD_STDOUT}", @vars) options[:stdout] = @env.expand_varref("${CMD_STDOUT}", @vars)
end end
standard_command("#{cmd_desc} <target>#{@target}<reset>", command, options) standard_command("#{cmd_desc} => #{@target}", command, options)
end end
end end
-74
View File
@@ -1,74 +0,0 @@
require "pathname"
module Rscons
module Builders
# The Copy builder copies files/directories to new locations.
class Copy < Builder
# Construct builder.
def initialize(*args)
super
@install_builder = self.class.name == "Install"
@nop = @install_builder && !Rscons.application.operation("install")
end
# Return whether the builder is a no-op.
#
# @return [Boolean]
# Whether the builder is a no-op.
def nop?
@nop
end
# Run the builder to produce a build target.
def run(options)
if @nop
true
else
target_is_dir = (@sources.length > 1) ||
Dir.exists?(@sources.first) ||
Dir.exists?(@target)
outdir = target_is_dir ? @target : File.dirname(@target)
# Collect the list of files to copy over.
file_map = {}
if target_is_dir
@sources.each do |src|
if Dir.exists? src
Dir.glob("#{src}/**/*", File::FNM_DOTMATCH).select do |f|
File.file?(f)
end.each do |subfile|
subpath = Pathname.new(subfile).relative_path_from(Pathname.new(src)).to_s
file_map[subfile] = "#{outdir}/#{subpath}"
end
else
file_map[src] = "#{outdir}/#{File.basename(src)}"
end
end
else
file_map[sources.first] = target
end
printed_message = false
file_map.each do |src, dest|
# Check the cache and copy if necessary
unless @cache.up_to_date?(dest, :Copy, [src], @env)
unless printed_message
message = "#{name} <source>#{Util.short_format_paths(@sources)}<reset> => <target>#{@target}<reset>"
print_run_message(message, nil)
printed_message = true
end
@cache.mkdir_p(File.dirname(dest), install: @install_builder)
FileUtils.cp(src, dest, :preserve => true)
end
@cache.register_build(dest, :Copy, [src], @env, install: @install_builder)
end
(target_is_dir ? Dir.exists?(@target) : File.exists?(@target)) ? true : false
end
end
end
# The Install builder is identical to the Copy builder.
class Install < Copy; end
end
end
+2 -27
View File
@@ -1,47 +1,22 @@
module Rscons module Rscons
module Builders module Builders
# The Directory builder creates a directory. # The Directory builder creates a directory.
class Directory < Builder class Directory < Builder
# Construct builder.
def initialize(*args)
super
@install_builder = self.class.name == "InstallDirectory"
@nop = @install_builder && !Rscons.application.operation("install")
end
# Return whether the builder is a no-op.
#
# @return [Boolean]
# Whether the builder is a no-op.
def nop?
@nop
end
# Run the builder to produce a build target. # Run the builder to produce a build target.
def run(options) def run(options)
if @nop
true
else
if File.directory?(@target) if File.directory?(@target)
true true
elsif File.exists?(@target) elsif File.exists?(@target)
Ansi.write($stderr, :red, "Error: `#{@target}' already exists and is not a directory", :reset, "\n") Ansi.write($stderr, :red, "Error: `#{@target}' already exists and is not a directory", :reset, "\n")
false false
else else
print_run_message("Creating directory <target>#{@target}<reset>", nil) print_run_message("Creating directory => #{@target}", nil)
@cache.mkdir_p(@target, install: @install_builder) @cache.mkdir_p(@target)
true true
end end
end end
end
end end
# The InstallDirectory class is identical to Directory but only performs
# an action for an "install" operation.
class InstallDirectory < Directory; end
end end
end end
+1 -1
View File
@@ -10,7 +10,7 @@ module Rscons
else else
@vars["_SOURCES"] = @sources @vars["_SOURCES"] = @sources
command = @env.build_command("${DISASM_CMD}", @vars) command = @env.build_command("${DISASM_CMD}", @vars)
standard_command("Disassembling <source>#{Util.short_format_paths(@sources)}<reset> => <target>#{target}<reset>", command, stdout: @target) standard_command("Disassembling #{Util.short_format_paths(@sources)} => #{target}", command, stdout: @target)
end end
end end
+55
View File
@@ -0,0 +1,55 @@
require "pathname"
module Rscons
module Builders
# The Install builder copies files/directories to new locations.
class Install < Builder
# Run the builder to produce a build target.
def run(options)
target_is_dir = (@sources.length > 1) ||
Dir.exists?(@sources.first) ||
Dir.exists?(@target)
outdir = target_is_dir ? @target : File.dirname(@target)
# Collect the list of files to copy over.
file_map = {}
if target_is_dir
@sources.each do |src|
if Dir.exists? src
Dir.glob("#{src}/**/*", File::FNM_DOTMATCH).select do |f|
File.file?(f)
end.each do |subfile|
subpath = Pathname.new(subfile).relative_path_from(Pathname.new(src)).to_s
file_map[subfile] = "#{outdir}/#{subpath}"
end
else
file_map[src] = "#{outdir}/#{File.basename(src)}"
end
end
else
file_map[sources.first] = target
end
printed_message = false
file_map.each do |src, dest|
# Check the cache and copy if necessary
unless @cache.up_to_date?(dest, :Copy, [src], @env)
unless printed_message
message = "#{name} #{Util.short_format_paths(@sources)} => #{@target}"
print_run_message(message, nil)
printed_message = true
end
@cache.mkdir_p(File.dirname(dest))
FileUtils.cp(src, dest, :preserve => true)
end
@cache.register_build(dest, :Copy, [src], @env)
end
(target_is_dir ? Dir.exists?(@target) : File.exists?(@target)) ? true : false
end
end
# The Copy builder is identical to the Install builder.
class Copy < Install; end
end
end
+1 -1
View File
@@ -20,7 +20,7 @@ module Rscons
@vars["_TARGET"] = @target @vars["_TARGET"] = @target
@vars["_SOURCES"] = @objects @vars["_SOURCES"] = @objects
command = @env.build_command("${ARCMD}", @vars) command = @env.build_command("${ARCMD}", @vars)
standard_command("Building static library archive <target>#{@target}<reset>", command, sources: @objects) standard_command("Building static library archive => #{@target}", command, sources: @objects)
end end
end end
+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_dependency_file(@vars["_DEPFILE"]) deps += Util.parse_makefile_deps(@vars["_DEPFILE"])
end end
@cache.register_build(@target, @command, deps.uniq, @env) @cache.register_build(@target, @command, deps.uniq, @env)
true true
+3 -4
View File
@@ -8,7 +8,6 @@ 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
@@ -82,11 +81,11 @@ module Rscons
end end
@cache.mkdir_p(File.dirname(@vars["_DEPFILE"])) @cache.mkdir_p(File.dirname(@vars["_DEPFILE"]))
command = @env.build_command(@command_template, @vars) command = @env.build_command(@command_template, @vars)
self.produces(@vars["_DEPFILE"]) @env.produces(@target, @vars["_DEPFILE"])
if @vars[:direct] if @vars[:direct]
message = "#{@short_description}/Linking <source>#{Util.short_format_paths(@sources)}<reset> => <target>#{@target}<reset>" message = "#{@short_description}/Linking #{Util.short_format_paths(@sources)} => #{@target}"
else else
message = "#{@short_description} <source>#{Util.short_format_paths(@sources)}<reset>" message = "#{@short_description} #{Util.short_format_paths(@sources)}"
end end
standard_command(message, command) standard_command(message, command)
end end
+1 -1
View File
@@ -27,7 +27,7 @@ module Rscons
@vars["_SOURCES"] = @objects @vars["_SOURCES"] = @objects
@vars["#{ld_var}"] = ld @vars["#{ld_var}"] = ld
command = @env.build_command("${#{ld_var}CMD}", @vars) command = @env.build_command("${#{ld_var}CMD}", @vars)
standard_command("Linking <target>#{@target}<reset>", command, sources: @objects) standard_command("Linking => #{@target}", command, sources: @objects)
end end
end end
+2 -2
View File
@@ -25,8 +25,8 @@ module Rscons
@vars["_SOURCES"] = @sources @vars["_SOURCES"] = @sources
@vars["_DEPFILE"] = Rscons.set_suffix(target, env.expand_varref("${DEPFILESUFFIX}", vars)) @vars["_DEPFILE"] = Rscons.set_suffix(target, env.expand_varref("${DEPFILESUFFIX}", vars))
command = @env.build_command("${CPP_CMD}", @vars) command = @env.build_command("${CPP_CMD}", @vars)
self.produces(@vars["_DEPFILE"]) @env.produces(@target, @vars["_DEPFILE"])
standard_command("Preprocessing <source>#{Util.short_format_paths(@sources)}<reset> => <target>#{@target}<reset>", command) standard_command("Preprocessing #{Util.short_format_paths(@sources)} => #{@target}", command)
end end
end end
-2
View File
@@ -8,8 +8,6 @@ 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,8 +8,6 @@ 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,8 +7,6 @@ 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
View File
@@ -1,6 +1,8 @@
module Rscons module Rscons
module Builders module Builders
# A Generic builder which has its operation is defined at instantiation. # A Generic builder which has its operation is defined at instantiation.
#
# @since 1.8.0
class SimpleBuilder < Builder class SimpleBuilder < Builder
# @return [String] # @return [String]
+18 -23
View File
@@ -100,6 +100,7 @@ module Rscons
# @return [void] # @return [void]
def write def write
@cache["version"] = VERSION @cache["version"] = VERSION
validate_json_object(@cache)
File.open(CACHE_FILE, "w") do |fh| File.open(CACHE_FILE, "w") do |fh|
fh.puts(JSON.dump(@cache)) fh.puts(JSON.dump(@cache))
end end
@@ -238,18 +239,11 @@ module Rscons
# @param options [Hash] Optional arguments. # @param options [Hash] Optional arguments.
# @option options [Boolean] :install # @option options [Boolean] :install
# Whether the target is for an install operation. # Whether the target is for an install operation.
# @option options [Boolean] :side_effect
# Whether the target is a side-effect file (no checksum will be stored).
# #
# @return [void] # @return [void]
def register_build(targets, command, deps, env, options = {}) def register_build(targets, command, deps, env, options = {})
Array(targets).each do |target| Array(targets).each do |target|
target_checksum = target_checksum = Rscons.phony_target?(target) ? "" : calculate_checksum(target)
if options[:side_effect] or Rscons.phony_target?(target)
""
else
calculate_checksum(target)
end
@cache["targets"][get_cache_key(target)] = { @cache["targets"][get_cache_key(target)] = {
"command" => Digest::MD5.hexdigest(command.inspect), "command" => Digest::MD5.hexdigest(command.inspect),
"checksum" => target_checksum, "checksum" => target_checksum,
@@ -281,7 +275,7 @@ module Rscons
def targets(install) def targets(install)
install = !!install install = !!install
@cache["targets"].select do |key, target| @cache["targets"].select do |key, target|
target["install"] == install target[:install] == install
end.map(&:first) end.map(&:first)
end end
@@ -326,20 +320,6 @@ module Rscons
end.map(&:first) end.map(&:first)
end end
# Remove a target from the cache.
#
# @return [void]
def remove_target(target)
@cache["targets"].delete(target)
end
# Remove a directory from the cache.
#
# @return [void]
def remove_directory(directory)
@cache["directories"].delete(directory)
end
private private
# Return a String key based on the target name to use in the on-disk cache. # Return a String key based on the target name to use in the on-disk cache.
@@ -390,5 +370,20 @@ module Rscons
@lookup_checksums[file] = Digest::MD5.hexdigest(File.read(file, mode: "rb")) rescue "" @lookup_checksums[file] = Digest::MD5.hexdigest(File.read(file, mode: "rb")) rescue ""
end end
# Validate that an object is one of a known set of values that can be
# serialized properly with JSON.
#
# @param o [Object]
# Object to validate.
def validate_json_object(o)
if o.is_a?(Array)
o.each {|v| validate_json_object(v)}
elsif o.is_a?(Hash)
o.each {|*kv| validate_json_object(kv)}
elsif [NilClass, TrueClass, FalseClass, String].none? {|c| o.is_a?(c)}
raise "Unexpected cache value for serialization: #{o.inspect}"
end
end
end end
end end
+10 -14
View File
@@ -1,12 +1,11 @@
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 failed command log from previous build and exit -F, --show-failure Show previous failed command(s) 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)
@@ -122,23 +121,20 @@ module Rscons
rsconscript = DEFAULT_RSCONSCRIPTS.find do |f| rsconscript = DEFAULT_RSCONSCRIPTS.find do |f|
File.exists?(f) File.exists?(f)
end end
end
if rsconscript
script = Script.new
script.load(rsconscript)
end
if do_help
puts USAGE
exit 0
end
unless rsconscript unless rsconscript
$stderr.puts "Could not find the Rsconscript to execute." $stderr.puts "Could not find the Rsconscript to execute."
$stderr.puts "Looked for: #{DEFAULT_RSCONSCRIPTS.join(", ")}" $stderr.puts "Looked for: #{DEFAULT_RSCONSCRIPTS.join(", ")}"
exit 1 exit 1
end end
end
script = Script.new
script.load(rsconscript)
if do_help
puts USAGE
exit 0
end
operation_options = parse_operation_args(operation, argv) || {} operation_options = parse_operation_args(operation, argv) || {}
+108 -224
View File
@@ -10,48 +10,20 @@ module Rscons
# Create a ConfigureOp. # Create a ConfigureOp.
# #
# @param options [Hash] # @param work_dir [String]
# Optional parameters. # Work directory for configure operation.
# @option options [String] :build_dir def initialize(work_dir)
# Build directory. @work_dir = work_dir
# @option options [String] :prefix
# Install prefix.
# @option options [String] :project_name
# Project name.
def initialize(options)
# Default options.
options[:build_dir] ||= "build"
options[:prefix] ||= "/usr/local"
@work_dir = "#{options[:build_dir]}/configure"
FileUtils.mkdir_p(@work_dir) FileUtils.mkdir_p(@work_dir)
@log_fh = File.open("#{@work_dir}/config.log", "wb") @log_fh = File.open("#{@work_dir}/config.log", "wb")
cache = Cache.instance
cache["failed_commands"] = []
cache["configuration_data"] = {}
cache["configuration_data"]["build_dir"] = options[:build_dir]
cache["configuration_data"]["prefix"] = options[:prefix]
if project_name = options[:project_name]
Ansi.write($stdout, "Configuring ", :cyan, project_name, :reset, "...\n")
else
$stdout.puts "Configuring project..."
end
Ansi.write($stdout, "Setting build directory... ", :green, options[:build_dir], :reset, "\n")
Ansi.write($stdout, "Setting prefix... ", :green, options[:prefix], :reset, "\n")
store_merge("prefix" => options[:prefix])
end end
# Close the log file handle. # Close the log file handle.
# #
# @param success [Boolean]
# Whether all configure operations were successful.
#
# @return [void] # @return [void]
def close(success) def close
@log_fh.close @log_fh.close
@log_fh = nil @log_fh = nil
cache = Cache.instance
cache["configuration_data"]["configured"] = success
cache.write
end end
# Check for a working C compiler. # Check for a working C compiler.
@@ -69,7 +41,7 @@ module Rscons
cc = ccc.find do |cc| cc = ccc.find do |cc|
test_c_compiler(cc) test_c_compiler(cc)
end end
complete(cc ? 0 : 1, success_message: cc) common_config_checks(cc ? 0 : 1, success_message: cc)
end end
# Check for a working C++ compiler. # Check for a working C++ compiler.
@@ -87,7 +59,7 @@ module Rscons
cc = ccc.find do |cc| cc = ccc.find do |cc|
test_cxx_compiler(cc) test_cxx_compiler(cc)
end end
complete(cc ? 0 : 1, success_message: cc) common_config_checks(cc ? 0 : 1, success_message: cc)
end end
# Check for a working D compiler. # Check for a working D compiler.
@@ -105,7 +77,7 @@ module Rscons
dc = cdc.find do |dc| dc = cdc.find do |dc|
test_d_compiler(dc) test_d_compiler(dc)
end end
complete(dc ? 0 : 1, success_message: dc) common_config_checks(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,12 +94,11 @@ module Rscons
if status == 0 if status == 0
store_parse(stdout, options) store_parse(stdout, options)
end end
complete(status, options) common_config_checks(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
@@ -140,30 +111,15 @@ module Rscons
vars = { vars = {
"LD" => "${CC}", "LD" => "${CC}",
"_SOURCES" => "#{@work_dir}/cfgtest.c", "_SOURCES" => "#{@work_dir}/cfgtest.c",
"_TARGET" => "#{@work_dir}/cfgtest.o", "_TARGET" => "#{@work_dir}/cfgtest.exe",
"_DEPFILE" => "#{@work_dir}/cfgtest.mf",
} }
status = 1 command = BasicEnvironment.new.build_command("${LDCMD}", vars)
check_cpppath.each do |cpppath|
env = BasicEnvironment.new
if cpppath
env["CPPPATH"] += Array(cpppath)
end
command = env.build_command("${CCCMD}", vars)
_, _, status = log_and_test_command(command) _, _, status = log_and_test_command(command)
if status == 0 common_config_checks(status, options)
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
@@ -176,30 +132,15 @@ module Rscons
vars = { vars = {
"LD" => "${CXX}", "LD" => "${CXX}",
"_SOURCES" => "#{@work_dir}/cfgtest.cxx", "_SOURCES" => "#{@work_dir}/cfgtest.cxx",
"_TARGET" => "#{@work_dir}/cfgtest.o", "_TARGET" => "#{@work_dir}/cfgtest.exe",
"_DEPFILE" => "#{@work_dir}/cfgtest.mf",
} }
status = 1 command = BasicEnvironment.new.build_command("${LDCMD}", vars)
check_cpppath.each do |cpppath|
env = BasicEnvironment.new
if cpppath
env["CPPPATH"] += Array(cpppath)
end
command = env.build_command("${CXXCMD}", vars)
_, _, status = log_and_test_command(command) _, _, status = log_and_test_command(command)
if status == 0 common_config_checks(status, options)
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
@@ -212,30 +153,15 @@ module Rscons
vars = { vars = {
"LD" => "${DC}", "LD" => "${DC}",
"_SOURCES" => "#{@work_dir}/cfgtest.d", "_SOURCES" => "#{@work_dir}/cfgtest.d",
"_TARGET" => "#{@work_dir}/cfgtest.o", "_TARGET" => "#{@work_dir}/cfgtest.exe",
"_DEPFILE" => "#{@work_dir}/cfgtest.mf",
} }
status = 1 command = BasicEnvironment.new.build_command("${LDCMD}", vars)
check_d_import_path.each do |d_import_path|
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) _, _, status = log_and_test_command(command)
if status == 0 common_config_checks(status, options)
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
@@ -250,143 +176,19 @@ module Rscons
"_SOURCES" => "#{@work_dir}/cfgtest.c", "_SOURCES" => "#{@work_dir}/cfgtest.c",
"_TARGET" => "#{@work_dir}/cfgtest.exe", "_TARGET" => "#{@work_dir}/cfgtest.exe",
} }
status = 1 command = BasicEnvironment.new.build_command("${LDCMD}", vars)
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) _, _, 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
complete(status, options) common_config_checks(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)
complete(path ? 0 : 1, options.merge(success_message: path)) common_config_checks(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
@@ -479,12 +281,7 @@ 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
@@ -497,6 +294,64 @@ 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]
@@ -520,5 +375,34 @@ 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
+50 -52
View File
@@ -59,9 +59,6 @@ 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.
@@ -75,8 +72,7 @@ module Rscons
# Hash of Thread object => {Command} or {Builder}. # Hash of Thread object => {Command} or {Builder}.
@threads = {} @threads = {}
@registered_build_dependencies = {} @registered_build_dependencies = {}
# Set of side-effect files that have not yet been built. @side_effects = {}
@side_effects = Set.new
@builder_sets = [] @builder_sets = []
@build_targets = {} @build_targets = {}
@user_deps = {} @user_deps = {}
@@ -169,6 +165,8 @@ module Rscons
# Create a new {Builders::SimpleBuilder} instance and add it to the # Create a new {Builders::SimpleBuilder} instance and add it to the
# environment. # environment.
# #
# @since 1.8.0
#
# @param name [String,Symbol] # @param name [String,Symbol]
# The name of the builder to add. # The name of the builder to add.
# #
@@ -218,6 +216,8 @@ module Rscons
# build operation succeeded. Post-build hooks can register new build # build operation succeeded. Post-build hooks can register new build
# targets. # targets.
# #
# @since 1.7.0
#
# @yield [build_op] # @yield [build_op]
# Invoke the given block with the current build operation. # Invoke the given block with the current build operation.
# @yieldparam build_op [Hash] # @yieldparam build_op [Hash]
@@ -261,7 +261,6 @@ module Rscons
# #
# @return [void] # @return [void]
def process def process
Cache.instance.clear_checksum_cache!
@process_failures = [] @process_failures = []
@process_blocking_wait = false @process_blocking_wait = false
@process_commands_waiting_to_run = [] @process_commands_waiting_to_run = []
@@ -287,7 +286,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 += "\nUse -F to view the failed command log from the previous build operation" msg += "\nRun `#{$0} -F` to see the failed command(s)."
end end
raise BuildError.new(msg) raise BuildError.new(msg)
end end
@@ -375,6 +374,8 @@ module Rscons
# described: # described:
# env.build_after("program.o", "gen.c") # env.build_after("program.o", "gen.c")
# #
# @since 1.10.0
#
# @param targets [String, Array<String>] # @param targets [String, Array<String>]
# Target files to wait to build until the prerequisites are finished # Target files to wait to build until the prerequisites are finished
# building. # building.
@@ -401,6 +402,8 @@ module Rscons
# Manually record the given side effect file(s) as being produced when the # Manually record the given side effect file(s) as being produced when the
# named target is produced. # named target is produced.
# #
# @since 1.13.0
#
# @param target [String] # @param target [String]
# Target of a build operation. # Target of a build operation.
# @param side_effects [Array<String>] # @param side_effects [Array<String>]
@@ -409,25 +412,11 @@ module Rscons
# @return [void] # @return [void]
def produces(target, *side_effects) def produces(target, *side_effects)
target = expand_path(expand_varref(target)) target = expand_path(expand_varref(target))
@builder_sets.reverse.each do |builder_set| side_effects = Array(side_effects).map do |side_effect|
if builders = builder_set[target] expand_path(expand_varref(side_effect))
builders.last.produces(*side_effects) end.flatten
return @side_effects[target] ||= []
end @side_effects[target] += side_effects
end
raise "Could not find a registered build target #{target.inspect}"
end
# Register a side effect file.
#
# This is an internally used method.
#
# @api private
#
# @param side_effect [String]
# Side effect fiel name.
def register_side_effect(side_effect)
@side_effects << side_effect
end end
# Return the list of user dependencies for a given target. # Return the list of user dependencies for a given target.
@@ -490,6 +479,29 @@ module Rscons
end end
end end
# Execute a command using the system shell.
#
# The shell is automatically determined but can be overridden by the SHELL
# construction variable. If the SHELL construction variable is specified,
# the flag to pass to the shell is automatically dtermined but can be
# overridden by the SHELLFLAG construction variable.
#
# @param command [String] Command to execute.
#
# @return [String] The command's standard output.
def shell(command)
shell_cmd =
if self["SHELL"]
flag = self["SHELLFLAG"] || (self["SHELL"] == "cmd" ? "/c" : "-c")
[self["SHELL"], flag]
else
Rscons.get_system_shell
end
IO.popen([*shell_cmd, command]) do |io|
io.read
end
end
# Print the builder run message, depending on the Environment's echo mode. # Print the builder run message, depending on the Environment's echo mode.
# #
# @param builder [Builder] # @param builder [Builder]
@@ -514,12 +526,7 @@ module Rscons
when :short when :short
message = short_description if short_description message = short_description if short_description
end end
if message Ansi.write($stdout, :cyan, message, :reset, "\n") if message
total_build_steps = Rscons.application.get_total_build_steps.to_s
this_build_step = sprintf("%#{total_build_steps.size}d", builder.build_step)
progress = "[#{this_build_step}/#{total_build_steps}]"
Ansi.write($stdout, *Util.colorize_markup("#{progress} #{message}"), "\n")
end
end end
# Get the Builder for a target. # Get the Builder for a target.
@@ -540,16 +547,6 @@ module Rscons
@builder_sets << build_builder_set @builder_sets << build_builder_set
end end
# Get the number of build steps remaining.
#
# @return [Integer]
# The number of build steps remaining.
def build_steps_remaining
@builder_sets.reduce(0) do |result, builder_set|
result + builder_set.build_steps_remaining
end
end
private private
# Build a BuilderSet. # Build a BuilderSet.
@@ -567,9 +564,9 @@ module Rscons
# #
# @return [void] # @return [void]
def run_builder(builder) def run_builder(builder)
unless builder.nop? # TODO: have Cache determine when checksums may be invalid based on
builder.build_step ||= Rscons.application.get_next_build_step # file size and/or timestamp.
end Cache.instance.clear_checksum_cache!
case result = builder.run({}) case result = builder.run({})
when Array when Array
result.each do |waititem| result.each do |waititem|
@@ -591,9 +588,8 @@ module Rscons
when true when true
# Register side-effect files as build targets so that a Cache # Register side-effect files as build targets so that a Cache
# clean operation will remove them. # clean operation will remove them.
builder.side_effects.each do |side_effect| (@side_effects[builder.target] || []).each do |side_effect_file|
Cache.instance.register_build(side_effect, nil, [], self, side_effect: true) Cache.instance.register_build(side_effect_file, nil, [], self)
@side_effects.delete(side_effect)
end end
@build_hooks[:post].each do |build_hook_block| @build_hooks[:post].each do |build_hook_block|
build_hook_block.call(builder) build_hook_block.call(builder)
@@ -638,7 +634,7 @@ module Rscons
if thread if thread
# We found a completed thread. # We found a completed thread.
process_remove_wait(thread) process_remove_wait(thread)
unblocked_builder = builder_for_thread(thread) builder = builder_for_thread(thread)
completed_command = @threads[thread] completed_command = @threads[thread]
@threads.delete(thread) @threads.delete(thread)
if completed_command.is_a?(Command) if completed_command.is_a?(Command)
@@ -646,7 +642,7 @@ module Rscons
completed_command.status = thread.value completed_command.status = thread.value
unless completed_command.status unless completed_command.status
Cache.instance["failed_commands"] << completed_command.command Cache.instance["failed_commands"] << completed_command.command
@process_failures << "Failed to build #{unblocked_builder.target}." @process_failures << "Failed to build #{builder.target}."
return return
end end
end end
@@ -671,14 +667,16 @@ module Rscons
result << builder_for_thread(thread).target result << builder_for_thread(thread).target
end end
if @builder_sets.size > 0 if @builder_sets.size > 0
if builder = @builder_sets[0].get_next_builder_to_run(targets_still_building) builder = @builder_sets[0].get_next_builder_to_run(targets_still_building)
end
if builder
builder.vars = @varset.merge(builder.vars) builder.vars = @varset.merge(builder.vars)
@build_hooks[:pre].each do |build_hook_block| @build_hooks[:pre].each do |build_hook_block|
build_hook_block.call(builder) build_hook_block.call(builder)
end end
return run_builder(builder) return run_builder(builder)
end end
end
if @threads.size > 0 if @threads.size > 0
# A runnable builder was not found but there is a thread running, # A runnable builder was not found but there is a thread running,
-15
View File
@@ -3,7 +3,6 @@ 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)
@@ -58,7 +57,6 @@ module Rscons
end end
end end
# DSL available to the 'configure' block.
class ConfigureDsl class ConfigureDsl
# Create a ConfigureDsl. # Create a ConfigureDsl.
# #
@@ -83,19 +81,6 @@ 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]
+3 -55
View File
@@ -1,5 +1,4 @@
module Rscons module Rscons
# A collection of stand-alone utility methods.
module Util module Util
class << self class << self
@@ -16,30 +15,6 @@ module Rscons
end end
end end
# Colorize a builder run message.
#
# @param message [String]
# Builder run message.
#
# @return [Array]
# Colorized message with color codes for {Ansi} module.
def colorize_markup(message)
if message =~ /^(.*?)(<[^>]+>)(.*)$/
prefix, code, suffix = $1, $2, $3
case code
when "<target>"
code = :magenta
when "<source>"
code = :cyan
when "<reset>"
code = :reset
end
[prefix, code, *colorize_markup(suffix)].delete_if {|v| v == ""}
else
[message]
end
end
# Return a string representation of a command. # Return a string representation of a command.
# #
# @param command [Array<String>] # @param command [Array<String>]
@@ -120,27 +95,6 @@ module Rscons
end end
end end
# Format an elapsed time in human-readable format.
#
# @return [String]
# Elapsed time in human-readable format.
def format_elapsed_time(elapsed)
hours = (elapsed / (60 * 60)).to_i
elapsed -= hours * 60 * 60
minutes = (elapsed / 60).to_i
elapsed -= minutes * 60
seconds = elapsed.ceil
result = ""
if hours > 0
result += "#{hours}h "
end
if hours > 0 || minutes > 0
result += "#{minutes}m "
end
result += "#{seconds}s"
result
end
# Make a relative path corresponding to a possibly absolute one. # Make a relative path corresponding to a possibly absolute one.
# #
# @param path [String] # @param path [String]
@@ -160,7 +114,7 @@ module Rscons
end end
end end
# Parse dependencies from a Makefile or ldc2 dependency file. # Parse dependencies from a Makefile.
# #
# This method is used internally by Rscons builders. # This method is used internally by Rscons builders.
# #
@@ -169,7 +123,7 @@ module Rscons
# #
# @return [Array<String>] # @return [Array<String>]
# Paths of dependency files. # Paths of dependency files.
def parse_dependency_file(mf_fname) def parse_makefile_deps(mf_fname)
deps = [] deps = []
buildup = '' buildup = ''
File.read(mf_fname).each_line do |line| File.read(mf_fname).each_line do |line|
@@ -177,11 +131,7 @@ module Rscons
buildup += ' ' + $1 buildup += ' ' + $1
else else
buildup += ' ' + line buildup += ' ' + line
if buildup =~ /^[^:]+\(\S+\)\s*:.*?:[^:]+\((\S+)\)/ if buildup =~ /^.*: (.*)$/
# 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
@@ -204,7 +154,6 @@ 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
if File.directory?(path_entry)
executable = executable.downcase executable = executable.downcase
dir_entries = Dir.entries(path_entry) dir_entries = Dir.entries(path_entry)
dir_entries.find do |entry| dir_entries.find do |entry|
@@ -217,7 +166,6 @@ module Rscons
return path return path
end end
end end
end
else else
path = "#{path_entry}/#{executable}" path = "#{path_entry}/#{executable}"
return path if is_executable[path] return path if is_executable[path]
+23 -50
View File
@@ -11,13 +11,11 @@ module Rscons
append(vars) append(vars)
end end
# Access the value of a variable. # Access the value of variable.
# #
# @param key [String, Symbol] # @param key [String, Symbol] The variable name.
# The variable name.
# #
# @return [Object] # @return [Object] The variable's value.
# The variable's value.
def [](key) def [](key)
if @my_vars.include?(key) if @my_vars.include?(key)
@my_vars[key] @my_vars[key]
@@ -32,38 +30,11 @@ module Rscons
end end
end end
# Access the value of a variable.
#
# This method is similar to #[] but does not make a copy-on-access copy of
# the variable accessed. This means that the returned value is NOT safe to
# be modified by the caller. Thus the caller must guarantee that it does
# not modify the returned value.
#
# @param key [String, Symbol]
# The variable name.
#
# @return [Object]
# The variable's value.
def get_var(key)
if @my_vars.include?(key)
@my_vars[key]
else
@coa_vars.each do |coa_vars|
if coa_vars.include?(key)
return coa_vars[key]
end
end
nil
end
end
# Assign a value to a variable. # Assign a value to a variable.
# #
# @param key [String, Symbol] # @param key [String, Symbol] The variable name.
# The variable name.
# #
# @param val [Object] # @param val [Object] The value to set.
# The value to set.
def []=(key, val) def []=(key, val)
@my_vars[key] = val @my_vars[key] = val
end end
@@ -124,27 +95,29 @@ module Rscons
# Expanded value with "$!{var}" variable references replaced. # Expanded value with "$!{var}" variable references replaced.
def expand_varref(varref, lambda_args) def expand_varref(varref, lambda_args)
case varref case varref
when Symbol, true, false, nil
varref
when String when String
if varref =~ /^(.*)\$\{([^}]+)\}(.*)$/ if varref =~ /^(.*)\$\{([^}]+)\}(.*)$/
prefix, varname, suffix = $1, $2, $3 prefix, varname, suffix = $1, $2, $3
prefix = expand_varref(prefix, lambda_args) unless prefix.empty? prefix = expand_varref(prefix, lambda_args) unless prefix.empty?
varval = expand_varref(get_var(varname), lambda_args) varval = expand_varref(self[varname], lambda_args)
# suffix needs no expansion since the regex matches the last occurence # suffix needs no expansion since the regex matches the last occurence
case varval 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 when Array
if prefix.is_a?(Array) if prefix.is_a?(Array)
varval.map {|vv| prefix.map {|p| "#{p}#{vv}#{suffix}"}}.flatten varval.map {|vv| prefix.map {|p| "#{p}#{vv}#{suffix}"}}.flatten
else else
varval.map {|vv| "#{prefix}#{vv}#{suffix}"} varval.map {|vv| "#{prefix}#{vv}#{suffix}"}
end end
when String, Symbol, true, false, nil
if prefix.is_a?(Array)
prefix.map {|p| "#{p}#{varval}#{suffix}"}
else else
"#{prefix}#{varval}#{suffix}" raise "Unknown construction variable type: #{varval.class} (from #{varname.inspect} => #{self[varname].inspect})"
end
else
raise "Unknown construction variable type: #{varval.class} (from #{varname.inspect} => #{get_var(varname).inspect})"
end end
else else
varref varref
@@ -153,8 +126,6 @@ module Rscons
varref.map do |ent| varref.map do |ent|
expand_varref(ent, lambda_args) expand_varref(ent, lambda_args)
end.flatten end.flatten
when Symbol, true, false, nil
varref
when Proc when Proc
expand_varref(varref[*lambda_args], lambda_args) expand_varref(varref[*lambda_args], lambda_args)
else else
@@ -164,6 +135,8 @@ module Rscons
# Return a Hash containing all variables in the VarSet. # Return a Hash containing all variables in the VarSet.
# #
# @since 1.8.0
#
# This method is not terribly efficient. It is intended to be used only by # This method is not terribly efficient. It is intended to be used only by
# debugging code to dump out a VarSet's variables. # debugging code to dump out a VarSet's variables.
# #
@@ -221,16 +194,16 @@ module Rscons
# #
# @return [Object] Deep copied value. # @return [Object] Deep copied value.
def deep_dup(obj) def deep_dup(obj)
case obj obj_class = obj.class
when String if obj_class == Hash
obj.dup
when Array
obj.map { |v| deep_dup(v) }
when Hash
obj.reduce({}) do |result, (k, v)| obj.reduce({}) do |result, (k, v)|
result[k] = deep_dup(v) result[k] = deep_dup(v)
result result
end end
elsif obj_class == Array
obj.map { |v| deep_dup(v) }
elsif obj_class == String
obj.dup
else else
obj obj
end end
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons module Rscons
# Project version. # Project version.
VERSION = "2.0.0beta2" VERSION = "1.16.0"
end end
-77
View File
@@ -1,77 +0,0 @@
<html>
<head>
<title>RScons User Guide<%= subpage_title %> - Version <%= Rscons::VERSION %></title>
<style>
body {
background-color: #CCC;
margin: 0px;
padding: 0px;
}
#body-table {
margin-left: auto;
margin-right: auto;
border-collapse: collapse;
}
#body-left-border {
width: 10px;
background-image: linear-gradient(to right, #CCC, #000, #FFF);
}
#body-right-border {
width: 10px;
background-image: linear-gradient(to right, #FFF, #000, #CCC);
}
#body-center {
background-color: #FFF;
width: 120ex;
padding: 1ex;
}
a {
text-decoration: none;
}
.separator {
height: 2em;
}
.page_nav {
width: 100%;
table-layout: fixed;
}
.page_nav_toc {
text-align: center;
}
.page_nav_next {
text-align: right;
}
.code {
padding-left: 2ex;
width: 116ex;
overflow-x: auto;
}
.ruby_code .normal {}
.ruby_code .comment { color: #005; font-style: italic; }
.ruby_code .keyword { color: #A00; font-weight: bold; }
.ruby_code .method { color: #077; }
.ruby_code .class { color: #074; }
.ruby_code .module { color: #050; }
.ruby_code .punct { color: #447; font-weight: bold; }
.ruby_code .symbol { color: #099; }
.ruby_code .string { color: #090; }
.ruby_code .char { color: #F07; }
.ruby_code .ident { color: #004; }
.ruby_code .constant { color: #07F; }
.ruby_code .regex { color: #B66; }
.ruby_code .number { color: #D55; }
.ruby_code .attribute { color: #377; }
.ruby_code .global { color: #3B7; }
.ruby_code .expr { color: #227; }
</style>
</head>
<body>
<table id="body-table">
<tr>
<td id="body-left-border"></td>
<td id="body-center"><%= content %></td>
<td id="body-right-border"></td>
</tr>
</table>
</body>
</html>
+10 -9
View File
@@ -1,13 +1,17 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require "fileutils" require "fileutils"
require "digest/md5"
if File.read("lib/rscons/version.rb") =~ /VERSION = "(.+)"/ if File.read("lib/rscons/version.rb") =~ /VERSION = "(.+)"/
VERSION = $1 VERSION = $1
else else
raise "Could not determine version." raise "Could not determine version."
end end
if `git show | head -n 1` =~ /commit\s+([0-9a-f]{7})/i
GIT = $1
else
raise "Could not determine git revision."
end
PROG_NAME = "rscons" PROG_NAME = "rscons"
START_FILE = "bin/#{PROG_NAME}" START_FILE = "bin/#{PROG_NAME}"
LIB_DIR = "lib" LIB_DIR = "lib"
@@ -59,22 +63,19 @@ 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)
encoded_compressed_script = Base64.encode64(compressed_script).gsub("\n", "") escaped_compressed_script = compressed_script.gsub("#", "#1").gsub("\n", "#2").gsub("\r", "#3").gsub("\0", "#4")
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|
fh.write(<<EOF) fh.write(<<EOF)
#!/usr/bin/env ruby #!/usr/bin/env ruby
script = File.join(File.dirname(__FILE__), ".rscons-#{VERSION}-#{hash}.rb") script = ".rscons-#{VERSION}-#{GIT}.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)
@@ -87,6 +88,6 @@ load script
if __FILE__ == $0 if __FILE__ == $0
Rscons::Cli.run(ARGV) Rscons::Cli.run(ARGV)
end end
#==>#{encoded_compressed_script} #==>#{escaped_compressed_script}
EOF EOF
end end
-227
View File
@@ -1,227 +0,0 @@
#!/usr/bin/env ruby
require "erb"
require "fileutils"
require "redcarpet"
require "syntax"
require "syntax/convertors/html"
require "rscons/version"
def load_file(file_name)
contents = File.read(file_name)
contents.gsub(/\$\{include (.*?)\}/) do |match|
include_file_name = $1
File.read(include_file_name)
end
end
class Generator
class Section
attr_reader :number
attr_reader :title
attr_reader :page
attr_reader :contents
attr_reader :anchor
def initialize(number, title, page, anchor)
@number = number
@title = title.gsub(/[`]/, "")
@page = page
@anchor = anchor
@contents = ""
end
def append(contents)
@contents += contents
end
end
class Page
attr_reader :name
attr_reader :title
attr_accessor :contents
def initialize(name, title, contents = "")
@name = name
@title = title
@contents = contents
end
end
def initialize(input, output_file, multi_page)
current_page =
if multi_page
nil
else
File.basename(output_file).sub(/\.html$/, "")
end
@sections = []
@current_section_number = [0]
@lines = input.lines
while @lines.size > 0
line = @lines.slice!(0)
if line =~ /^```(.*)$/
@sections.last.append(gather_code_section($1))
elsif line.chomp == "${remove}"
remove_section
elsif line =~ /^(#+)(>)?\s*(.*)$/
level_text, new_page_text, title_text = $1, $2, $3
level = $1.size
new_page = !new_page_text.nil?
section_number = get_next_section_number(level)
anchor = make_anchor(section_number, title_text)
if multi_page and (new_page or current_page.nil?)
current_page = anchor
end
@sections << Section.new(section_number, title_text, current_page, anchor)
@sections.last.append("#{level_text} #{section_number} #{title_text}")
elsif @sections.size > 0
@sections.last.append(line)
end
end
renderer = Redcarpet::Render::HTML.new
@markdown_renderer = Redcarpet::Markdown.new(renderer)
changelog = @markdown_renderer.render(File.read("CHANGELOG.md"))
@pages = [Page.new("toc", "Table of Contents", render_toc)]
@sections.each do |section|
unless @pages.last.name == section.page
@pages << Page.new(section.page, "#{section.number} #{section.title}")
end
@pages.last.contents += render_section(section)
end
@pages.each do |page|
page.contents.gsub!("${changelog}", changelog)
page.contents.gsub!(%r{\$\{#(.+?)\}}) do |match|
section_name = $1
href = get_link_to_section(section_name)
%[<a href="#{href}">#{section_name}</a>]
end
end
template = File.read("rb/assets/user_guide.html.erb")
erb = ERB.new(template, nil, "<>")
if multi_page
@pages.each_with_index do |page, page_index|
subpage_title = " - #{page.title}"
page_nav_bar = render_page_nav_bar(page_index)
content = page_nav_bar + separator + page.contents + separator + page_nav_bar
html_result = erb.result(binding.clone)
File.open(File.join(output_file, "#{page.name}.html"), "w") do |fh|
fh.write(html_result)
end
end
else
subpage_title = ""
content = @pages.reduce("") do |result, page|
result + page.contents
end
html_result = erb.result(binding.clone)
File.open(output_file, "w") do |fh|
fh.write(html_result)
end
end
end
def separator
%[<div class="separator"></div>]
end
def render_page_nav_bar(page_index)
page_nav_prev =
if page_index > 1
%[<a href="#{@pages[page_index - 1].name}.html">&laquo; Prev<br/>#{@pages[page_index - 1].title}</a>]
else
""
end
page_nav_toc =
if page_index > 0
%[<a href="toc.html">Table of Contents</a>]
else
""
end
page_nav_next =
if page_index < @pages.size - 1
%[<a href="#{@pages[page_index + 1].name}.html">Next &raquo<br/>#{@pages[page_index + 1].title}</a>]
else
""
end
%[<table class="page_nav"><tr>] + \
%[<td class="page_nav_prev">#{page_nav_prev}</td>] + \
%[<td class="page_nav_toc">#{page_nav_toc}</td>] + \
%[<td class="page_nav_next">#{page_nav_next}</td>] + \
%[</tr></table>]
end
def render_toc
toc_content = %[<h1>Table of Contents</h1>\n]
@sections.each do |section|
indent = section.number.split(".").size - 1
toc_content += %[<span style="padding-left: #{4 * indent}ex;">]
toc_content += %[<a href="#{section.page}.html##{section.anchor}">#{section.number} #{section.title}</a><br/>\n]
toc_content += %[</span>]
end
toc_content
end
def render_section(section)
%[<a name="#{section.anchor}" />] + \
@markdown_renderer.render(section.contents)
end
def make_anchor(section_number, section_title)
"s" + ("#{section_number} #{section_title}").gsub(/[^a-zA-Z0-9]/, "_")
end
def get_link_to_section(section_name)
section = @sections.find do |section|
section.title == section_name
end
raise "Could not find section #{section_name}" unless section
"#{section.page}.html##{section.anchor}"
end
def gather_code_section(syntax)
code = ""
loop do
line = @lines.slice!(0)
if line =~ /^```/
break
end
code += line
end
if syntax != ""
convertor = Syntax::Convertors::HTML.for_syntax(syntax)
%[<div class="code #{syntax}_code">\n#{convertor.convert(code)}\n</div>\n]
else
%[<div class="code">\n<pre>#{code}</pre>\n</div>\n]
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)
if @current_section_number.size == level - 1
@current_section_number << 1
elsif @current_section_number.size >= level
@current_section_number[level - 1] += 1
@current_section_number.slice!(level, @current_section_number.size)
else
raise "Section level change from #{@current_section_number.size} to #{level}"
end
@current_section_number.join(".")
end
end
input = load_file("doc/user_guide.md")
FileUtils.rm_rf("gen/user_guide")
FileUtils.mkdir_p("gen/user_guide")
Generator.new(input, "gen/user_guide/user_guide.html", false)
Generator.new(input, "gen/user_guide", true)
+252 -666
View File
File diff suppressed because it is too large. Load diff
+30
View File
@@ -0,0 +1,30 @@
module Rscons
describe Application do
describe ".clean" do
it "removes all build targets and created directories" do
cache = "cache"
expect(Rscons::Cache).to receive(:instance).and_return(cache)
expect(cache).to receive(:targets).and_return(["build/a.out", "build/main.o"])
expect(FileUtils).to receive(:rm_f).with("build/a.out")
expect(FileUtils).to receive(:rm_f).with("build/main.o")
expect(cache).to receive(:directories).and_return(["build/one", "build/one/two", "build", "other"])
expect(File).to receive(:directory?).with("build/one/two").and_return(true)
expect(Dir).to receive(:entries).with("build/one/two").and_return([".", ".."])
expect(Dir).to receive(:rmdir).with("build/one/two")
expect(File).to receive(:directory?).with("build/one").and_return(true)
expect(Dir).to receive(:entries).with("build/one").and_return([".", ".."])
expect(Dir).to receive(:rmdir).with("build/one")
expect(File).to receive(:directory?).with("build").and_return(true)
expect(Dir).to receive(:entries).with("build").and_return([".", ".."])
expect(Dir).to receive(:rmdir).with("build")
expect(File).to receive(:directory?).with("other").and_return(true)
expect(Dir).to receive(:entries).with("other").and_return([".", "..", "other.file"])
expect(cache).to receive(:clear)
Rscons.application.__send__(:clean)
end
end
end
end
+23 -2
View File
@@ -13,6 +13,13 @@ module Rscons
end end
end end
describe "#targets" do
it "returns a list of targets that are cached" do
cache = {"targets" => {"t1" => {}, "t2" => {}, "t3" => {}}}
expect(build_from(cache).targets).to eq ["t1", "t2", "t3"]
end
end
describe "#mkdir_p" do describe "#mkdir_p" do
it "makes directories and records any created in the cache" do it "makes directories and records any created in the cache" do
_cache = {} _cache = {}
@@ -28,7 +35,7 @@ module Rscons
expect(FileUtils).to receive(:mkdir_p).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/three")
cache.mkdir_p("one\\two\\four") cache.mkdir_p("one\\two\\four")
expect(cache.directories(false)).to eq ["one/two", "one/two/three", "one/two/four"] expect(cache.directories).to eq ["one/two", "one/two/three", "one/two/four"]
end end
it "handles absolute paths" do it "handles absolute paths" do
@@ -38,7 +45,14 @@ module Rscons
expect(File).to receive(:exists?).with("/one/two").and_return(false) expect(File).to receive(:exists?).with("/one/two").and_return(false)
expect(FileUtils).to receive(:mkdir_p).with("/one/two") expect(FileUtils).to receive(:mkdir_p).with("/one/two")
cache.mkdir_p("/one/two") cache.mkdir_p("/one/two")
expect(cache.directories(false)).to eq ["/one/two"] expect(cache.directories).to eq ["/one/two"]
end
end
describe "#directories" do
it "returns a list of directories that are cached" do
_cache = {"directories" => {"dir1" => true, "dir2" => true}}
expect(build_from(_cache).directories).to eq ["dir1", "dir2"]
end end
end end
@@ -65,5 +79,12 @@ module Rscons
expect(build_from({}).send(:calculate_checksum, "fname")).to eq "the_checksum" expect(build_from({}).send(:calculate_checksum, "fname")).to eq "the_checksum"
end end
end end
describe "#validate_json_object" do
it "raises an error when an unexpected object is passed" do
cache = build_from({})
expect {cache.__send__(:validate_json_object, :symbol)}.to raise_error /Unexpected cache value/
end
end
end end
end end
+4 -40
View File
@@ -29,12 +29,6 @@ module Rscons
end end
end end
describe ".colorize_markup" do
it "turns a builder message string with markup into an Array for Ansi module" do
expect(Util.colorize_markup("[ 1/33] Generating <foo> lexer from <source>parser/lexer.l<reset> => <target>build/e.1/parser/lexer.cc<reset>")).to eq ["[ 1/33] Generating ", "<foo>", " lexer from ", :cyan, "parser/lexer.l", :reset, " => ", :magenta, "build/e.1/parser/lexer.cc", :reset]
end
end
describe ".determine_n_threads" do describe ".determine_n_threads" do
context "when specified by environment variable" do context "when specified by environment variable" do
before(:each) do before(:each) do
@@ -121,10 +115,7 @@ 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:\\none;C:\\bin;C:\\Windows") expect(ENV).to receive(:[]).with("PATH").and_return("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
@@ -160,20 +151,6 @@ EOF
end end
end end
describe ".format_elapsed_time" do
it "includes minutes when hours is nonzero and minutes is zero" do
expect(Util.format_elapsed_time(60 * 60 + 4)).to eq "1h 0m 4s"
end
it "only includes minutes and seconds when hours is zero and minutes is nonzero" do
expect(Util.format_elapsed_time(3 * 60 + 33)).to eq "3m 33s"
end
it "only includes seconds when hours and minutes are zero" do
expect(Util.format_elapsed_time(7.84)).to eq "8s"
end
end
describe ".make_relative_path" do describe ".make_relative_path" do
context "when passed a relative path" do context "when passed a relative path" do
it "returns the path itself" do it "returns the path itself" do
@@ -200,12 +177,12 @@ EOF
end end
end end
describe ".parse_dependency_file" do describe ".parse_makefile_deps" 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_dependency_file('makefile')).to eq ['source.cc'] expect(Util.parse_makefile_deps('makefile')).to eq ['source.cc']
end end
it 'handles dependencies split across many lines' do it 'handles dependencies split across many lines' do
@@ -214,22 +191,9 @@ module.o: module.c \\
module.h \\ module.h \\
other.h other.h
EOS EOS
expect(Util.parse_dependency_file('makefile')).to eq [ expect(Util.parse_makefile_deps('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
+1 -1
View File
@@ -169,7 +169,7 @@ module Rscons
expect { v.expand_varref({a: :b}, :lambda_args) }.to raise_error /Unknown construction variable type: Hash/ expect { v.expand_varref({a: :b}, :lambda_args) }.to raise_error /Unknown construction variable type: Hash/
end end
it "raises an error when an expanded variable is an unexpected type" do it "raises an error when an expanded variable is an unexpected type" do
expect(v).to receive(:get_var).at_least(1).times.with("bad").and_return("bad_val") 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).with("bad_val", :lambda_args).and_return({a: :b})
expect(v).to receive(:expand_varref).and_call_original expect(v).to receive(:expand_varref).and_call_original
expect { v.expand_varref("${bad}", :lambda_args) }.to raise_error /Unknown construction variable type: Hash/ expect { v.expand_varref("${bad}", :lambda_args) }.to raise_error /Unknown construction variable type: Hash/